Merge branch 'develop' of github.com:frappe/erpnext into general-ui-fixes

This commit is contained in:
Suraj Shetty 2018-12-24 16:08:43 +05:30
commit 146be9f6a6
184 changed files with 69835 additions and 64782 deletions

View File

@ -4,6 +4,10 @@
"node": true, "node": true,
"es6": true "es6": true
}, },
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"extends": "eslint:recommended", "extends": "eslint:recommended",
"rules": { "rules": {
"indent": [ "indent": [
@ -50,9 +54,9 @@
"root": true, "root": true,
"globals": { "globals": {
"frappe": true, "frappe": true,
"Vue": true,
"erpnext": true, "erpnext": true,
"hub": true, "hub": true,
"$": true, "$": true,
"jQuery": true, "jQuery": true,
"moment": true, "moment": true,

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ erpnext/docs/current
*.swo *.swo
__pycache__ __pycache__
*~ *~
.idea/

View File

@ -74,7 +74,7 @@ def is_perpetual_inventory_enabled(company):
frappe.local.enable_perpetual_inventory = {} frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory: if not company in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company', frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company',
company, "enable_perpetual_inventory") or 0 company, "enable_perpetual_inventory") or 0
return frappe.local.enable_perpetual_inventory[company] return frappe.local.enable_perpetual_inventory[company]
@ -87,7 +87,7 @@ def get_default_finance_book(company=None):
frappe.local.default_finance_book = {} frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book: if not company in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value('Company', frappe.local.default_finance_book[company] = frappe.get_cached_value('Company',
company, "default_finance_book") company, "default_finance_book")
return frappe.local.default_finance_book[company] return frappe.local.default_finance_book[company]
@ -108,7 +108,7 @@ def get_region(company=None):
You can also set global company flag in `frappe.flags.company` You can also set global company flag in `frappe.flags.company`
''' '''
if company or frappe.flags.company: if company or frappe.flags.company:
return frappe.get_cached_value('Company', return frappe.get_cached_value('Company',
company or frappe.flags.company, 'country') company or frappe.flags.company, 'country')
elif frappe.flags.country: elif frappe.flags.country:
return frappe.flags.country return frappe.flags.country
@ -144,4 +144,4 @@ def is_member():
last_membership = get_last_membership() last_membership = get_last_membership()
if last_membership and getdate(last_membership.to_date) > getdate(): if last_membership and getdate(last_membership.to_date) > getdate():
return True return True
return False return False

View File

@ -29,7 +29,7 @@ def validate_service_stop_date(doc):
if date_diff(item.service_stop_date, item.service_end_date) > 0: if date_diff(item.service_stop_date, item.service_end_date) > 0:
frappe.throw(_("Service Stop Date cannot be after Service End Date")) frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if old_stop_dates and old_stop_dates[item.name] and item.service_stop_date!=old_stop_dates[item.name]: if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx))) frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx)))
def convert_deferred_expense_to_expense(start_date=None, end_date=None): def convert_deferred_expense_to_expense(start_date=None, end_date=None):

View File

@ -406,9 +406,9 @@ def get_transaction_entries(filename, headers):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_id=filename) rows = read_xlsx_file_from_attached_file(file_id=filename)
elif (filename.lower().endswith("csv")): elif (filename.lower().endswith("csv")):
from frappe.utils.file_manager import get_file_path
from frappe.utils.csvutils import read_csv_content from frappe.utils.csvutils import read_csv_content
filepath = get_file_path(filename) _file = frappe.get_doc("File", {"file_name": filename})
filepath = _file.get_full_path()
with open(filepath,'rb') as csvfile: with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read()) rows = read_csv_content(csvfile.read())
elif (filename.lower().endswith("xls")): elif (filename.lower().endswith("xls")):
@ -428,8 +428,8 @@ def get_transaction_entries(filename, headers):
return transactions return transactions
def get_rows_from_xls_file(filename): def get_rows_from_xls_file(filename):
from frappe.utils.file_manager import get_file_path _file = frappe.get_doc("File", {"file_name": filename})
filepath = get_file_path(filename) filepath = _file.get_full_path()
import xlrd import xlrd
book = xlrd.open_workbook(filepath) book = xlrd.open_workbook(filepath)
sheets = book.sheets() sheets = book.sheets()

View File

@ -15,7 +15,7 @@ class DuplicateBudgetError(frappe.ValidationError): pass
class Budget(Document): class Budget(Document):
def autoname(self): def autoname(self):
self.name = make_autoname(self.get(frappe.scrub(self.budget_against)) self.name = make_autoname(self.get(frappe.scrub(self.budget_against))
+ "/" + self.fiscal_year + "/.###") + "/" + self.fiscal_year + "/.###")
def validate(self): def validate(self):
@ -89,7 +89,7 @@ def validate_expense_against_budget(args):
if args.get('company') and not args.fiscal_year: if args.get('company') and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get('posting_date'), company=args.get('company'))[0] args.fiscal_year = get_fiscal_year(args.get('posting_date'), company=args.get('company'))[0]
frappe.flags.exception_approver_role = frappe.get_cached_value('Company', frappe.flags.exception_approver_role = frappe.get_cached_value('Company',
args.get('company'), 'exception_budget_approver_role') args.get('company'), 'exception_budget_approver_role')
if not args.account: if not args.account:
@ -106,12 +106,12 @@ def validate_expense_against_budget(args):
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})): and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
if args.project and budget_against == 'project': if args.project and budget_against == 'project':
condition = "and b.project='%s'" % frappe.db.escape(args.project) condition = "and b.project=%s" % frappe.db.escape(args.project)
args.budget_against_field = "Project" args.budget_against_field = "Project"
elif args.cost_center and budget_against == 'cost_center': elif args.cost_center and budget_against == 'cost_center':
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"]) cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"])
condition = """and exists(select name from `tabCost Center` condition = """and exists(select name from `tabCost Center`
where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt) where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt)
args.budget_against_field = "Cost Center" args.budget_against_field = "Cost Center"
@ -126,13 +126,13 @@ def validate_expense_against_budget(args):
b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded, b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded,
b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr, b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr,
b.action_if_annual_budget_exceeded_on_po, b.action_if_accumulated_monthly_budget_exceeded_on_po b.action_if_annual_budget_exceeded_on_po, b.action_if_accumulated_monthly_budget_exceeded_on_po
from from
`tabBudget` b, `tabBudget Account` ba `tabBudget` b, `tabBudget Account` ba
where where
b.name=ba.parent and b.fiscal_year=%s b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1 and ba.account=%s and b.docstatus=1
{condition} {condition}
""".format(condition=condition, """.format(condition=condition,
budget_against_field=frappe.scrub(args.get("budget_against_field"))), budget_against_field=frappe.scrub(args.get("budget_against_field"))),
(args.fiscal_year, args.account), as_dict=True) (args.fiscal_year, args.account), as_dict=True)
@ -151,12 +151,12 @@ def validate_budget_records(args, budget_records):
args["month_end_date"] = get_last_day(args.posting_date) args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget(args, budget_amount, compare_expense_with_budget(args, budget_amount,
_("Accumulated Monthly"), monthly_action, budget.budget_against, amount) _("Accumulated Monthly"), monthly_action, budget.budget_against, amount)
if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \ if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
and yearly_action != monthly_action: and yearly_action != monthly_action:
compare_expense_with_budget(args, flt(budget.budget_amount), compare_expense_with_budget(args, flt(budget.budget_amount),
_("Annual"), yearly_action, budget.budget_against, amount) _("Annual"), yearly_action, budget.budget_against, amount)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
@ -166,9 +166,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
currency = frappe.get_cached_value('Company', args.company, 'default_currency') currency = frappe.get_cached_value('Company', args.company, 'default_currency')
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format( msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
_(action_for), frappe.bold(args.account), args.budget_against_field, _(action_for), frappe.bold(args.account), args.budget_against_field,
frappe.bold(budget_against), frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)), frappe.bold(fmt_money(budget_amount, currency=currency)),
frappe.bold(fmt_money(diff, currency=currency))) frappe.bold(fmt_money(diff, currency=currency)))
if (frappe.flags.exception_approver_role if (frappe.flags.exception_approver_role
@ -250,12 +250,12 @@ def get_actual_expense(args):
condition1 = " and gle.posting_date <= %(month_end_date)s" \ condition1 = " and gle.posting_date <= %(month_end_date)s" \
if args.get("month_end_date") else "" if args.get("month_end_date") else ""
if args.budget_against_field == "Cost Center": if args.budget_against_field == "Cost Center":
lft_rgt = frappe.db.get_value(args.budget_against_field, lft_rgt = frappe.db.get_value(args.budget_against_field,
args.budget_against, ["lft", "rgt"], as_dict=1) args.budget_against, ["lft", "rgt"], as_dict=1)
args.update(lft_rgt) args.update(lft_rgt)
condition2 = """and exists(select name from `tabCost Center` condition2 = """and exists(select name from `tabCost Center`
where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)""" where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)"""
elif args.budget_against_field == "Project": elif args.budget_against_field == "Project":
condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)" condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)"

View File

@ -19,7 +19,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Cashier-closing-", "default": "POS-CLO-",
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@ -32,7 +32,7 @@
"label": "Series", "label": "Series",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Cashier-closing-\n", "options": "POS-CLO-",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -230,6 +230,37 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00",
"fieldname": "returns",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Returns",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "2",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -364,7 +395,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-09-03 10:59:54.500567", "modified": "2018-10-21 14:26:15.812416",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cashier Closing", "name": "Cashier Closing",
@ -400,4 +431,4 @@
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1,
"track_seen": 0 "track_seen": 0
} }

View File

@ -29,7 +29,7 @@ class CashierClosing(Document):
for i in self.payments: for i in self.payments:
total += flt(i.amount) total += flt(i.amount)
self.net_amount = total + self.outstanding_amount + self.expense - self.custody self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns
def validate_time(self): def validate_time(self):
if self.from_time >= self.time: if self.from_time >= self.time:

View File

@ -162,7 +162,7 @@ def check_freezing_date(posting_date, adv_adj=False):
def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False): def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
if party_type and party: if party_type and party:
party_condition = " and party_type='{0}' and party='{1}'"\ party_condition = " and party_type={0} and party={1}"\
.format(frappe.db.escape(party_type), frappe.db.escape(party)) .format(frappe.db.escape(party_type), frappe.db.escape(party))
else: else:
party_condition = "" party_condition = ""

View File

@ -800,7 +800,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s
and (jv_detail.reference_type is null or jv_detail.reference_type = '') and (jv_detail.reference_type is null or jv_detail.reference_type = '')
and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(frappe.db.escape(searchfield)), and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield),
(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len)) (filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len))

View File

@ -19,7 +19,7 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non
condition = '' condition = ''
if company: if company:
condition = " and company='%s' " % frappe.db.escape(company) condition = " and company=%s " % frappe.db.escape(company)
if not include_expired_entry: if not include_expired_entry:
condition += " and expiry_date>='%s' " % expiry_date condition += " and expiry_date>='%s' " % expiry_date

View File

@ -549,7 +549,7 @@ def get_outstanding_reference_documents(args):
# Get positive outstanding sales /purchase invoices/ Fees # Get positive outstanding sales /purchase invoices/ Fees
condition = "" condition = ""
if args.get("voucher_type") and args.get("voucher_no"): if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type='{0}' and voucher_no='{1}'"\ condition = " and voucher_type={0} and voucher_no={1}"\
.format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])) .format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]))
# Add cost center condition # Add cost center condition

View File

@ -13,20 +13,20 @@ class PaymentReconciliation(Document):
def get_unreconciled_entries(self): def get_unreconciled_entries(self):
self.get_nonreconciled_payment_entries() self.get_nonreconciled_payment_entries()
self.get_invoice_entries() self.get_invoice_entries()
def get_nonreconciled_payment_entries(self): def get_nonreconciled_payment_entries(self):
self.check_mandatory_to_fetch() self.check_mandatory_to_fetch()
payment_entries = self.get_payment_entries() payment_entries = self.get_payment_entries()
journal_entries = self.get_jv_entries() journal_entries = self.get_jv_entries()
self.add_payment_entries(payment_entries + journal_entries) self.add_payment_entries(payment_entries + journal_entries)
def get_payment_entries(self): def get_payment_entries(self):
order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order" order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order"
payment_entries = get_advance_payment_entries(self.party_type, self.party, payment_entries = get_advance_payment_entries(self.party_type, self.party,
self.receivable_payable_account, order_doctype, against_all_orders=True) self.receivable_payable_account, order_doctype, against_all_orders=True)
return payment_entries return payment_entries
def get_jv_entries(self): def get_jv_entries(self):
@ -38,8 +38,8 @@ class PaymentReconciliation(Document):
journal_entries = frappe.db.sql(""" journal_entries = frappe.db.sql("""
select select
"Journal Entry" as reference_type, t1.name as reference_name, "Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row, t1.posting_date, t1.remark as remarks, t2.name as reference_row,
{dr_or_cr} as amount, t2.is_advance {dr_or_cr} as amount, t2.is_advance
from from
`tabJournal Entry` t1, `tabJournal Entry Account` t2 `tabJournal Entry` t1, `tabJournal Entry Account` t2
@ -47,8 +47,8 @@ class PaymentReconciliation(Document):
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1 t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
and t2.party_type = %(party_type)s and t2.party = %(party)s and t2.party_type = %(party_type)s and t2.party = %(party)s
and t2.account = %(account)s and {dr_or_cr} > 0 and t2.account = %(account)s and {dr_or_cr} > 0
and (t2.reference_type is null or t2.reference_type = '' or and (t2.reference_type is null or t2.reference_type = '' or
(t2.reference_type in ('Sales Order', 'Purchase Order') (t2.reference_type in ('Sales Order', 'Purchase Order')
and t2.reference_name is not null and t2.reference_name != '')) and t2.reference_name is not null and t2.reference_name != ''))
and (CASE and (CASE
WHEN t1.voucher_type in ('Debit Note', 'Credit Note') WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
@ -106,7 +106,7 @@ class PaymentReconciliation(Document):
self.validate_invoice() self.validate_invoice()
dr_or_cr = ("credit_in_account_currency" dr_or_cr = ("credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency") if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
lst = [] lst = []
for e in self.get('payments'): for e in self.get('payments'):
if e.invoice_number and e.allocated_amount: if e.invoice_number and e.allocated_amount:
@ -124,11 +124,11 @@ class PaymentReconciliation(Document):
'unadjusted_amount' : flt(e.amount), 'unadjusted_amount' : flt(e.amount),
'allocated_amount' : flt(e.allocated_amount) 'allocated_amount' : flt(e.allocated_amount)
})) }))
if lst: if lst:
from erpnext.accounts.utils import reconcile_against_document from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst) reconcile_against_document(lst)
msgprint(_("Successfully Reconciled")) msgprint(_("Successfully Reconciled"))
self.get_unreconciled_entries() self.get_unreconciled_entries()
@ -171,8 +171,8 @@ class PaymentReconciliation(Document):
frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row")) frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
def check_condition(self): def check_condition(self):
cond = " and posting_date >= '{0}'".format(frappe.db.escape(self.from_date)) if self.from_date else "" cond = " and posting_date >= {0}".format(frappe.db.escape(self.from_date)) if self.from_date else ""
cond += " and posting_date <= '{0}'".format(frappe.db.escape(self.to_date)) if self.to_date else "" cond += " and posting_date <= {0}".format(frappe.db.escape(self.to_date)) if self.to_date else ""
dr_or_cr = ("debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable' dr_or_cr = ("debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
else "credit_in_account_currency") else "credit_in_account_currency")

View File

@ -107,7 +107,7 @@ def get_item_groups(pos_profile):
if pos_profile.get('item_groups'): if pos_profile.get('item_groups'):
# Get items based on the item groups defined in the POS profile # Get items based on the item groups defined in the POS profile
for data in pos_profile.get('item_groups'): for data in pos_profile.get('item_groups'):
item_groups.extend(["'%s'" % frappe.db.escape(d.name) for d in get_child_nodes('Item Group', data.item_group)]) item_groups.extend(["%s" % frappe.db.escape(d.name) for d in get_child_nodes('Item Group', data.item_group)])
return list(set(item_groups)) return list(set(item_groups))

View File

@ -255,10 +255,12 @@ def get_pricing_rules(args):
if parent_groups: if parent_groups:
if allow_blank: parent_groups.append('') if allow_blank: parent_groups.append('')
condition = " ifnull("+field+", '') in ('" + \ condition = "ifnull({field}, '') in ({parent_groups})".format(
"', '".join([frappe.db.escape(d) for d in parent_groups])+"')" field=field,
frappe.flags.tree_conditions[key] = condition parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
)
frappe.flags.tree_conditions[key] = condition
return condition return condition

View File

@ -15,6 +15,7 @@
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -44,10 +45,12 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -77,10 +80,12 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -99,7 +104,7 @@
"no_copy": 0, "no_copy": 0,
"oldfieldname": "charge_type", "oldfieldname": "charge_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total", "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total\nOn Item Quantity",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@ -109,10 +114,12 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -141,10 +148,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -172,10 +181,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -200,10 +211,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -232,10 +245,12 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -265,10 +280,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -297,11 +314,13 @@
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0, "unique": 0,
"width": "300px" "width": "300px"
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -327,10 +346,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -358,10 +379,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -387,10 +410,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -419,10 +444,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -450,10 +477,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -482,10 +511,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -511,10 +542,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -542,10 +575,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -573,10 +608,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -604,10 +641,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -635,10 +674,12 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -666,6 +707,7 @@
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
@ -679,7 +721,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-12-06 13:37:44.483509", "modified": "2018-09-19 13:48:32.755198",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Taxes and Charges", "name": "Purchase Taxes and Charges",
@ -690,5 +732,6 @@
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"track_changes": 1, "track_changes": 1,
"track_seen": 0 "track_seen": 0,
"track_views": 0
} }

View File

@ -375,7 +375,7 @@ class TestSalesInvoice(unittest.TestCase):
si.insert() si.insert()
self.assertEqual(si.net_total, 4600) self.assertEqual(si.net_total, 4600)
self.assertEqual(si.get("taxes")[0].tax_amount, 874.0) self.assertEqual(si.get("taxes")[0].tax_amount, 874.0)
self.assertEqual(si.get("taxes")[0].total, 5474.0) self.assertEqual(si.get("taxes")[0].total, 5474.0)
@ -405,12 +405,12 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.total, 975) self.assertEqual(si.total, 975)
self.assertEqual(si.net_total, 900) self.assertEqual(si.net_total, 900)
self.assertEqual(si.get("taxes")[0].tax_amount, 216.0) self.assertEqual(si.get("taxes")[0].tax_amount, 216.0)
self.assertEqual(si.get("taxes")[0].total, 1116.0) self.assertEqual(si.get("taxes")[0].total, 1116.0)
self.assertEqual(si.grand_total, 1116.0) self.assertEqual(si.grand_total, 1116.0)
def test_inclusive_rate_validations(self): def test_inclusive_rate_validations(self):
si = frappe.copy_doc(test_records[2]) si = frappe.copy_doc(test_records[2])
for i, tax in enumerate(si.get("taxes")): for i, tax in enumerate(si.get("taxes")):
@ -552,7 +552,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(si.grand_total, 1215.90) self.assertEqual(si.grand_total, 1215.90)
self.assertEqual(si.rounding_adjustment, 0.01) self.assertEqual(si.rounding_adjustment, 0.01)
self.assertEqual(si.base_rounding_adjustment, 0.50) self.assertEqual(si.base_rounding_adjustment, 0.50)
def test_outstanding(self): def test_outstanding(self):
w = self.make() w = self.make()
@ -923,7 +923,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertRaises(SerialNoWarehouseError, si.submit) self.assertRaises(SerialNoWarehouseError, si.submit)
def test_serial_numbers_against_delivery_note(self): def test_serial_numbers_against_delivery_note(self):
""" """
check if the sales invoice item serial numbers and the delivery note items check if the sales invoice item serial numbers and the delivery note items
serial numbers are same serial numbers are same
""" """
@ -1238,7 +1238,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_item_wise_tax_breakup_india(self): def test_item_wise_tax_breakup_india(self):
frappe.flags.country = "India" frappe.flags.country = "India"
si = self.create_si_to_test_tax_breakup() si = self.create_si_to_test_tax_breakup()
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
@ -1256,12 +1256,12 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(itemised_tax, expected_itemised_tax) self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
frappe.flags.country = None frappe.flags.country = None
def test_item_wise_tax_breakup_outside_india(self): def test_item_wise_tax_breakup_outside_india(self):
frappe.flags.country = "United States" frappe.flags.country = "United States"
si = self.create_si_to_test_tax_breakup() si = self.create_si_to_test_tax_breakup()
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
@ -1287,7 +1287,7 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(itemised_tax, expected_itemised_tax) self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
frappe.flags.country = None frappe.flags.country = None
def create_si_to_test_tax_breakup(self): def create_si_to_test_tax_breakup(self):
@ -1375,7 +1375,7 @@ class TestSalesInvoice(unittest.TestCase):
shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test") shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test")
si = frappe.copy_doc(test_records[2]) si = frappe.copy_doc(test_records[2])
si.shipping_rule = shipping_rule.name si.shipping_rule = shipping_rule.name
si.insert() si.insert()
@ -1392,14 +1392,14 @@ class TestSalesInvoice(unittest.TestCase):
"cost_center": shipping_rule.cost_center, "cost_center": shipping_rule.cost_center,
"tax_amount": shipping_amount, "tax_amount": shipping_amount,
"description": shipping_rule.name "description": shipping_rule.name
} }
si.append("taxes", shipping_charge) si.append("taxes", shipping_charge)
si.save() si.save()
self.assertEqual(si.net_total, 1250) self.assertEqual(si.net_total, 1250)
self.assertEqual(si.total_taxes_and_charges, 577.05) self.assertEqual(si.total_taxes_and_charges, 577.05)
self.assertEqual(si.grand_total, 1827.05) self.assertEqual(si.grand_total, 1827.05)
def test_create_invoice_without_terms(self): def test_create_invoice_without_terms(self):
si = create_sales_invoice(do_not_save=1) si = create_sales_invoice(do_not_save=1)
@ -1496,7 +1496,7 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save() accounts_settings.save()
@ -1524,9 +1524,9 @@ def create_sales_invoice(**args):
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1, "qty": args.qty or 1,
"rate": args.rate or 100, "rate": args.rate or 100,
"income_account": "Sales - _TC", "income_account": args.income_account or "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC", "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no "serial_no": args.serial_no
}) })

View File

@ -33,7 +33,7 @@
"no_copy": 0, "no_copy": 0,
"oldfieldname": "charge_type", "oldfieldname": "charge_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total", "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total\nOn Item Quantity",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@ -652,7 +652,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-21 16:15:51.518582", "modified": "2018-09-19 13:48:59.341454",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Taxes and Charges", "name": "Sales Taxes and Charges",
@ -666,4 +666,4 @@
"track_changes": 0, "track_changes": 0,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@ -75,7 +75,7 @@ class TaxRule(Document):
for d in filters: for d in filters:
if conds: if conds:
conds += " and " conds += " and "
conds += """ifnull({0}, '') = '{1}'""".format(d, frappe.db.escape(cstr(filters[d]))) conds += """ifnull({0}, '') = {1}""".format(d, frappe.db.escape(cstr(filters[d])))
if self.from_date and self.to_date: if self.from_date and self.to_date:
conds += """ and ((from_date > '{from_date}' and from_date < '{to_date}') or conds += """ and ((from_date > '{from_date}' and from_date < '{to_date}') or
@ -152,7 +152,7 @@ def get_tax_template(posting_date, args):
customer_group_condition = get_customer_group_condition(value) customer_group_condition = get_customer_group_condition(value)
conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition)) conditions.append("ifnull({0}, '') in ('', {1})".format(key, customer_group_condition))
else: else:
conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value)))) conditions.append("ifnull({0}, '') in ('', {1})".format(key, frappe.db.escape(cstr(value))))
tax_rule = frappe.db.sql("""select * from `tabTax Rule` tax_rule = frappe.db.sql("""select * from `tabTax Rule`
where {0}""".format(" and ".join(conditions)), as_dict = True) where {0}""".format(" and ".join(conditions)), as_dict = True)
@ -180,7 +180,7 @@ def get_tax_template(posting_date, args):
def get_customer_group_condition(customer_group): def get_customer_group_condition(customer_group):
condition = "" condition = ""
customer_groups = ["'%s'"%(frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)] customer_groups = ["%s"%(frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)]
if customer_groups: if customer_groups:
condition = ",".join(['%s'] * len(customer_groups))%(tuple(customer_groups)) condition = ",".join(['%s'] * len(customer_groups))%(tuple(customer_groups))
return condition return condition

View File

@ -443,7 +443,7 @@ def get_timeline_data(doctype, name):
# fetch and append data from Activity Log # fetch and append data from Activity Log
data += frappe.db.sql("""select {fields} data += frappe.db.sql("""select {fields}
from `tabActivity Log` from `tabActivity Log`
where reference_doctype="{doctype}" and reference_name="{name}" where reference_doctype={doctype} and reference_name={name}
and status!='Success' and creation > {after} and status!='Success' and creation > {after}
{group_by} order by creation desc {group_by} order by creation desc
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields, """.format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,

View File

@ -107,6 +107,11 @@ frappe.query_reports["Accounts Receivable"] = {
"label": __("Show PDC in Print"), "label": __("Show PDC in Print"),
"fieldtype": "Check", "fieldtype": "Check",
}, },
{
"fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"),
"fieldtype": "Check",
},
{ {
"fieldname":"tax_id", "fieldname":"tax_id",
"label": __("Tax Id"), "label": __("Tax Id"),

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _, scrub from frappe import _, scrub
from frappe.utils import getdate, nowdate, flt, cint from frappe.utils import getdate, nowdate, flt, cint, formatdate, cstr
class ReceivablePayableReport(object): class ReceivablePayableReport(object):
def __init__(self, filters=None): def __init__(self, filters=None):
@ -57,6 +57,21 @@ class ReceivablePayableReport(object):
credit_or_debit_note = "Credit Note" if args.get("party_type") == "Customer" else "Debit Note" credit_or_debit_note = "Credit Note" if args.get("party_type") == "Customer" else "Debit Note"
if self.filters.based_on_payment_terms:
columns.append({
"label": "Payment Term",
"fieldname": "payment_term",
"fieldtype": "Data",
"width": 120
})
columns.append({
"label": "Invoice Grand Total",
"fieldname": "invoice_grand_total",
"fieldtype": "Currency",
"options": "currency",
"width": 120
})
for label in ("Invoiced Amount", "Paid Amount", credit_or_debit_note, "Outstanding Amount"): for label in ("Invoiced Amount", "Paid Amount", credit_or_debit_note, "Outstanding Amount"):
columns.append({ columns.append({
"label": label, "label": label,
@ -97,12 +112,6 @@ class ReceivablePayableReport(object):
"options": "Currency", "options": "Currency",
"width": 100 "width": 100
}, },
{
"fieldname": "pdc/lc_date",
"label": _("PDC/LC Date"),
"fieldtype": "Date",
"width": 110
},
{ {
"fieldname": "pdc/lc_ref", "fieldname": "pdc/lc_ref",
"label": _("PDC/LC Ref"), "label": _("PDC/LC Ref"),
@ -113,14 +122,14 @@ class ReceivablePayableReport(object):
"fieldname": "pdc/lc_amount", "fieldname": "pdc/lc_amount",
"label": _("PDC/LC Amount"), "label": _("PDC/LC Amount"),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "Currency", "options": "currency",
"width": 130 "width": 130
}, },
{ {
"fieldname": "remaining_balance", "fieldname": "remaining_balance",
"label": _("Remaining Balance"), "label": _("Remaining Balance"),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "Currency", "options": "currency",
"width": 130 "width": 130
}] }]
@ -151,108 +160,203 @@ class ReceivablePayableReport(object):
def get_data(self, party_naming_by, args): def get_data(self, party_naming_by, args):
from erpnext.accounts.utils import get_currency_precision from erpnext.accounts.utils import get_currency_precision
currency_precision = get_currency_precision() or 2 self.currency_precision = get_currency_precision() or 2
dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" self.dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit"
future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type")) future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type"))
if not self.filters.get("company"): if not self.filters.get("company"):
self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company')
company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency")
return_entries = self.get_return_entries(args.get("party_type")) return_entries = self.get_return_entries(args.get("party_type"))
data = [] data = []
pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date) self.pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date)
gl_entries_data = self.get_entries_till(self.filters.report_date, args.get("party_type")) gl_entries_data = self.get_entries_till(self.filters.report_date, args.get("party_type"))
if gl_entries_data: if gl_entries_data:
voucher_nos = [d.voucher_no for d in gl_entries_data] or [] voucher_nos = [d.voucher_no for d in gl_entries_data] or []
dn_details = get_dn_details(args.get("party_type"), voucher_nos) dn_details = get_dn_details(args.get("party_type"), voucher_nos)
voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) self.voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details)
if self.filters.based_on_payment_terms:
self.payment_term_map = self.get_payment_term_detail(voucher_nos)
for gle in gl_entries_data: for gle in gl_entries_data:
if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers): if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers):
outstanding_amount, credit_note_amount = self.get_outstanding_amount(gle, outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount(
self.filters.report_date, dr_or_cr, return_entries, currency_precision) gle,self.filters.report_date, self.dr_or_cr, return_entries)
if abs(outstanding_amount) > 0.1/10**currency_precision:
row = [gle.posting_date, gle.party]
# customer / supplier name temp_outstanding_amt = outstanding_amount
if party_naming_by == "Naming Series": temp_credit_note_amt = credit_note_amount
row += [self.get_party_name(gle.party_type, gle.party)]
# get due date if abs(outstanding_amount) > 0.1/10**self.currency_precision:
due_date = voucher_details.get(gle.voucher_no, {}).get("due_date", "") if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no):
bill_date = voucher_details.get(gle.voucher_no, {}).get("bill_date", "") for d in self.payment_term_map.get(gle.voucher_no):
# Allocate payment amount based on payment terms(FIFO order)
payment_amount, d.payment_amount = self.allocate_based_on_fifo(payment_amount, d.payment_term_amount)
row += [gle.voucher_type, gle.voucher_no, due_date] term_outstanding_amount = d.payment_term_amount - d.payment_amount
# get supplier bill details # Allocate credit note based on payment terms(FIFO order)
if args.get("party_type") == "Supplier": credit_note_amount, d.credit_note_amount = self.allocate_based_on_fifo(credit_note_amount, term_outstanding_amount)
row += [
voucher_details.get(gle.voucher_no, {}).get("bill_no", ""),
voucher_details.get(gle.voucher_no, {}).get("bill_date", "")
]
# invoiced and paid amounts term_outstanding_amount -= d.credit_note_amount
invoiced_amount = gle.get(dr_or_cr) if (gle.get(dr_or_cr) > 0) else 0
paid_amt = invoiced_amount - outstanding_amount - credit_note_amount row_outstanding = term_outstanding_amount
row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] # Allocate PDC based on payment terms(FIFO order)
d.pdc_details, d.pdc_amount = self.allocate_pdc_amount_in_fifo(gle, row_outstanding)
if term_outstanding_amount > 0:
row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount,
d.credit_note_amount, d.due_date, d.payment_amount , d.payment_term_amount,
d.description, d.pdc_amount, d.pdc_details)
data.append(row)
if credit_note_amount:
row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, temp_outstanding_amt,
temp_credit_note_amt)
data.append(row)
# ageing data
if self.filters.ageing_based_on == "Due Date":
entry_date = due_date
elif self.filters.ageing_based_on == "Supplier Invoice Date":
entry_date = bill_date
else: else:
entry_date = gle.posting_date row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, outstanding_amount,
credit_note_amount)
row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), data.append(row)
cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount)
# issue 6371-Ageing buckets should not have amounts if due date is not reached
if self.filters.ageing_based_on == "Due Date" \
and getdate(due_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0
if self.filters.ageing_based_on == "Supplier Invoice Date" \
and getdate(bill_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0
if self.filters.get(scrub(args.get("party_type"))):
row.append(gle.account_currency)
else:
row.append(company_currency)
pdc = pdc_details.get((gle.voucher_no, gle.party), {})
remaining_balance = outstanding_amount - flt(pdc.get("pdc_amount"))
row += [pdc.get("pdc_date"), pdc.get("pdc_ref"),
flt(pdc.get("pdc_amount")), remaining_balance]
if args.get('party_type') == 'Customer':
# customer LPO
row += [voucher_details.get(gle.voucher_no, {}).get("po_no")]
# Delivery Note
row += [voucher_details.get(gle.voucher_no, {}).get("delivery_note")]
# customer territory / supplier group
if args.get("party_type") == "Customer":
row += [self.get_territory(gle.party), self.get_customer_group(gle.party),
voucher_details.get(gle.voucher_no, {}).get("sales_person")]
if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(gle.party)]
row.append(gle.remarks)
data.append(row)
return data return data
def allocate_pdc_amount_in_fifo(self, gle, row_outstanding):
pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), [])
pdc_details = []
pdc_amount = 0
for pdc in pdc_list:
if row_outstanding <= pdc.pdc_amount:
pdc_amount += row_outstanding
pdc.pdc_amount -= row_outstanding
if row_outstanding and pdc.pdc_ref and pdc.pdc_date:
pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date))
row_outstanding = 0
else:
pdc_amount = pdc.pdc_amount
if pdc.pdc_amount and pdc.pdc_ref and pdc.pdc_date:
pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date))
pdc.pdc_amount = 0
row_outstanding -= pdc_amount
return pdc_details, pdc_amount
def prepare_row_without_payment_terms(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount):
pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), [])
pdc_amount = 0
pdc_details = []
for d in pdc_list:
pdc_amount += flt(d.pdc_amount)
if pdc_amount and d.pdc_ref and d.pdc_date:
pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date))
row = self.prepare_row(party_naming_by, args, gle, outstanding_amount,
credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details)
return row
def allocate_based_on_fifo(self, total_amount, row_amount):
allocated_amount = 0
if row_amount <= total_amount:
allocated_amount = row_amount
total_amount -= row_amount
else:
allocated_amount = total_amount
total_amount = 0
return total_amount, allocated_amount
def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount,
due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None, pdc_details=None):
row = [gle.posting_date, gle.party]
# customer / supplier name
if party_naming_by == "Naming Series":
row += [self.get_party_name(gle.party_type, gle.party)]
# get due date
if not due_date:
due_date = self.voucher_details.get(gle.voucher_no, {}).get("due_date", "")
bill_date = self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "")
row += [gle.voucher_type, gle.voucher_no, due_date]
# get supplier bill details
if args.get("party_type") == "Supplier":
row += [
self.voucher_details.get(gle.voucher_no, {}).get("bill_no", ""),
self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "")
]
# invoiced and paid amounts
invoiced_amount = gle.get(self.dr_or_cr) if (gle.get(self.dr_or_cr) > 0) else 0
if self.filters.based_on_payment_terms:
row+=[payment_term, invoiced_amount]
if payment_term_amount:
invoiced_amount = payment_term_amount
if not payment_term_amount:
paid_amt = invoiced_amount - outstanding_amount - credit_note_amount
row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount]
# ageing data
if self.filters.ageing_based_on == "Due Date":
entry_date = due_date
elif self.filters.ageing_based_on == "Supplier Invoice Date":
entry_date = bill_date
else:
entry_date = gle.posting_date
row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2),
cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount)
# issue 6371-Ageing buckets should not have amounts if due date is not reached
if self.filters.ageing_based_on == "Due Date" \
and getdate(due_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0
if self.filters.ageing_based_on == "Supplier Invoice Date" \
and getdate(bill_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0
if self.filters.get(scrub(args.get("party_type"))):
row.append(gle.account_currency)
else:
row.append(self.company_currency)
remaining_balance = outstanding_amount - flt(pdc_amount)
pdc_details = ", ".join(pdc_details)
row += [pdc_details, pdc_amount, remaining_balance]
if args.get('party_type') == 'Customer':
# customer LPO
row += [self.voucher_details.get(gle.voucher_no, {}).get("po_no")]
# Delivery Note
row += [self.voucher_details.get(gle.voucher_no, {}).get("delivery_note")]
# customer territory / supplier group
if args.get("party_type") == "Customer":
row += [self.get_territory(gle.party), self.get_customer_group(gle.party),
self.voucher_details.get(gle.voucher_no, {}).get("sales_person")]
if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(gle.party)]
row.append(gle.remarks)
return row
def get_entries_after(self, report_date, party_type): def get_entries_after(self, report_date, party_type):
# returns a distinct list # returns a distinct list
return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries(party_type, report_date, for_future=True)])) return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries(party_type, report_date, for_future=True)]))
@ -280,25 +384,25 @@ class ReceivablePayableReport(object):
doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice" doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice"
return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})] return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})]
def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries, currency_precision): def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries):
payment_amount, credit_note_amount = 0.0, 0.0 payment_amount, credit_note_amount = 0.0, 0.0
reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit" reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit"
for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no): for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no):
if getdate(e.posting_date) <= report_date and e.name!=gle.name: if getdate(e.posting_date) <= report_date and e.name!=gle.name:
amount = flt(e.get(reverse_dr_or_cr), currency_precision) - flt(e.get(dr_or_cr), currency_precision) amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision)
if e.voucher_no not in return_entries: if e.voucher_no not in return_entries:
payment_amount += amount payment_amount += amount
else: else:
credit_note_amount += amount credit_note_amount += amount
outstanding_amount = (flt((flt(gle.get(dr_or_cr), currency_precision) outstanding_amount = (flt((flt(gle.get(dr_or_cr), self.currency_precision)
- flt(gle.get(reverse_dr_or_cr), currency_precision) - flt(gle.get(reverse_dr_or_cr), self.currency_precision)
- payment_amount - credit_note_amount), currency_precision)) - payment_amount - credit_note_amount), self.currency_precision))
credit_note_amount = flt(credit_note_amount, currency_precision) credit_note_amount = flt(credit_note_amount, self.currency_precision)
return outstanding_amount, credit_note_amount return outstanding_amount, credit_note_amount, payment_amount
def get_party_name(self, party_type, party_name): def get_party_name(self, party_type, party_name):
return self.get_party_map(party_type).get(party_name, {}).get("customer_name" if party_type == "Customer" else "supplier_name") or "" return self.get_party_map(party_type).get(party_name, {}).get("customer_name" if party_type == "Customer" else "supplier_name") or ""
@ -383,7 +487,7 @@ class ReceivablePayableReport(object):
conditions.append("""party in (select name from tabCustomer conditions.append("""party in (select name from tabCustomer
where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1} where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1}
and name=tabCustomer.customer_group))""".format(lft, rgt)) and name=tabCustomer.customer_group))""".format(lft, rgt))
if self.filters.get("territory"): if self.filters.get("territory"):
lft, rgt = frappe.db.get_value("Territory", lft, rgt = frappe.db.get_value("Territory",
self.filters.get("territory"), ["lft", "rgt"]) self.filters.get("territory"), ["lft", "rgt"])
@ -415,7 +519,7 @@ class ReceivablePayableReport(object):
conditions.append("""party in (select name from tabSupplier conditions.append("""party in (select name from tabSupplier
where supplier_group=%s)""") where supplier_group=%s)""")
values.append(self.filters.get("supplier_group")) values.append(self.filters.get("supplier_group"))
return " and ".join(conditions), values return " and ".join(conditions), values
def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher): def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher):
@ -432,6 +536,31 @@ class ReceivablePayableReport(object):
.get(against_voucher_type, {})\ .get(against_voucher_type, {})\
.get(against_voucher, []) .get(against_voucher, [])
def get_payment_term_detail(self, voucher_nos):
payment_term_map = frappe._dict()
payment_terms_details = frappe.db.sql(""" select si.name,
party_account_currency, currency, si.conversion_rate,
ps.due_date, ps.payment_amount, ps.description
from `tabSales Invoice` si, `tabPayment Schedule` ps
where si.name = ps.parent and
si.docstatus = 1 and si.company = %s and
si.name in (%s) order by ps.due_date
""" % (frappe.db.escape(self.filters.company), ','.join(['%s'] *len(voucher_nos))),
(tuple(voucher_nos)), as_dict = 1)
for d in payment_terms_details:
if self.filters.get("customer") and d.currency == d.party_account_currency:
payment_term_amount = d.payment_amount
else:
payment_term_amount = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision)
payment_term_map.setdefault(d.name, []).append(frappe._dict({
"due_date": d.due_date,
"payment_term_amount": payment_term_amount,
"description": d.description
}))
return payment_term_map
def get_chart_data(self, columns, data): def get_chart_data(self, columns, data):
ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4] ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4]
@ -479,12 +608,11 @@ def get_ageing_data(first_range, second_range, third_range, age_as_on, entry_dat
def get_pdc_details(party_type, report_date): def get_pdc_details(party_type, report_date):
pdc_details = frappe._dict() pdc_details = frappe._dict()
pdc_via_pe = frappe.db.sql("""
for pdc in frappe.db.sql("""
select select
pref.reference_name as invoice_no, pent.party, pent.party_type, pref.reference_name as invoice_no, pent.party, pent.party_type,
max(pent.posting_date) as pdc_date, sum(ifnull(pref.allocated_amount,0)) as pdc_amount, pent.posting_date as pdc_date, ifnull(pref.allocated_amount,0) as pdc_amount,
GROUP_CONCAT(pent.reference_no SEPARATOR ', ') as pdc_ref pent.reference_no as pdc_ref
from from
`tabPayment Entry` as pent inner join `tabPayment Entry Reference` as pref `tabPayment Entry` as pent inner join `tabPayment Entry Reference` as pref
on on
@ -492,19 +620,22 @@ def get_pdc_details(party_type, report_date):
where where
pent.docstatus < 2 and pent.posting_date > %s pent.docstatus < 2 and pent.posting_date > %s
and pent.party_type = %s and pent.party_type = %s
group by pent.party, pref.reference_name""", (report_date, party_type), as_dict=1): """, (report_date, party_type), as_dict=1)
pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc)
for pdc in pdc_via_pe:
pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc)
if scrub(party_type): if scrub(party_type):
amount_field = ("jea.debit_in_account_currency" amount_field = ("jea.debit_in_account_currency"
if party_type == 'Supplier' else "jea.credit_in_account_currency") if party_type == 'Supplier' else "jea.credit_in_account_currency")
else: else:
amount_field = "jea.debit + jea.credit" amount_field = "jea.debit + jea.credit"
for pdc in frappe.db.sql(""" pdc_via_je = frappe.db.sql("""
select select
jea.reference_name as invoice_no, jea.party, jea.party_type, jea.reference_name as invoice_no, jea.party, jea.party_type,
max(je.posting_date) as pdc_date, sum(ifnull({0},0)) as pdc_amount, je.posting_date as pdc_date, ifnull({0},0) as pdc_amount,
GROUP_CONCAT(je.cheque_no SEPARATOR ', ') as pdc_ref je.cheque_no as pdc_ref
from from
`tabJournal Entry` as je inner join `tabJournal Entry Account` as jea `tabJournal Entry` as je inner join `tabJournal Entry Account` as jea
on on
@ -512,16 +643,10 @@ def get_pdc_details(party_type, report_date):
where where
je.docstatus < 2 and je.posting_date > %s je.docstatus < 2 and je.posting_date > %s
and jea.party_type = %s and jea.party_type = %s
group by jea.party, jea.reference_name""".format(amount_field), (report_date, party_type), as_dict=1): """.format(amount_field), (report_date, party_type), as_dict=1)
if (pdc.invoice_no, pdc.party) in pdc_details:
key = (pdc.invoice_no, pdc.party) for pdc in pdc_via_je:
pdc_details[key]["pdc_amount"] += pdc.pdc_amount pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc)
if pdc.pdc_ref:
pdc_details[key]["pdc_ref"] += ", " + pdc.pdc_ref
if pdc.pdc_date:
pdc_details[key]["pdc_date"] = max(pdc_details[key]["pdc_date"], pdc.pdc_date)
else:
pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc)
return pdc_details return pdc_details

View File

@ -0,0 +1,84 @@
import frappe
import frappe.defaults
import unittest
from frappe.utils import today, getdate, add_days
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
class TestAccountsReceivable(unittest.TestCase):
def test_accounts_receivable(self):
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
filters = {
'company': '_Test Company 2',
'based_on_payment_terms': 1
}
name = make_sales_invoice()
report = execute(filters)
expected_data = [[100,30], [100,50], [100,20]]
self.assertEqual(expected_data[0], report[1][0][6:8])
self.assertEqual(expected_data[1], report[1][1][6:8])
self.assertEqual(expected_data[2], report[1][2][6:8])
make_payment(name)
report = execute(filters)
expected_data_after_payment = [[100,50], [100,20]]
self.assertEqual(expected_data_after_payment[0], report[1][0][6:8])
self.assertEqual(expected_data_after_payment[1], report[1][1][6:8])
make_credit_note(name)
report = execute(filters)
expected_data_after_credit_note = [[100,100,30,100,-30]]
self.assertEqual(expected_data_after_credit_note[0], report[1][0][6:11])
def make_sales_invoice():
frappe.set_user("Administrator")
si = create_sales_invoice(company="_Test Company 2",
customer = '_Test Customer 2',
currency = 'EUR',
warehouse = 'Finished Goods - _TC2',
debit_to = 'Debtors - _TC2',
income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2',
cost_center = '_Test Company 2 - _TC2',
do_not_save=1)
si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30))
si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50))
si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20))
si.submit()
return si.name
def make_payment(docname):
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30)
pe.paid_from = "Debtors - _TC2"
pe.insert()
pe.submit()
def make_credit_note(docname):
create_sales_invoice(company="_Test Company 2",
customer = '_Test Customer 2',
currency = 'EUR',
qty = -1,
warehouse = 'Finished Goods - _TC2',
debit_to = 'Debtors - _TC2',
income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2',
cost_center = '_Test Company 2 - _TC2',
is_return = 1,
return_against = docname)

View File

@ -86,20 +86,20 @@ def get_columns(filters):
_("Total Variance") + ":Float:80"] _("Total Variance") + ":Float:80"]
else: else:
return columns return columns
def get_cost_centers(filters): def get_cost_centers(filters):
cond = "and 1=1" cond = "and 1=1"
if filters.get("budget_against") == "Cost Center": if filters.get("budget_against") == "Cost Center":
cond = "order by lft" cond = "order by lft"
return frappe.db.sql_list("""select name from `tab{tab}` where company=%s return frappe.db.sql_list("""select name from `tab{tab}` where company=%s
{cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company")) {cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company"))
#Get cost center & target details #Get cost center & target details
def get_cost_center_target_details(filters): def get_cost_center_target_details(filters):
cond = "" cond = ""
if filters.get("cost_center"): if filters.get("cost_center"):
cond += " and b.cost_center='%s'" % frappe.db.escape(filters.get("cost_center")) cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center"))
return frappe.db.sql(""" return frappe.db.sql("""
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
@ -159,7 +159,7 @@ def get_cost_center_account_month_map(filters):
for ccd in cost_center_target_details: for ccd in cost_center_target_details:
actual_details = get_actual_details(ccd.budget_against, filters) actual_details = get_actual_details(ccd.budget_against, filters)
for month_id in range(1, 13): for month_id in range(1, 13):
month = datetime.date(2013, month_id, 1).strftime('%B') month = datetime.date(2013, month_id, 1).strftime('%B')
cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\ cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\
@ -172,7 +172,7 @@ def get_cost_center_account_month_map(filters):
if ccd.monthly_distribution else 100.0/12 if ccd.monthly_distribution else 100.0/12
tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100
for ad in actual_details.get(ccd.account, []): for ad in actual_details.get(ccd.account, []):
if ad.month_name == month: if ad.month_name == month:
tav_dict.actual += flt(ad.debit) - flt(ad.credit) tav_dict.actual += flt(ad.debit) - flt(ad.credit)

View File

@ -101,7 +101,7 @@ def get_income_expense_data(companies, fiscal_year, filters):
net_profit_loss = get_net_profit_loss(income, expense, companies, filters.company, company_currency, True) net_profit_loss = get_net_profit_loss(income, expense, companies, filters.company, company_currency, True)
return income, expense, net_profit_loss return income, expense, net_profit_loss
def get_cash_flow_data(fiscal_year, companies, filters): def get_cash_flow_data(fiscal_year, companies, filters):
cash_flow_accounts = get_cash_flow_accounts() cash_flow_accounts = get_cash_flow_accounts()
@ -123,7 +123,7 @@ def get_cash_flow_data(fiscal_year, companies, filters):
# add first net income in operations section # add first net income in operations section
if net_profit_loss: if net_profit_loss:
net_profit_loss.update({ net_profit_loss.update({
"indent": 1, "indent": 1,
"parent_account": cash_flow_accounts[0]['section_header'] "parent_account": cash_flow_accounts[0]['section_header']
}) })
data.append(net_profit_loss) data.append(net_profit_loss)
@ -274,7 +274,8 @@ def get_companies(filters):
return all_companies, companies return all_companies, companies
def get_subsidiary_companies(company): def get_subsidiary_companies(company):
lft, rgt = frappe.db.get_value('Company', company, ["lft", "rgt"]) lft, rgt = frappe.get_cached_value('Company',
company, ["lft", "rgt"])
return frappe.db.sql_list("""select name from `tabCompany` return frappe.db.sql_list("""select name from `tabCompany`
where lft >= {0} and rgt <= {1} order by lft, rgt""".format(lft, rgt)) where lft >= {0} and rgt <= {1} order by lft, rgt""".format(lft, rgt))
@ -327,7 +328,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
accounts_by_name, ignore_closing_entries=False): accounts_by_name, ignore_closing_entries=False):
"""Returns a dict like { "account": [gl entries], ... }""" """Returns a dict like { "account": [gl entries], ... }"""
company_lft, company_rgt = frappe.get_cached_value('Company', company_lft, company_rgt = frappe.get_cached_value('Company',
filters.get('company'), ["lft", "rgt"]) filters.get('company'), ["lft", "rgt"])
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters) additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
@ -387,10 +388,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
company_finance_book = erpnext.get_default_finance_book(filters.get("company")) company_finance_book = erpnext.get_default_finance_book(filters.get("company"))
if not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book): if not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book):
additional_conditions.append("ifnull(finance_book, '') in ('%s', '')" % additional_conditions.append("ifnull(finance_book, '') in (%s, '')" %
frappe.db.escape(company_finance_book)) frappe.db.escape(company_finance_book))
elif filters.get("finance_book"): elif filters.get("finance_book"):
additional_conditions.append("ifnull(finance_book, '') = '%s' " % additional_conditions.append("ifnull(finance_book, '') = %s " %
frappe.db.escape(filters.get("finance_book"))) frappe.db.escape(filters.get("finance_book")))
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""

View File

@ -343,8 +343,8 @@ def set_gl_entries_by_account(
accounts = frappe.db.sql_list("""select name from `tabAccount` accounts = frappe.db.sql_list("""select name from `tabAccount`
where lft >= %s and rgt <= %s""", (root_lft, root_rgt)) where lft >= %s and rgt <= %s""", (root_lft, root_rgt))
additional_conditions += " and account in ('{}')"\ additional_conditions += " and account in ({})"\
.format("', '".join([frappe.db.escape(d) for d in accounts])) .format(", ".join([frappe.db.escape(d) for d in accounts]))
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry` gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
where company=%(company)s where company=%(company)s
@ -392,10 +392,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
company_finance_book = erpnext.get_default_finance_book(filters.get("company")) company_finance_book = erpnext.get_default_finance_book(filters.get("company"))
if not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book): if not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book):
additional_conditions.append("ifnull(finance_book, '') in ('%s', '')" % additional_conditions.append("ifnull(finance_book, '') in (%s, '')" %
frappe.db.escape(company_finance_book)) frappe.db.escape(company_finance_book))
elif filters.get("finance_book"): elif filters.get("finance_book"):
additional_conditions.append("ifnull(finance_book, '') = '%s' " % additional_conditions.append("ifnull(finance_book, '') = %s " %
frappe.db.escape(filters.get("finance_book"))) frappe.db.escape(filters.get("finance_book")))
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""

View File

@ -99,7 +99,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
cond = [] cond = []
if date: if date:
cond.append("posting_date <= '%s'" % frappe.db.escape(cstr(date))) cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
else: else:
# get balance of all entries that exist # get balance of all entries that exist
date = nowdate() date = nowdate()
@ -127,7 +127,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
)""" % (cc.lft, cc.rgt)) )""" % (cc.lft, cc.rgt))
else: else:
cond.append("""gle.cost_center = "%s" """ % (frappe.db.escape(cost_center, percent=False), )) cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False), ))
if account: if account:
@ -158,14 +158,14 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
if acc.account_currency == frappe.get_cached_value('Company', acc.company, "default_currency"): if acc.account_currency == frappe.get_cached_value('Company', acc.company, "default_currency"):
in_account_currency = False in_account_currency = False
else: else:
cond.append("""gle.account = "%s" """ % (frappe.db.escape(account, percent=False), )) cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False), ))
if party_type and party: if party_type and party:
cond.append("""gle.party_type = "%s" and gle.party = "%s" """ % cond.append("""gle.party_type = %s and gle.party = %s """ %
(frappe.db.escape(party_type), frappe.db.escape(party, percent=False))) (frappe.db.escape(party_type), frappe.db.escape(party, percent=False)))
if company: if company:
cond.append("""gle.company = "%s" """ % (frappe.db.escape(company, percent=False))) cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account or (party_type and party): if account or (party_type and party):
if in_account_currency: if in_account_currency:
@ -183,7 +183,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
def get_count_on(account, fieldname, date): def get_count_on(account, fieldname, date):
cond = [] cond = []
if date: if date:
cond.append("posting_date <= '%s'" % frappe.db.escape(cstr(date))) cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
else: else:
# get balance of all entries that exist # get balance of all entries that exist
date = nowdate() date = nowdate()
@ -218,7 +218,7 @@ def get_count_on(account, fieldname, date):
and ac.lft >= %s and ac.rgt <= %s and ac.lft >= %s and ac.rgt <= %s
)""" % (acc.lft, acc.rgt)) )""" % (acc.lft, acc.rgt))
else: else:
cond.append("""gle.account = "%s" """ % (frappe.db.escape(account, percent=False), )) cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False), ))
entries = frappe.db.sql(""" entries = frappe.db.sql("""
SELECT name, posting_date, account, party_type, party,debit,credit, SELECT name, posting_date, account, party_type, party,debit,credit,

View File

@ -203,12 +203,11 @@ def get_children(doctype, parent=None, location=None, is_root=False):
from from
`tab{doctype}` comp `tab{doctype}` comp
where where
ifnull(parent_location, "")="{parent}" ifnull(parent_location, "")={parent}
""".format( """.format(
doctype=frappe.db.escape(doctype), doctype=doctype,
parent=frappe.db.escape(parent) parent=frappe.db.escape(parent)
), as_dict=1) ), as_dict=1)
@frappe.whitelist() @frappe.whitelist()
def add_node(): def add_node():

View File

@ -42,6 +42,7 @@ frappe.ui.form.on("Purchase Order", {
frm: frm, frm: frm,
child_docname: "items", child_docname: "items",
child_doctype: "Purchase Order Detail", child_doctype: "Purchase Order Detail",
cannot_add_row: false,
}) })
}); });
} }

View File

@ -182,7 +182,7 @@ class PurchaseOrder(BuyingController):
def check_modified_date(self): def check_modified_date(self):
mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
self.name) self.name)
date_diff = frappe.db.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.modified))) date_diff = frappe.db.sql("select '%s' - '%s' " % (mod_db[0][0], cstr(self.modified)))
if date_diff and date_diff[0][0]: if date_diff and date_diff[0][0]:
msgprint(_("{0} {1} has been modified. Please refresh.").format(self.doctype, self.name), msgprint(_("{0} {1} has been modified. Please refresh.").format(self.doctype, self.name),
@ -295,7 +295,10 @@ class PurchaseOrder(BuyingController):
for item in self.items: for item in self.items:
received_qty += item.received_qty received_qty += item.received_qty
total_qty += item.qty total_qty += item.qty
self.db_set("per_received", flt(received_qty/total_qty) * 100, update_modified=False) if total_qty:
self.db_set("per_received", flt(received_qty/total_qty) * 100, update_modified=False)
else:
self.db_set("per_received", 0, update_modified=False)
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor= 1.0): def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor= 1.0):
"""get last purchase rate for an item""" """get last purchase rate for an item"""

View File

@ -77,9 +77,20 @@ frappe.query_reports["Purchase Analytics"] = {
events: { events: {
onCheckRow: function(data) { onCheckRow: function(data) {
row_name = data[2].content; row_name = data[2].content;
row_values = data.slice(5).map(function (column) { length = data.length;
return column.content;
}) var tree_type = frappe.query_report.filters[0].value;
if(tree_type == "Supplier" || tree_type == "Item") {
row_values = data.slice(4,length-1).map(function (column) {
return column.content;
})
}
else {
row_values = data.slice(3,length-1).map(function (column) {
return column.content;
})
}
entry = { entry = {
'name':row_name, 'name':row_name,

View File

@ -6,6 +6,7 @@ import frappe, erpnext
import json import json
from frappe import _, throw from frappe import _, throw
from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.setup.utils import get_exchange_rate from erpnext.setup.utils import get_exchange_rate
from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
@ -1067,24 +1068,68 @@ def get_supplier_block_status(party_name):
} }
return info return info
def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_code):
"""
Returns a Sales Order Item child item containing the default values
"""
p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
child_item = frappe.new_doc('Sales Order Item', p_doctype, child_docname)
item = frappe.get_doc("Item", item_code)
child_item.item_code = item.item_code
child_item.item_name = item.item_name
child_item.description = item.description
child_item.reqd_by_date = p_doctype.delivery_date
child_item.uom = item.stock_uom
child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
return child_item
def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_code):
"""
Returns a Purchase Order Item child item containing the default values
"""
p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
child_item = frappe.new_doc('Purchase Order Item', p_doctype, child_docname)
item = frappe.get_doc("Item", item_code)
child_item.item_code = item.item_code
child_item.item_name = item.item_name
child_item.description = item.description
child_item.schedule_date = p_doctype.schedule_date
child_item.uom = item.stock_uom
child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
return child_item
@frappe.whitelist() @frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name): def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
data = json.loads(trans_items) data = json.loads(trans_items)
for d in data:
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
child_item.qty = flt(d.get("qty"))
for d in data:
new_child_flag = False
if not d.get("docname"):
new_child_flag = True
if parent_doctype == "Sales Order":
child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code"))
if parent_doctype == "Purchase Order":
child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code"))
else:
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
child_item.qty = flt(d.get("qty"))
if child_item.billed_amt > (flt(d.get("rate")) * flt(d.get("qty"))): if child_item.billed_amt > (flt(d.get("rate")) * flt(d.get("qty"))):
frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.") frappe.throw(_("Row #{0}: Cannot set Rate if amount is greater than billed amount for Item {1}.")
.format(child_item.idx, child_item.item_code)) .format(child_item.idx, child_item.item_code))
else: else:
child_item.rate = flt(d.get("rate")) child_item.rate = flt(d.get("rate"))
child_item.flags.ignore_validate_update_after_submit = True child_item.flags.ignore_validate_update_after_submit = True
child_item.save() if new_child_flag:
child_item.insert()
else:
child_item.save()
p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name) p_doctype = frappe.get_doc(parent_doctype, parent_doctype_name)
p_doctype.flags.ignore_validate_update_after_submit = True p_doctype.flags.ignore_validate_update_after_submit = True
p_doctype.set_qty_as_per_stock_uom() p_doctype.set_qty_as_per_stock_uom()
p_doctype.calculate_taxes_and_totals() p_doctype.calculate_taxes_and_totals()

View File

@ -759,7 +759,7 @@ def validate_item_type(doc, fieldname, message):
if not items: if not items:
return return
item_list = ", ".join(["'%s'" % frappe.db.escape(d) for d in items]) item_list = ", ".join(["%s" % frappe.db.escape(d) for d in items])
invalid_items = [d[0] for d in frappe.db.sql(""" invalid_items = [d[0] for d in frappe.db.sql("""
select item_code from tabItem where name in ({0}) and {1}=0 select item_code from tabItem where name in ({0}) and {1}=0

View File

@ -119,7 +119,7 @@ def get_attribute_values(item):
return frappe.flags.attribute_values, frappe.flags.numeric_values return frappe.flags.attribute_values, frappe.flags.numeric_values
def find_variant(template, args, variant_item_code=None): def find_variant(template, args, variant_item_code=None):
conditions = ["""(iv_attribute.attribute="{0}" and iv_attribute.attribute_value="{1}")"""\ conditions = ["""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})"""\
.format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()] .format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
conditions = " or ".join(conditions) conditions = " or ".join(conditions)

View File

@ -208,9 +208,8 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
limit %(start)s, %(page_len)s """.format( limit %(start)s, %(page_len)s """.format(
fcond=get_filters_cond(doctype, filters, conditions), fcond=get_filters_cond(doctype, filters, conditions),
mcond=get_match_cond(doctype), mcond=get_match_cond(doctype),
key=frappe.db.escape(searchfield)), key=searchfield), {
{ 'txt': '%' + txt + '%',
'txt': "%%%s%%" % frappe.db.escape(txt),
'_txt': txt.replace("%", ""), '_txt': txt.replace("%", ""),
'start': start or 0, 'start': start or 0,
'page_len': page_len or 20 'page_len': page_len or 20
@ -219,7 +218,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
def get_project_name(doctype, txt, searchfield, start, page_len, filters): def get_project_name(doctype, txt, searchfield, start, page_len, filters):
cond = '' cond = ''
if filters.get('customer'): if filters.get('customer'):
cond = """(`tabProject`.customer = '%s' or cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
return frappe.db.sql("""select `tabProject`.name from `tabProject` return frappe.db.sql("""select `tabProject`.name from `tabProject`
@ -353,7 +352,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
{condition} {match_condition} {condition} {match_condition}
order by idx desc, name""" order by idx desc, name"""
.format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), { .format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), {
'txt': "%%%s%%" % frappe.db.escape(txt), 'txt': '%' + txt + '%',
'company': filters.get("company", "") 'company': filters.get("company", "")
}) })
@ -375,10 +374,10 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
and tabAccount.docstatus!=2 and tabAccount.docstatus!=2
and tabAccount.{key} LIKE %(txt)s and tabAccount.{key} LIKE %(txt)s
{condition} {match_condition}""" {condition} {match_condition}"""
.format(condition=condition, key=frappe.db.escape(searchfield), .format(condition=condition, key=searchfield,
match_condition=get_match_cond(doctype)), { match_condition=get_match_cond(doctype)), {
'company': filters.get("company", ""), 'company': filters.get("company", ""),
'txt': "%%%s%%" % frappe.db.escape(txt) 'txt': '%' + txt + '%'
}) })
@ -398,7 +397,7 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty
from `tabWarehouse` from `tabWarehouse`
where where
`tabWarehouse`.`{key}` like '{txt}' `tabWarehouse`.`{key}` like {txt}
{fcond} {mcond} {fcond} {mcond}
order by order by
`tabWarehouse`.name desc `tabWarehouse`.name desc
@ -406,7 +405,7 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
{start}, {page_len} {start}, {page_len}
""".format( """.format(
sub_query=sub_query, sub_query=sub_query,
key=frappe.db.escape(searchfield), key=searchfield,
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions), fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
mcond=get_match_cond(doctype), mcond=get_match_cond(doctype),
start=start, start=start,
@ -430,9 +429,9 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
query = """select batch_id from `tabBatch` query = """select batch_id from `tabBatch`
where disabled = 0 where disabled = 0
and (expiry_date >= CURDATE() or expiry_date IS NULL) and (expiry_date >= CURDATE() or expiry_date IS NULL)
and name like '{txt}'""".format(txt = frappe.db.escape('%{0}%'.format(txt))) and name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
if filters and filters.get('item'): if filters and filters.get('item'):
query += " and item = '{item}'".format(item = frappe.db.escape(filters.get('item'))) query += " and item = {item}".format(item = frappe.db.escape(filters.get('item')))
return frappe.db.sql(query, filters) return frappe.db.sql(query, filters)

View File

@ -244,6 +244,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1* source_doc.qty target_doc.qty = -1* source_doc.qty
default_return_warehouse = frappe.db.get_single_value("Stock Settings", "default_return_warehouse")
if doctype == "Purchase Receipt": if doctype == "Purchase Receipt":
target_doc.received_qty = -1* source_doc.received_qty target_doc.received_qty = -1* source_doc.received_qty
target_doc.rejected_qty = -1* source_doc.rejected_qty target_doc.rejected_qty = -1* source_doc.rejected_qty
@ -268,6 +269,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.so_detail = source_doc.so_detail target_doc.so_detail = source_doc.so_detail
target_doc.si_detail = source_doc.si_detail target_doc.si_detail = source_doc.si_detail
target_doc.expense_account = source_doc.expense_account target_doc.expense_account = source_doc.expense_account
target_doc.warehouse = default_return_warehouse if default_return_warehouse else source_doc.warehouse
elif doctype == "Sales Invoice": elif doctype == "Sales Invoice":
target_doc.sales_order = source_doc.sales_order target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note target_doc.delivery_note = source_doc.delivery_note

View File

@ -308,7 +308,7 @@ class StatusUpdater(Document):
def _update_modified(self, args, update_modified): def _update_modified(self, args, update_modified):
args['update_modified'] = '' args['update_modified'] = ''
if update_modified: if update_modified:
args['update_modified'] = ', modified = now(), modified_by = "{0}"'\ args['update_modified'] = ', modified = now(), modified_by = {0}'\
.format(frappe.db.escape(frappe.session.user)) .format(frappe.db.escape(frappe.session.user))
def update_billing_status_for_zero_amount_refdoc(self, ref_dt): def update_billing_status_for_zero_amount_refdoc(self, ref_dt):

View File

@ -272,6 +272,8 @@ class calculate_taxes_and_totals(object):
elif tax.charge_type == "On Previous Row Total": elif tax.charge_type == "On Previous Row Total":
current_tax_amount = (tax_rate / 100.0) * \ current_tax_amount = (tax_rate / 100.0) * \
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
elif tax.charge_type == "On Item Quantity":
current_tax_amount = tax_rate * item.stock_qty
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)

View File

@ -0,0 +1,94 @@
from __future__ import unicode_literals, print_function
import unittest
import frappe
from uuid import uuid4 as _uuid4
def uuid4():
return str(_uuid4())
class TestTaxes(unittest.TestCase):
def setUp(self):
self.company = frappe.get_doc({
'doctype': 'Company',
'company_name': uuid4(),
'abbr': ''.join(s[0] for s in uuid4().split('-')),
'default_currency': 'USD',
'country': 'United States',
}).insert()
self.account = frappe.get_doc({
'doctype': 'Account',
'account_name': uuid4(),
'account_type': 'Tax',
'company': self.company.name,
'parent_account': 'Duties and Taxes - {self.company.abbr}'.format(self=self)
}).insert()
self.item_group = frappe.get_doc({
'doctype': 'Item Group',
'item_group_name': uuid4(),
'parent_item_group': 'All Item Groups',
}).insert()
self.item = frappe.get_doc({
'doctype': 'Item',
'item_code': uuid4(),
'item_group': self.item_group.name,
'is_stock_item': 0,
'taxes': [
{
'tax_type': self.account.name,
'tax_rate': 2,
}
],
}).insert()
self.customer = frappe.get_doc({
'doctype': 'Customer',
'customer_name': uuid4(),
'customer_group': 'All Customer Groups',
}).insert()
self.supplier = frappe.get_doc({
'doctype': 'Supplier',
'supplier_name': uuid4(),
'supplier_group': 'All Supplier Groups',
}).insert()
def test_taxes(self):
self.created_docs = []
for dt in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice',
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']:
doc = frappe.get_doc({
'doctype': dt,
'company': self.company.name,
'supplier': self.supplier.name,
'schedule_date': frappe.utils.nowdate(),
'delivery_date': frappe.utils.nowdate(),
'customer': self.customer.name,
'buying_price_list' if dt.startswith('Purchase') else 'selling_price_list'
: 'Standard Buying' if dt.startswith('Purchase') else 'Standard Selling',
'items': [
{
'item_code': self.item.name,
'qty': 300,
'rate': 100,
}
],
'taxes': [
{
'charge_type': 'On Item Quantity',
'account_head': self.account.name,
'description': 'N/A',
'rate': 0,
},
],
})
doc.run_method('set_missing_values')
doc.run_method('calculate_taxes_and_totals')
doc.insert()
self.assertEqual(doc.taxes[0].tax_amount, 600)
self.created_docs.append(doc)
def tearDown(self):
for doc in self.created_docs:
doc.delete()
self.item.delete()
self.item_group.delete()
self.account.delete()
self.company.delete()

View File

@ -117,14 +117,14 @@ def generate_fee(fee_schedule):
def get_students(student_group, academic_year, academic_term=None, student_category=None): def get_students(student_group, academic_year, academic_term=None, student_category=None):
conditions = "" conditions = ""
if student_category: if student_category:
conditions = " and pe.student_category='{}'".format(frappe.db.escape(student_category)) conditions = " and pe.student_category={}".format(frappe.db.escape(student_category))
if academic_term: if academic_term:
conditions = " and pe.academic_term='{}'".format(frappe.db.escape(academic_term)) conditions = " and pe.academic_term={}".format(frappe.db.escape(academic_term))
students = frappe.db.sql(""" students = frappe.db.sql("""
select pe.student, pe.student_name, pe.program, pe.student_batch_name select pe.student, pe.student_name, pe.program, pe.student_batch_name
from `tabStudent Group Student` sgs, `tabProgram Enrollment` pe from `tabStudent Group Student` sgs, `tabProgram Enrollment` pe
where where
pe.student = sgs.student and pe.academic_year = %s pe.student = sgs.student and pe.academic_year = %s
and sgs.parent = %s and sgs.active = 1 and sgs.parent = %s and sgs.active = 1
{conditions} {conditions}

View File

@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext" source_link = "https://github.com/frappe/erpnext"
develop_version = '12.x.x-develop' develop_version = '12.x.x-develop'
staging_version = '11.0.3-beta.30' staging_version = '11.0.3-beta.31'
error_report_email = "support@erpnext.com" error_report_email = "support@erpnext.com"
@ -133,6 +133,13 @@ website_route_rules = [
{"from_route": "/admissions", "to_route": "Student Admission"}, {"from_route": "/admissions", "to_route": "Student Admission"},
{"from_route": "/boms", "to_route": "BOM"}, {"from_route": "/boms", "to_route": "BOM"},
{"from_route": "/timesheets", "to_route": "Timesheet"}, {"from_route": "/timesheets", "to_route": "Timesheet"},
{"from_route": "/material-requests", "to_route": "Material Request"},
{"from_route": "/material-requests/<path:name>", "to_route": "material_request_info",
"defaults": {
"doctype": "Material Request",
"parents": [{"label": _("Material Request"), "route": "material-requests"}]
}
},
] ]
standard_portal_menu_items = [ standard_portal_menu_items = [
@ -155,6 +162,7 @@ standard_portal_menu_items = [
{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"}, {"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission"}, {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission"},
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application"}, {"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application"},
{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
] ]
default_roles = [ default_roles = [
@ -168,6 +176,7 @@ has_website_permission = {
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", "Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission", "Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Supplier Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission", "Supplier Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Material Request": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Issue": "erpnext.support.doctype.issue.issue.has_website_permission", "Issue": "erpnext.support.doctype.issue.issue.has_website_permission",
"Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission", "Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission",

View File

@ -59,15 +59,15 @@ class HotelRoomReservation(Document):
if not d.item: if not d.item:
continue continue
day_rate = frappe.db.sql(""" day_rate = frappe.db.sql("""
select select
item.rate item.rate
from from
`tabHotel Room Pricing Item` item, `tabHotel Room Pricing Item` item,
`tabHotel Room Pricing` pricing `tabHotel Room Pricing` pricing
where where
item.parent = pricing.name item.parent = pricing.name
and item.item = %s and item.item = %s
and %s between pricing.from_date and %s between pricing.from_date
and pricing.to_date""", (d.item, day)) and pricing.to_date""", (d.item, day))
if day_rate: if day_rate:
@ -90,7 +90,7 @@ def get_room_rate(hotel_room_reservation):
def get_rooms_booked(room_type, day, exclude_reservation=None): def get_rooms_booked(room_type, day, exclude_reservation=None):
exclude_condition = '' exclude_condition = ''
if exclude_reservation: if exclude_reservation:
exclude_condition = 'and reservation.name != "{0}"'.format(frappe.db.escape(exclude_reservation)) exclude_condition = 'and reservation.name != {0}'.format(frappe.db.escape(exclude_reservation))
return frappe.db.sql(""" return frappe.db.sql("""
select sum(item.qty) select sum(item.qty)
@ -105,5 +105,5 @@ def get_rooms_booked(room_type, day, exclude_reservation=None):
and reservation.docstatus = 1 and reservation.docstatus = 1
{exclude_condition} {exclude_condition}
and %s between reservation.from_date and %s between reservation.from_date
and reservation.to_date""".format(exclude_condition=exclude_condition), and reservation.to_date""".format(exclude_condition=exclude_condition),
(room_type, day))[0][0] or 0 (room_type, day))[0][0] or 0

View File

@ -13,7 +13,7 @@ from frappe.utils import cstr
class Attendance(Document): class Attendance(Document):
def validate_duplicate_record(self): def validate_duplicate_record(self):
res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and attendance_date = %s res = frappe.db.sql("""select name from `tabAttendance` where employee = %s and attendance_date = %s
and name != %s and docstatus = 1""", and name != %s and docstatus != 2""",
(self.employee, self.attendance_date, self.name)) (self.employee, self.attendance_date, self.name))
if res: if res:
frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee)) frappe.throw(_("Attendance for employee {0} is already marked").format(self.employee))
@ -89,4 +89,4 @@ def add_attendance(events, start, end, conditions=None):
"docstatus": d.docstatus "docstatus": d.docstatus
} }
if e not in events: if e not in events:
events.append(e) events.append(e)

View File

@ -2,7 +2,28 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Employee Grade', { frappe.ui.form.on('Employee Grade', {
refresh: function(frm) { refresh: function (frm) {
},
setup: function (frm) {
frm.set_query("default_salary_structure", function () {
return {
"filters": {
"docstatus": 1,
"is_active": "Yes"
}
};
});
frm.set_query("default_leave_policy", function () {
return {
"filters": {
"docstatus": 1
}
};
});
}
}
}); });

View File

@ -15,150 +15,153 @@
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_in_quick_entry": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "default_leave_policy", "columns": 0,
"fieldtype": "Link", "fieldname": "default_leave_policy",
"hidden": 0, "fieldtype": "Link",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Default Leave Policy", "in_standard_filter": 0,
"length": 0, "label": "Default Leave Policy",
"no_copy": 0, "length": 0,
"options": "Leave Policy", "no_copy": 0,
"permlevel": 0, "options": "Leave Policy",
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"translatable": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_in_quick_entry": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "default_salary_structure", "columns": 0,
"fieldtype": "Link", "fieldname": "default_salary_structure",
"hidden": 0, "fieldtype": "Link",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Default Salary Structure", "in_standard_filter": 0,
"length": 0, "label": "Default Salary Structure",
"no_copy": 0, "length": 0,
"options": "Salary Structure", "no_copy": 0,
"permlevel": 0, "options": "Salary Structure",
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"translatable": 0, "set_only_once": 0,
"translatable": 0,
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-04-13 16:14:24.174138", "modified": "2018-09-18 17:17:45.617624",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Grade", "name": "Employee Grade",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1,
"track_seen": 0 "track_seen": 0,
"track_views": 0
} }

View File

@ -18,7 +18,7 @@ class ExpenseApproverIdentityError(frappe.ValidationError): pass
class ExpenseClaim(AccountsController): class ExpenseClaim(AccountsController):
def onload(self): def onload(self):
self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings', self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
'make_payment_via_journal_entry') 'make_payment_via_journal_entry')
def validate(self): def validate(self):
@ -103,7 +103,7 @@ class ExpenseClaim(AccountsController):
self.validate_account_details() self.validate_account_details()
payable_amount = flt(self.total_sanctioned_amount) - flt(self.total_advance_amount) payable_amount = flt(self.total_sanctioned_amount) - flt(self.total_advance_amount)
# payable entry # payable entry
if payable_amount: if payable_amount:
gl_entry.append( gl_entry.append(
@ -233,7 +233,7 @@ class ExpenseClaim(AccountsController):
expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"] expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
def update_reimbursed_amount(doc): def update_reimbursed_amount(doc):
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt
from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s
and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt
@ -288,7 +288,7 @@ def get_expense_claim_account(expense_claim_type, company):
if not account: if not account:
frappe.throw(_("Please set default account in Expense Claim Type {0}") frappe.throw(_("Please set default account in Expense Claim Type {0}")
.format(expense_claim_type)) .format(expense_claim_type))
return { return {
"account": account "account": account
} }
@ -296,14 +296,14 @@ def get_expense_claim_account(expense_claim_type, company):
@frappe.whitelist() @frappe.whitelist()
def get_advances(employee, advance_id=None): def get_advances(employee, advance_id=None):
if not advance_id: if not advance_id:
condition = 'docstatus=1 and employee="{0}" and paid_amount > 0 and paid_amount > claimed_amount'.format(frappe.db.escape(employee)) condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount'.format(frappe.db.escape(employee))
else: else:
condition = 'name="{0}"'.format(frappe.db.escape(advance_id)) condition = 'name={0}'.format(frappe.db.escape(advance_id))
return frappe.db.sql(""" return frappe.db.sql("""
select select
name, posting_date, paid_amount, claimed_amount, advance_account name, posting_date, paid_amount, claimed_amount, advance_account
from from
`tabEmployee Advance` `tabEmployee Advance`
where {0} where {0}
""".format(condition), as_dict=1) """.format(condition), as_dict=1)

View File

@ -20,6 +20,10 @@ class PayrollEntry(Document):
if self.validate_employee_attendance(): if self.validate_employee_attendance():
frappe.throw(_("Cannot Submit, Employees left to mark attendance")) frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
def on_cancel(self):
frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
where payroll_entry=%s """, (self.name)))
def get_emp_list(self): def get_emp_list(self):
""" """
Returns list of active employees based on selected criteria Returns list of active employees based on selected criteria

View File

@ -162,28 +162,30 @@ class BOM(WebsiteGenerator):
if arg.get('scrap_items'): if arg.get('scrap_items'):
rate = self.get_valuation_rate(arg) rate = self.get_valuation_rate(arg)
elif arg: elif arg:
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom: #Customer Provided parts will have zero rate
rate = self.get_bom_unitcost(arg['bom_no']) if not frappe.db.get_value('Item', arg["item_code"], 'is_customer_provided_item'):
else: if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
if self.rm_cost_as_per == 'Valuation Rate': rate = self.get_bom_unitcost(arg['bom_no'])
rate = self.get_valuation_rate(arg) else:
elif self.rm_cost_as_per == 'Last Purchase Rate': if self.rm_cost_as_per == 'Valuation Rate':
rate = arg.get('last_purchase_rate') \ rate = self.get_valuation_rate(arg)
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate") elif self.rm_cost_as_per == 'Last Purchase Rate':
elif self.rm_cost_as_per == "Price List": rate = arg.get('last_purchase_rate') \
if not self.buying_price_list: or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")
frappe.throw(_("Please select Price List")) elif self.rm_cost_as_per == "Price List":
rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list, if not self.buying_price_list:
"item_code": arg["item_code"]}, "price_list_rate") or 0.0 frappe.throw(_("Please select Price List"))
rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list,
"item_code": arg["item_code"]}, "price_list_rate") or 0.0
price_list_currency = frappe.db.get_value("Price List", price_list_currency = frappe.db.get_value("Price List",
self.buying_price_list, "currency") self.buying_price_list, "currency")
if price_list_currency != self.company_currency(): if price_list_currency != self.company_currency():
rate = flt(rate * self.conversion_rate) rate = flt(rate * self.conversion_rate)
if not rate: if not rate:
frappe.msgprint(_("{0} not found for Item {1}") frappe.msgprint(_("{0} not found for Item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True) .format(self.rm_cost_as_per, arg["item_code"]), alert=True)
return flt(rate) return flt(rate)

View File

@ -131,4 +131,4 @@ class TestBOM(unittest.TestCase):
self.assertEqual(bom.base_total_cost, 33000) self.assertEqual(bom.base_total_cost, 33000)
def get_default_bom(item_code="_Test FG Item 2"): def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@ -104,7 +104,7 @@ class ProductionPlan(Document):
item_condition = "" item_condition = ""
if self.item_code: if self.item_code:
item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.item_code)) item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
items = frappe.db.sql("""select distinct parent, item_code, warehouse, items = frappe.db.sql("""select distinct parent, item_code, warehouse,
(qty - work_order_qty) * conversion_factor as pending_qty, name (qty - work_order_qty) * conversion_factor as pending_qty, name
@ -115,7 +115,7 @@ class ProductionPlan(Document):
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
if self.item_code: if self.item_code:
item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.item_code)) item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.item_code))
packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse, packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty) (((so_item.qty - so_item.work_order_qty) * pi.qty) / so_item.qty)
@ -139,7 +139,7 @@ class ProductionPlan(Document):
item_condition = "" item_condition = ""
if self.item_code: if self.item_code:
item_condition = " and mr_item.item_code ='{0}'".format(frappe.db.escape(self.item_code)) item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, items = frappe.db.sql("""select distinct parent, name, item_code, warehouse,
(qty - ordered_qty) as pending_qty (qty - ordered_qty) as pending_qty
@ -325,8 +325,8 @@ class ProductionPlan(Document):
for item in self.mr_items: for item in self.mr_items:
item_doc = frappe.get_cached_doc('Item', item.item_code) item_doc = frappe.get_cached_doc('Item', item.item_code)
# key for Sales Order:Material Request Type # key for Sales Order:Material Request Type:Customer
key = '{}:{}'.format(item.sales_order, item_doc.default_material_request_type) key = '{}:{}:{}'.format(item.sales_order, item_doc.default_material_request_type,item_doc.customer or '')
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days)) schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
if not key in material_request_map: if not key in material_request_map:
@ -338,7 +338,8 @@ class ProductionPlan(Document):
"status": "Draft", "status": "Draft",
"company": self.company, "company": self.company,
"requested_by": frappe.session.user, "requested_by": frappe.session.user,
'material_request_type': item_doc.default_material_request_type 'material_request_type': item_doc.default_material_request_type,
'customer': item_doc.customer or ''
}) })
material_request_list.append(material_request) material_request_list.append(material_request)
else: else:
@ -499,7 +500,7 @@ def get_bin_details(row):
conditions = "" conditions = ""
warehouse = row.source_warehouse or row.default_warehouse or row.warehouse warehouse = row.source_warehouse or row.default_warehouse or row.warehouse
if warehouse: if warehouse:
conditions = " and warehouse='{0}'".format(frappe.db.escape(warehouse)) conditions = " and warehouse={0}".format(frappe.db.escape(warehouse))
item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty from `tabBin` ifnull(sum(actual_qty),0) as actual_qty from `tabBin`

View File

@ -143,12 +143,27 @@ class TestProductionPlan(unittest.TestCase):
self.assertEqual(sales_orders, []) self.assertEqual(sales_orders, [])
def test_pp_to_mr_customer_provided(self):
#Material Request from Production Plan for Customer Provided
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
create_item('Production Item CUST')
for item, raw_materials in {'Production Item CUST': ['Raw Material Item 1', 'CUST-0987']}.items():
if not frappe.db.get_value('BOM', {'item': item}):
make_bom(item = item, raw_materials = raw_materials)
production_plan = create_production_plan(item_code = 'Production Item CUST')
production_plan.make_material_request()
material_request = frappe.get_value('Material Request Item', {'production_plan': production_plan.name}, 'parent')
mr = frappe.get_doc('Material Request', material_request)
self.assertTrue(mr.material_request_type, 'Customer Provided')
self.assertTrue(mr.customer, '_Test Customer')
def create_production_plan(**args): def create_production_plan(**args):
args = frappe._dict(args) args = frappe._dict(args)
pln = frappe.get_doc({ pln = frappe.get_doc({
'doctype': 'Production Plan', 'doctype': 'Production Plan',
'company': args.company or '_Test Company', 'company': args.company or '_Test Company',
'customer': args.customer or '_Test Customer',
'posting_date': nowdate(), 'posting_date': nowdate(),
'include_non_stock_items': args.include_non_stock_items or 1, 'include_non_stock_items': args.include_non_stock_items or 1,
'include_subcontracted_items': args.include_subcontracted_items or 1, 'include_subcontracted_items': args.include_subcontracted_items or 1,

View File

@ -127,7 +127,7 @@ class ProductionPlanningTool(Document):
item_condition = "" item_condition = ""
if self.fg_item: if self.fg_item:
item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.fg_item)) item_condition = ' and so_item.item_code = {0}'.format(frappe.db.escape(self.fg_item))
items = frappe.db.sql("""select distinct parent, item_code, warehouse, items = frappe.db.sql("""select distinct parent, item_code, warehouse,
(qty - delivered_qty)*conversion_factor as pending_qty (qty - delivered_qty)*conversion_factor as pending_qty
@ -138,7 +138,7 @@ class ProductionPlanningTool(Document):
(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
if self.fg_item: if self.fg_item:
item_condition = ' and pi.item_code = "{0}"'.format(frappe.db.escape(self.fg_item)) item_condition = ' and pi.item_code = {0}'.format(frappe.db.escape(self.fg_item))
packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse, packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse,
(((so_item.qty - so_item.delivered_qty) * pi.qty) / so_item.qty) (((so_item.qty - so_item.delivered_qty) * pi.qty) / so_item.qty)
@ -161,7 +161,7 @@ class ProductionPlanningTool(Document):
item_condition = "" item_condition = ""
if self.fg_item: if self.fg_item:
item_condition = ' and mr_item.item_code = "' + frappe.db.escape(self.fg_item, percent=False) + '"' item_condition = ' and mr_item.item_code =' + frappe.db.escape(self.fg_item, percent=False)
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, items = frappe.db.sql("""select distinct parent, name, item_code, warehouse,
(qty - ordered_qty) as pending_qty (qty - ordered_qty) as pending_qty
@ -487,7 +487,7 @@ class ProductionPlanningTool(Document):
def get_item_projected_qty(self,item): def get_item_projected_qty(self,item):
conditions = "" conditions = ""
if self.purchase_request_for_warehouse: if self.purchase_request_for_warehouse:
conditions = " and warehouse='{0}'".format(frappe.db.escape(self.purchase_request_for_warehouse)) conditions = " and warehouse={0}".format(frappe.db.escape(self.purchase_request_for_warehouse))
item_projected_qty = frappe.db.sql(""" item_projected_qty = frappe.db.sql("""
select ifnull(sum(projected_qty),0) as qty select ifnull(sum(projected_qty),0) as qty

View File

@ -62,7 +62,7 @@ def get_bom_stock(filters):
where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft, where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft,
warehouse_details.rgt) warehouse_details.rgt)
else: else:
conditions += " and ledger.warehouse = '%s'" % frappe.db.escape(filters.get("warehouse")) conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
else: else:
conditions += "" conditions += ""

View File

@ -49,7 +49,7 @@ def get_bom_stock(filters):
where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft, where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" % (warehouse_details.lft,
warehouse_details.rgt) warehouse_details.rgt)
else: else:
conditions += " and ledger.warehouse = '%s'" % frappe.db.escape(filters.get("warehouse")) conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse"))
else: else:
conditions += "" conditions += ""

View File

@ -15,8 +15,8 @@ def execute():
value = frappe.db.escape(frappe.as_unicode(customer.get("customer_group"))) value = frappe.db.escape(frappe.as_unicode(customer.get("customer_group")))
when_then.append(''' when_then.append('''
WHEN `%s` = "%s" and %s != "%s" WHEN `%s` = %s and %s != %s
THEN "%s" THEN %s
'''%(d["master_fieldname"], frappe.db.escape(frappe.as_unicode(customer.name)), '''%(d["master_fieldname"], frappe.db.escape(frappe.as_unicode(customer.name)),
d["linked_to_fieldname"], value, value)) d["linked_to_fieldname"], value, value))

View File

@ -2,7 +2,7 @@ from __future__ import print_function, unicode_literals
import frappe import frappe
import os import os
from frappe.utils import get_files_path from frappe.utils import get_files_path
from frappe.utils.file_manager import get_content_hash from frappe.core.doctype.file.file import get_content_hash
def execute(): def execute():
files_path = get_files_path() files_path = get_files_path()

View File

@ -22,7 +22,7 @@ def execute():
condition = "" condition = ""
company = erpnext.get_default_company() company = erpnext.get_default_company()
if company: if company:
condition = " and name='{0}'".format(frappe.db.escape(company)) condition = " and name={0}".format(frappe.db.escape(company))
domains = frappe.db.sql_list("select distinct domain from `tabCompany` where domain != 'Other' {0}".format(condition)) domains = frappe.db.sql_list("select distinct domain from `tabCompany` where domain != 'Other' {0}".format(condition))

View File

@ -10,9 +10,9 @@ def execute():
frappe.reload_doc("stock", "doctype", "serial_no") frappe.reload_doc("stock", "doctype", "serial_no")
frappe.db.sql(""" update `tabSales Invoice Item` sii inner join frappe.db.sql(""" update `tabSales Invoice Item` sii inner join
`tabDelivery Note Item` dni on sii.dn_detail=dni.name and sii.qty=dni.qty `tabDelivery Note Item` dni on sii.dn_detail=dni.name and sii.qty=dni.qty
set sii.serial_no=dni.serial_no where sii.parent IN (select si.name set sii.serial_no=dni.serial_no where sii.parent IN (select si.name
from `tabSales Invoice` si where si.update_stock=0 and si.docstatus=1)""") from `tabSales Invoice` si where si.update_stock=0 and si.docstatus=1)""")
items = frappe.db.sql(""" select sii.parent, sii.serial_no from `tabSales Invoice Item` sii items = frappe.db.sql(""" select sii.parent, sii.serial_no from `tabSales Invoice Item` sii
@ -26,13 +26,13 @@ def execute():
if not sales_invoice or not serial_nos: if not sales_invoice or not serial_nos:
continue continue
serial_nos = ["'%s'"%frappe.db.escape(no) for no in serial_nos.split("\n")] serial_nos = ["{}".format(frappe.db.escape(no)) for no in serial_nos.split("\n")]
frappe.db.sql(""" frappe.db.sql("""
UPDATE UPDATE
`tabSerial No` `tabSerial No`
SET SET
sales_invoice='{sales_invoice}' sales_invoice={sales_invoice}
WHERE WHERE
name in ({serial_nos}) name in ({serial_nos})
""".format( """.format(

View File

@ -35,7 +35,7 @@ def execute():
else: else:
template = frappe.get_doc("Payment Terms Template", pyt_template_name) template = frappe.get_doc("Payment Terms Template", pyt_template_name)
payment_terms.append('WHEN `name`="%s" THEN "%s"' % (frappe.db.escape(party_name), template.template_name)) payment_terms.append('WHEN `name`={0} THEN {1}'.format(frappe.db.escape(party_name), template.template_name))
records.append(frappe.db.escape(party_name)) records.append(frappe.db.escape(party_name))
begin_query_str = "UPDATE `tab{0}` SET `payment_terms` = CASE ".format(doctype) begin_query_str = "UPDATE `tab{0}` SET `payment_terms` = CASE ".format(doctype)

View File

@ -168,12 +168,16 @@ def check_if_child_exists(name):
def get_project(doctype, txt, searchfield, start, page_len, filters): def get_project(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
return frappe.db.sql(""" select name from `tabProject` return frappe.db.sql(""" select name from `tabProject`
where %(key)s like "%(txt)s" where %(key)s like %(txt)s
%(mcond)s %(mcond)s
order by name order by name
limit %(start)s, %(page_len)s """ % {'key': searchfield, limit %(start)s, %(page_len)s""" % {
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype), 'key': searchfield,
'start': start, 'page_len': page_len}) 'txt': frappe.db.escape('%' + txt + '%'),
'mcond':get_match_cond(doctype),
'start': start,
'page_len': page_len
})
@frappe.whitelist() @frappe.whitelist()

View File

@ -235,7 +235,7 @@ def get_timesheet(doctype, txt, searchfield, start, page_len, filters):
and tsd.parent LIKE %(txt)s {condition} and tsd.parent LIKE %(txt)s {condition}
order by tsd.parent limit %(start)s, %(page_len)s""" order by tsd.parent limit %(start)s, %(page_len)s"""
.format(condition=condition), { .format(condition=condition), {
"txt": "%%%s%%" % frappe.db.escape(txt), 'txt': '%' + txt + '%',
"start": start, "page_len": page_len, 'project': filters.get("project") "start": start, "page_len": page_len, 'project': filters.get("project")
}) })

View File

@ -23,6 +23,6 @@ def query_task(doctype, txt, searchfield, start, page_len, filters):
`%s`, `%s`,
subject subject
limit %s, %s""" % limit %s, %s""" %
(frappe.db.escape(searchfield), "%s", "%s", match_conditions, "%s", (searchfield, "%s", "%s", match_conditions, "%s",
frappe.db.escape(searchfield), "%s", frappe.db.escape(searchfield), "%s", "%s"), searchfield, "%s", searchfield, "%s", "%s"),
(search_string, search_string, order_by_string, order_by_string, start, page_len)) (search_string, search_string, order_by_string, order_by_string, start, page_len))

View File

@ -68,12 +68,19 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
} }
this.frm.set_query("item_code", "items", function() { this.frm.set_query("item_code", "items", function() {
if(me.frm.doc.is_subcontracted == "Yes") { if (me.frm.doc.is_subcontracted == "Yes") {
return{ return{
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters:{ 'is_sub_contracted_item': 1 } filters:{ 'is_sub_contracted_item': 1 }
} }
} else { }
else if (me.frm.doc.material_request_type == "Customer Provided") {
return{
query: "erpnext.controllers.queries.item_query",
filters:{ 'customer': me.frm.doc.customer }
}
}
else {
return{ return{
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters: {'is_purchase_item': 1} filters: {'is_purchase_item': 1}

View File

@ -73,15 +73,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
if(this.frm.doc.currency == company_currency) { if(this.frm.doc.currency == company_currency) {
this.frm.set_value("conversion_rate", 1); this.frm.set_value("conversion_rate", 1);
} else { } else {
frappe.throw(repl('%(conversion_rate_label)s' + const err_message = __('{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}', [
__(' is mandatory. Maybe Currency Exchange record is not created for ') + conversion_rate_label,
'%(from_currency)s' + __(" to ") + '%(to_currency)s', { this.frm.doc.currency,
"conversion_rate_label": conversion_rate_label, company_currency
"from_currency": this.frm.doc.currency, ]);
"to_currency": company_currency frappe.throw(err_message);
}));
} }
} }
}, },

View File

@ -1,6 +1,6 @@
frappe.provide('erpnext.hub'); frappe.provide('erpnext.hub');
frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory { frappe.views.MarketplaceFactory = class MarketplaceFactory extends frappe.views.Factory {
show() { show() {
is_marketplace_disabled() is_marketplace_disabled()
.then(disabled => { .then(disabled => {

View File

@ -406,15 +406,19 @@ erpnext.utils.select_alternate_items = function(opts) {
erpnext.utils.update_child_items = function(opts) { erpnext.utils.update_child_items = function(opts) {
const frm = opts.frm; const frm = opts.frm;
const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row;
const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname;
this.data = []; this.data = [];
const dialog = new frappe.ui.Dialog({ const dialog = new frappe.ui.Dialog({
title: __("Update Items"), title: __("Update Items"),
fields: [ fields: [
{fieldtype:'Section Break', label: __('Items')}, {fieldtype:'Section Break', label: __('Items')},
{ {
fieldname: "trans_items", fieldtype: "Table", cannot_add_rows: true, fieldname: "trans_items",
in_place_edit: true, data: this.data, fieldtype: "Table",
cannot_add_rows: cannot_add_row,
in_place_edit: true,
data: this.data,
get_data: () => { get_data: () => {
return this.data; return this.data;
}, },
@ -450,10 +454,12 @@ erpnext.utils.update_child_items = function(opts) {
const trans_items = this.get_values()["trans_items"]; const trans_items = this.get_values()["trans_items"];
frappe.call({ frappe.call({
method: 'erpnext.controllers.accounts_controller.update_child_qty_rate', method: 'erpnext.controllers.accounts_controller.update_child_qty_rate',
freeze: true,
args: { args: {
'parent_doctype': frm.doc.doctype, 'parent_doctype': frm.doc.doctype,
'trans_items': trans_items, 'trans_items': trans_items,
'parent_doctype_name': frm.doc.name 'parent_doctype_name': frm.doc.name,
'child_docname': child_docname
}, },
callback: function() { callback: function() {
frm.reload_doc(); frm.reload_doc();

View File

@ -6,6 +6,6 @@ from frappe import _
from erpnext import get_region from erpnext import get_region
def check_deletion_permission(doc, method): def check_deletion_permission(doc, method):
region = get_region() region = get_region(doc.company)
if region in ["Nepal", "France"]: if region in ["Nepal", "France"]:
frappe.throw(_("Deletion is not permitted for country {0}".format(region))) frappe.throw(_("Deletion is not permitted for country {0}".format(region)))

View File

@ -117,7 +117,7 @@ class Gstr1Report(object):
if self.filters.get("type_of_business") == "B2B": if self.filters.get("type_of_business") == "B2B":
conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1 conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1
and customer in ('{0}')""".format("', '".join([frappe.db.escape(c.name) for c in customers])) and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
b2c_limit = frappe.db.get_single_value('GSt Settings', 'b2c_limit') b2c_limit = frappe.db.get_single_value('GSt Settings', 'b2c_limit')
@ -126,13 +126,13 @@ class Gstr1Report(object):
if self.filters.get("type_of_business") == "B2C Large": if self.filters.get("type_of_business") == "B2C Large":
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2) conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
and grand_total > {0} and is_return != 1 and customer in ('{1}')""".\ and grand_total > {0} and is_return != 1 and customer in ({1})""".\
format(flt(b2c_limit), "', '".join([frappe.db.escape(c.name) for c in customers])) format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers]))
elif self.filters.get("type_of_business") == "B2C Small": elif self.filters.get("type_of_business") == "B2C Small":
conditions += """ and ( conditions += """ and (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2) SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
or grand_total <= {0}) and is_return != 1 and customer in ('{1}')""".\ or grand_total <= {0}) and is_return != 1 and customer in ({1})""".\
format(flt(b2c_limit), "', '".join([frappe.db.escape(c.name) for c in customers])) format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers]))
elif self.filters.get("type_of_business") == "CDNR": elif self.filters.get("type_of_business") == "CDNR":
conditions += """ and is_return = 1 """ conditions += """ and is_return = 1 """

View File

@ -91,7 +91,7 @@ class Customer(TransactionBase):
def update_customer_groups(self): def update_customer_groups(self):
ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"] ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"]
if frappe.flags.customer_group_changed: if frappe.flags.customer_group_changed:
update_linked_doctypes('Customer', frappe.db.escape(self.name), 'Customer Group', update_linked_doctypes('Customer', self.name, 'Customer Group',
self.customer_group, ignore_doctypes) self.customer_group, ignore_doctypes)
def create_primary_contact(self): def create_primary_contact(self):

View File

@ -50,7 +50,7 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
} }
if(doc.docstatus == 1 && doc.status!=='Lost') { if(doc.docstatus == 1 && doc.status!=='Lost') {
if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) > 0) { if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
cur_frm.add_custom_button(__('Sales Order'), cur_frm.add_custom_button(__('Sales Order'),
cur_frm.cscript['Make Sales Order'], __('Create')); cur_frm.cscript['Make Sales Order'], __('Create'));
} }

View File

@ -25,6 +25,7 @@ frappe.ui.form.on("Sales Order", {
frm: frm, frm: frm,
child_docname: "items", child_docname: "items",
child_doctype: "Sales Order Detail", child_doctype: "Sales Order Detail",
cannot_add_row: false,
}) })
}); });
} }

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import json import json
import frappe.utils import frappe.utils
from frappe.utils import cstr, flt, getdate, comma_and, cint, nowdate, add_days from frappe.utils import cstr, flt, getdate, cint, nowdate, add_days, get_link_to_form
from frappe import _ from frappe import _
from six import string_types from six import string_types
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
@ -220,8 +220,9 @@ class SalesOrder(SellingController):
where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1""", self.name) where t1.name = t2.parent and t2.against_sales_order = %s and t1.docstatus = 1""", self.name)
if submit_dn: if submit_dn:
submit_dn = [get_link_to_form("Delivery Note", dn) for dn in submit_dn]
frappe.throw(_("Delivery Notes {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Delivery Notes {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(submit_dn))) .format(", ".join(submit_dn)))
# Checks Sales Invoice # Checks Sales Invoice
submit_rv = frappe.db.sql_list("""select t1.name submit_rv = frappe.db.sql_list("""select t1.name
@ -230,8 +231,9 @@ class SalesOrder(SellingController):
self.name) self.name)
if submit_rv: if submit_rv:
submit_rv = [get_link_to_form("Sales Invoice", si) for si in submit_rv]
frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Sales Invoice {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(submit_rv))) .format(", ".join(submit_rv)))
#check maintenance schedule #check maintenance schedule
submit_ms = frappe.db.sql_list(""" submit_ms = frappe.db.sql_list("""
@ -240,8 +242,9 @@ class SalesOrder(SellingController):
where t2.parent=t1.name and t2.sales_order = %s and t1.docstatus = 1""", self.name) where t2.parent=t1.name and t2.sales_order = %s and t1.docstatus = 1""", self.name)
if submit_ms: if submit_ms:
submit_ms = [get_link_to_form("Maintenance Schedule", ms) for ms in submit_ms]
frappe.throw(_("Maintenance Schedule {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Maintenance Schedule {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(submit_ms))) .format(", ".join(submit_ms)))
# check maintenance visit # check maintenance visit
submit_mv = frappe.db.sql_list(""" submit_mv = frappe.db.sql_list("""
@ -250,8 +253,9 @@ class SalesOrder(SellingController):
where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1""",self.name) where t2.parent=t1.name and t2.prevdoc_docname = %s and t1.docstatus = 1""",self.name)
if submit_mv: if submit_mv:
submit_mv = [get_link_to_form("Maintenance Visit", mv) for mv in submit_mv]
frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Maintenance Visit {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(submit_mv))) .format(", ".join(submit_mv)))
# check work order # check work order
pro_order = frappe.db.sql_list(""" pro_order = frappe.db.sql_list("""
@ -260,8 +264,9 @@ class SalesOrder(SellingController):
where sales_order = %s and docstatus = 1""", self.name) where sales_order = %s and docstatus = 1""", self.name)
if pro_order: if pro_order:
pro_order = [get_link_to_form("Work Order", po) for po in pro_order]
frappe.throw(_("Work Order {0} must be cancelled before cancelling this Sales Order") frappe.throw(_("Work Order {0} must be cancelled before cancelling this Sales Order")
.format(comma_and(pro_order))) .format(", ".join(pro_order)))
def check_modified_date(self): def check_modified_date(self):
mod_db = frappe.db.get_value("Sales Order", self.name, "modified") mod_db = frappe.db.get_value("Sales Order", self.name, "modified")
@ -349,15 +354,7 @@ class SalesOrder(SellingController):
def set_indicator(self): def set_indicator(self):
"""Set indicator for portal""" """Set indicator for portal"""
if self.status == 'Closed': if self.per_billed < 100 and self.per_delivered < 100:
self.indicator_color = "green"
self.indicator_title = _("Closed")
elif self.per_delivered < 100 and getdate(self.delivery_date) < getdate(nowdate()):
self.indicator_color = "red"
self.indicator_title = _("Overdue")
elif self.per_billed < 100 and self.per_delivered < 100:
self.indicator_color = "orange" self.indicator_color = "orange"
self.indicator_title = _("Not Paid and Not Delivered") self.indicator_title = _("Not Paid and Not Delivered")
@ -951,4 +948,4 @@ def make_raw_material_request(items, company, sales_order, project=None):
material_request.flags.ignore_permissions = 1 material_request.flags.ignore_permissions = 1
material_request.run_method("set_missing_values") material_request.run_method("set_missing_values")
material_request.submit() material_request.submit()
return material_request return material_request

View File

@ -12,6 +12,8 @@ from erpnext.selling.doctype.sales_order.sales_order import make_work_orders
from erpnext.controllers.accounts_controller import update_child_qty_rate from erpnext.controllers.accounts_controller import update_child_qty_rate
import json import json
from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request
class TestSalesOrder(unittest.TestCase): class TestSalesOrder(unittest.TestCase):
def tearDown(self): def tearDown(self):
frappe.set_user("Administrator") frappe.set_user("Administrator")
@ -268,6 +270,22 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"),
existing_reserved_qty_item2 + 20) existing_reserved_qty_item2 + 20)
def test_add_new_item_in_update_child_qty_rate(self):
so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4)
make_sales_invoice(so.name)
trans_item = json.dumps([{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7}])
update_child_qty_rate('Sales Order', trans_item, so.name)
so.reload()
self.assertEqual(so.get("items")[-1].item_code, '_Test Item 2')
self.assertEqual(so.get("items")[-1].rate, 200)
self.assertEqual(so.get("items")[-1].qty, 7)
self.assertEqual(so.get("items")[-1].amount, 1400)
self.assertEqual(so.status, 'To Deliver and Bill')
def test_update_child_qty_rate(self): def test_update_child_qty_rate(self):
so = make_sales_order(item_code= "_Test Item", qty=4) so = make_sales_order(item_code= "_Test Item", qty=4)
create_dn_against_so(so.name, 4) create_dn_against_so(so.name, 4)
@ -760,7 +778,7 @@ def make_sales_order(**args):
}) })
so.delivery_date = add_days(so.transaction_date, 10) so.delivery_date = add_days(so.transaction_date, 10)
if not args.do_not_save: if not args.do_not_save:
so.insert() so.insert()
if not args.do_not_submit: if not args.do_not_submit:

View File

@ -49,11 +49,15 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
where where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt}) and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
and {condition} limit {start}, {page_length}""".format(start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition), and {condition} limit {start}, {page_length}""".format(
{ start=start,
'item_code': item_code, page_length=page_length,
lft=lft,
rgt=rgt,
condition=condition
), {
'price_list': price_list 'price_list': price_list
} , as_dict=1) }, as_dict=1)
res = { res = {
'items': res 'items': res
@ -134,7 +138,7 @@ def get_conditions(item_code, serial_no, batch_no, barcode):
condition = """(i.name like %(item_code)s condition = """(i.name like %(item_code)s
or i.item_name like %(item_code)s)""" or i.item_name like %(item_code)s)"""
return '%%%s%%'%(frappe.db.escape(item_code)), condition return frappe.db.escape('%' + item_code + '%'), condition
def get_item_group_condition(pos_profile): def get_item_group_condition(pos_profile):
cond = "and 1=1" cond = "and 1=1"

View File

@ -29,9 +29,11 @@ def execute(filters=None):
if customer_naming_type == "Naming Series": if customer_naming_type == "Naming Series":
row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal, row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal,
d.bypass_credit_limit_check_at_sales_order, d.disabled] d.bypass_credit_limit_check_at_sales_order, d.is_frozen,
d.disabled]
else: else:
row = [d.name, credit_limit, outstanding_amt, bal, d.bypass_credit_limit_check_at_sales_order, d.disabled] row = [d.name, credit_limit, outstanding_amt, bal,
d.bypass_credit_limit_check_at_sales_order, d.is_frozen, d.disabled]
if credit_limit: if credit_limit:
data.append(row) data.append(row)
@ -44,8 +46,9 @@ def get_columns(customer_naming_type):
_("Credit Limit") + ":Currency:120", _("Credit Limit") + ":Currency:120",
_("Outstanding Amt") + ":Currency:100", _("Outstanding Amt") + ":Currency:100",
_("Credit Balance") + ":Currency:120", _("Credit Balance") + ":Currency:120",
_("Bypass credit check at Sales Order ") + ":Check:240", _("Bypass credit check at Sales Order ") + ":Check:80",
_("Is Disabled ") + ":Check:240" _("Is Frozen") + ":Check:80",
_("Disabled") + ":Check:80",
] ]
if customer_naming_type == "Naming Series": if customer_naming_type == "Naming Series":
@ -60,5 +63,5 @@ def get_details(filters):
conditions += " where name = %(customer)s" conditions += " where name = %(customer)s"
return frappe.db.sql("""select name, customer_name, return frappe.db.sql("""select name, customer_name,
bypass_credit_limit_check_at_sales_order,disabled from `tabCustomer` %s bypass_credit_limit_check_at_sales_order, is_frozen, disabled from `tabCustomer` %s
""" % conditions, filters, as_dict=1) """ % conditions, filters, as_dict=1)

View File

@ -76,10 +76,21 @@ frappe.query_reports["Sales Analytics"] = {
events: { events: {
onCheckRow: function(data) { onCheckRow: function(data) {
row_name = data[2].content; row_name = data[2].content;
length = data.length length = data.length;
row_values = data.slice(4,length-1).map(function (column) {
return column.content; var tree_type = frappe.query_report.filters[0].value;
})
if(tree_type == "Customer" || tree_type == "Item") {
row_values = data.slice(4,length-1).map(function (column) {
return column.content;
})
}
else {
row_values = data.slice(3,length-1).map(function (column) {
return column.content;
})
}
entry = { entry = {
'name':row_name, 'name':row_name,
'values':row_values 'values':row_values

View File

@ -276,7 +276,11 @@ class Analytics(object):
def get_chart_data(self): def get_chart_data(self):
length = len(self.columns) length = len(self.columns)
labels = [d.get("label") for d in self.columns[2:length-1]]
if self.filters.tree_type in ["Customer", "Supplier", "Item"]:
labels = [d.get("label") for d in self.columns[2:length-1]]
else:
labels = [d.get("label") for d in self.columns[1:length-1]]
self.chart = { self.chart = {
"data": { "data": {
'labels': labels, 'labels': labels,

View File

@ -40,7 +40,7 @@ class AuthorizationControl(TransactionBase):
chk = 1 chk = 1
add_cond1,add_cond2 = '','' add_cond1,add_cond2 = '',''
if based_on == 'Itemwise Discount': if based_on == 'Itemwise Discount':
add_cond1 += " and master_name = '"+frappe.db.escape(cstr(item))+"'" add_cond1 += " and master_name = " + frappe.db.escape(cstr(item))
itemwise_exists = frappe.db.sql("""select value from `tabAuthorization Rule` itemwise_exists = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction = %s and value <= %s where transaction = %s and value <= %s
and based_on = %s and company = %s and docstatus != 2 %s %s""" % and based_on = %s and company = %s and docstatus != 2 %s %s""" %

View File

@ -389,17 +389,19 @@ def update_company_current_month_sales(company):
current_month_year = formatdate(today(), "MM-yyyy") current_month_year = formatdate(today(), "MM-yyyy")
results = frappe.db.sql(''' results = frappe.db.sql('''
select SELECT
sum(base_grand_total) as total, date_format(posting_date, '%m-%Y') as month_year SUM(base_grand_total) AS total,
from DATE_FORMAT(`posting_date`, '%m-%Y') AS month_year
FROM
`tabSales Invoice` `tabSales Invoice`
where WHERE
date_format(posting_date, '%m-%Y')="{0}" DATE_FORMAT(`posting_date`, '%m-%Y') = '{current_month_year}'
and docstatus = 1 AND docstatus = 1
and company = "{1}" AND company = {company}
group by GROUP BY
month_year month_year
'''.format(current_month_year, frappe.db.escape(company)), as_dict = True) '''.format(current_month_year=current_month_year, company=frappe.db.escape(company)),
as_dict = True)
monthly_total = results[0]['total'] if len(results) > 0 else 0 monthly_total = results[0]['total'] if len(results) > 0 else 0
@ -409,7 +411,7 @@ def update_company_monthly_sales(company):
'''Cache past year monthly sales of every company based on sales invoices''' '''Cache past year monthly sales of every company based on sales invoices'''
from frappe.utils.goal import get_monthly_results from frappe.utils.goal import get_monthly_results
import json import json
filter_str = "company = '{0}' and status != 'Draft' and docstatus=1".format(frappe.db.escape(company)) filter_str = "company = {0} and status != 'Draft' and docstatus=1".format(frappe.db.escape(company))
month_to_value_dict = get_monthly_results("Sales Invoice", "base_grand_total", month_to_value_dict = get_monthly_results("Sales Invoice", "base_grand_total",
"posting_date", filter_str, "sum") "posting_date", filter_str, "sum")
@ -441,9 +443,9 @@ def get_children(doctype, parent=None, company=None, is_root=False):
from from
`tab{doctype}` comp `tab{doctype}` comp
where where
ifnull(parent_company, "")="{parent}" ifnull(parent_company, "")={parent}
""".format( """.format(
doctype = frappe.db.escape(doctype), doctype = doctype,
parent=frappe.db.escape(parent) parent=frappe.db.escape(parent)
), as_dict=1) ), as_dict=1)

View File

@ -89,18 +89,18 @@ def delete_lead_addresses(company_name):
leads = [ "'%s'"%row.get("name") for row in leads ] leads = [ "'%s'"%row.get("name") for row in leads ]
addresses = [] addresses = []
if leads: if leads:
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
in ({leads})""".format(leads=",".join(leads))) in ({leads})""".format(leads=",".join(leads)))
if addresses: if addresses:
addresses = ["'%s'"%frappe.db.escape(addr) for addr in addresses] addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
name not in (select distinct dl1.parent from `tabDynamic Link` dl1 name not in (select distinct dl1.parent from `tabDynamic Link` dl1
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))

View File

@ -4,7 +4,7 @@
"allow_rename": 0, "allow_rename": 0,
"autoname": "Prompt", "autoname": "Prompt",
"beta": 0, "beta": 0,
"creation": "2013-02-21 14:15:31", "creation": "2018-09-16 22:00:00",
"custom": 0, "custom": 0,
"description": "Send regular summary reports via Email.", "description": "Send regular summary reports via Email.",
"docstatus": 0, "docstatus": 0,
@ -563,6 +563,90 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "work_in_progress",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Work in Progress",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_orders_to_bill",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sales Orders to Bill",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_orders_to_bill",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Orders to Bill",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -646,34 +730,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "pending_sales_orders",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Pending Sales Orders",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -686,7 +742,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "New Purchase Orders", "label": "New Purchase Orders",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@ -707,15 +763,15 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "pending_purchase_orders", "fieldname": "sales_orders_to_deliver",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Pending Purchase Orders", "label": "Sales Orders to Deliver",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -730,6 +786,34 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_orders_to_receive",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Orders to Receive",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
@ -798,8 +882,8 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Pending Quotations", "label": "Open Quotations",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -869,6 +953,34 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_orders_items_overdue",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Orders Items Overdue",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1079,7 +1191,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2016-11-07 05:10:32.190134", "modified": "2018-09-16 22:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Email Digest", "name": "Email Digest",

View File

@ -96,7 +96,13 @@ class EmailDigest(Document):
quote = get_random_quote() quote = get_random_quote()
context.quote = {"text": quote[0], "author": quote[1]} context.quote = {"text": quote[0], "author": quote[1]}
if not (context.events or context.todo_list or context.notifications or context.cards): if self.get("purchase_orders_items_overdue"):
context.purchase_order_list, context.purchase_orders_items_overdue_list = self.get_purchase_orders_items_overdue_list()
if not context.purchase_order_list:
frappe.throw(_("No items to be received are overdue"))
if not (context.events or context.todo_list or context.notifications or context.cards
or context.purchase_orders_items_overdue_list):
return None return None
frappe.flags.ignore_account_permission = False frappe.flags.ignore_account_permission = False
@ -230,9 +236,10 @@ class EmailDigest(Document):
cache = frappe.cache() cache = frappe.cache()
context.cards = [] context.cards = []
for key in ("income", "expenses_booked", "income_year_to_date","expense_year_to_date", for key in ("income", "expenses_booked", "income_year_to_date", "expense_year_to_date",
"new_quotations","pending_quotations","sales_order","purchase_order","pending_sales_orders","pending_purchase_orders", "bank_balance", "credit_balance", "invoiced_amount", "payables",
"invoiced_amount", "payables", "bank_balance", "credit_balance"): "sales_orders_to_bill", "purchase_orders_to_bill", "sales_order", "purchase_order",
"sales_orders_to_deliver", "purchase_orders_to_receive", "new_quotations", "pending_quotations"):
if self.get(key): if self.get(key):
cache_key = "email_digest:card:{0}:{1}:{2}:{3}".format(self.company, self.frequency, key, self.from_date) cache_key = "email_digest:card:{0}:{1}:{2}:{3}".format(self.company, self.frequency, key, self.from_date)
card = cache.get(cache_key) card = cache.get(cache_key)
@ -346,6 +353,62 @@ class EmailDigest(Document):
return balance, past_balance, count return balance, past_balance, count
def get_sales_orders_to_bill(self):
"""Get value not billed"""
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0),
count(*) from `tabSales Order`
where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed"
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
return {
"label": self.meta.get_label("sales_orders_to_bill"),
"value": value,
"count": count
}
def get_sales_orders_to_deliver(self):
"""Get value not delivered"""
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_delivered/100)),0),
count(*) from `tabSales Order`
where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered"
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
return {
"label": self.meta.get_label("sales_orders_to_deliver"),
"value": value,
"count": count
}
def get_purchase_orders_to_receive(self):
"""Get value not received"""
value, count = frappe.db.sql("""select ifnull((sum(grand_total))-(sum(grand_total*per_received/100)),0),
count(*) from `tabPurchase Order`
where (transaction_date <= %(to_date)s) and per_received < 100
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
return {
"label": self.meta.get_label("purchase_orders_to_receive"),
"value": value,
"count": count
}
def get_purchase_orders_to_bill(self):
"""Get purchase not billed"""
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0),
count(*) from `tabPurchase Order`
where (transaction_date <= %(to_date)s) and per_billed < 100
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0]
return {
"label": self.meta.get_label("purchase_orders_to_bill"),
"value": value,
"count": count
}
def get_type_balance(self, fieldname, account_type, root_type=None): def get_type_balance(self, fieldname, account_type, root_type=None):
if root_type: if root_type:
@ -529,6 +592,30 @@ class EmailDigest(Document):
else: else:
return fmt_money(value, currency=self.currency) return fmt_money(value, currency=self.currency)
def get_purchase_orders_items_overdue_list(self):
fields_po = "distinct `tabPurchase Order Item`.parent as po"
fields_poi = "`tabPurchase Order Item`.parent, `tabPurchase Order Item`.schedule_date, item_code," \
"received_qty, qty - received_qty as missing_qty, rate, amount"
sql_po = """select {fields} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and curdate() > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.parent DESC,
`tabPurchase Order Item`.schedule_date DESC""".format(fields=fields_po)
sql_poi = """select {fields} from `tabPurchase Order Item`
left join `tabPurchase Order` on `tabPurchase Order`.name = `tabPurchase Order Item`.parent
where status<>'Closed' and `tabPurchase Order Item`.docstatus=1 and curdate() > `tabPurchase Order Item`.schedule_date
and received_qty < qty order by `tabPurchase Order Item`.idx""".format(fields=fields_poi)
purchase_order_list = frappe.db.sql(sql_po, as_dict=True)
purchase_order_items_overdue_list = frappe.db.sql(sql_poi, as_dict=True)
for t in purchase_order_items_overdue_list:
t.link = get_url_to_form("Purchase Order", t.parent)
t.rate = fmt_money(t.rate, 2, t.currency)
t.amount = fmt_money(t.amount, 2, t.currency)
return purchase_order_list, purchase_order_items_overdue_list
def send(): def send():
now_date = now_datetime().date() now_date = now_datetime().date()

View File

@ -1,6 +1,6 @@
{% macro show_card(card) %} {% macro show_card(card) %}
<div style="width: 50%; float:left; min-height: 80px; padding-top: 20px;"> <div style="width: 49%; display:inline-block; vertical-align: top; min-height: 80px; padding-top: 20px;">
<h6 style="color: {{ text_muted }}; font-size: 12px; margin-bottom: 0px; margin-top: 0px;">{{ _(card.label) }} <h6 style="color: {{ text_muted }}; font-size: 12px; margin-bottom: 0px; margin-top: 0px;">{{ card.label }}
{% if card.count %} {% if card.count %}
<span class="badge">({{ card.count }})</span> <span class="badge">({{ card.count }})</span>
{% endif %}</h6> {% endif %}</h6>
@ -180,5 +180,80 @@
<br> <br>
</div> </div>
{% endif %} {% endif %}
<!-- Purchase Order Items Overdue -->
{% if purchase_orders_items_overdue_list %}
<h4 style="{{ section_head }}" class="text-center">{{ _("Purchase Order Items not received on time") }}</h4>
<div>
<div style="background-color: #fafbfc;">
<hr>
<table style="width: 100%;">
<tr>
<th style="width: 40%;">
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold; {{ link_css }}">Item Code</span>
</th>
<th style="width: 20%; text-align: right">
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold; {{ link_css }}">Quantity</span>
</th>
<th style="width: 20%; text-align: right">
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold; {{ link_css }}">Rate</span>
</th>
<th style="width: 20%; text-align: right">
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold; {{ link_css }}">Amount</span>
</th>
</tr>
</table>
<hr>
</div>
<div>
{% for po in purchase_order_list %}
<div style="{{ line_item }}">
<table style="width: 100%;">
<tr>
<th>
<span style="padding: 3px 7px; margin-right: 7px; font-weight: bold;">{{ po.po }}</span>
</th>
</tr>
<tr>
<td>
{% for t in purchase_orders_items_overdue_list %}
{% if t.parent == po.po %}
<div >
<table style="width: 100%;">
<tr>
<td style="padding-left: 7px;">
<a style="width: 40%; {{ link_css }}" href="{{ t.link }}">{{ _(t.item_code) }}</a>
</td>
<td style="width: 20%; text-align: right">
<span style="{{ label_css }}">
{{ t.missing_qty }}
</span>
</td>
<td style="width: 20%; text-align: right">
<span style="{{ label_css }}">
{{ t.rate }}
</span>
</td>
<td style="width: 20%; text-align: right">
<span style="{{ label_css }}">
{{ t.amount }}
</span>
</td>
</tr>
</table>
</div>
{% endif %}
{% endfor %}
</td>
</tr>
</table>
</div>
{% endfor %}
</div>
</div>
<div class="text-center">
<br><br><span class="text-danger">Please take necessary action</span>
</div>
{% endif %}
</div> </div>

View File

@ -88,7 +88,7 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non
# return child item groups if the type is of "Is Group" # return child item groups if the type is of "Is Group"
return get_child_groups_for_list_in_html(item_group, start, limit, search) return get_child_groups_for_list_in_html(item_group, start, limit, search)
child_groups = ", ".join(['"' + frappe.db.escape(i[0]) + '"' for i in get_child_groups(product_group)]) child_groups = ", ".join([frappe.db.escape(i[0]) for i in get_child_groups(product_group)])
# base query # base query
query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group, query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,

View File

@ -20,6 +20,6 @@ def get_party_type(doctype, txt, searchfield, start, page_len, filters):
where `{key}` LIKE %(txt)s {cond} where `{key}` LIKE %(txt)s {cond}
order by name limit %(start)s, %(page_len)s""" order by name limit %(start)s, %(page_len)s"""
.format(key=searchfield, cond=cond), { .format(key=searchfield, cond=cond), {
'txt': "%%%s%%" % frappe.db.escape(txt), 'txt': '%' + txt + '%',
'start': start, 'page_len': page_len 'start': start, 'page_len': page_len
}) })

View File

@ -5,7 +5,6 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cstr, getdate from frappe.utils import cstr, getdate
from frappe.utils.file_manager import save_file
from .default_website import website_maker from .default_website import website_maker
from erpnext.accounts.doctype.account.account import RootNotEditable from erpnext.accounts.doctype.account.account import RootNotEditable
@ -107,10 +106,16 @@ def create_logo(args):
attach_logo = args.get("attach_logo").split(",") attach_logo = args.get("attach_logo").split(",")
if len(attach_logo)==3: if len(attach_logo)==3:
filename, filetype, content = attach_logo filename, filetype, content = attach_logo
fileurl = save_file(filename, content, "Website Settings", "Website Settings", _file = frappe.get_doc({
decode=True).file_url "doctype": "File",
"file_name": filename,
"attached_to_doctype": "Website Settings",
"attached_to_name": "Website Settings",
"decode": True})
_file.save()
fileurl = _file.file_url
frappe.db.set_value("Website Settings", "Website Settings", "brand_html", frappe.db.set_value("Website Settings", "Website Settings", "brand_html",
"<img src='{0}' style='max-width: 40px; max-height: 25px;'> {1}".format(fileurl, args.get("company_name") )) "<img src='{0}' style='max-width: 40px; max-height: 25px;'> {1}".format(fileurl, args.get("company_name")))
def create_website(args): def create_website(args):
website_maker(args) website_maker(args)
@ -121,4 +126,4 @@ def get_fy_details(fy_start_date, fy_end_date):
fy = cstr(start_year) fy = cstr(start_year)
else: else:
fy = cstr(start_year) + '-' + cstr(start_year + 1) fy = cstr(start_year) + '-' + cstr(start_year + 1)
return fy return fy

View File

@ -28,9 +28,10 @@ class ShoppingCartSettings(Document):
raise_exception=ShoppingCartSetupError) raise_exception=ShoppingCartSetupError)
price_list_currency_map = frappe.db.get_values("Price List", price_list_currency_map = frappe.db.get_values("Price List",
[self.price_list], [self.price_list], "currency")
"currency")
price_list_currency_map = dict(price_list_currency_map)
# check if all price lists have a currency # check if all price lists have a currency
for price_list, currency in price_list_currency_map.items(): for price_list, currency in price_list_currency_map.items():
if not currency: if not currency:
@ -39,8 +40,8 @@ class ShoppingCartSettings(Document):
expected_to_exist = [currency + "-" + company_currency expected_to_exist = [currency + "-" + company_currency
for currency in price_list_currency_map.values() for currency in price_list_currency_map.values()
if currency != company_currency] if currency != company_currency]
# manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange
from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency] from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency]
to_currency = company_currency to_currency = company_currency
# manqala end # manqala end

View File

@ -26,11 +26,12 @@ def boot_session(bootinfo):
'default_valid_till')) 'default_valid_till'))
# if no company, show a dialog box to create a new company # if no company, show a dialog box to create a new company
bootinfo.customer_count = frappe.db.sql("""select count(*) from tabCustomer""")[0][0] bootinfo.customer_count = frappe.db.sql("""SELECT count(*) FROM `tabCustomer`""")[0][0]
if not bootinfo.customer_count: if not bootinfo.customer_count:
bootinfo.setup_complete = frappe.db.sql("""select name from bootinfo.setup_complete = frappe.db.sql("""SELECT `name`
tabCompany limit 1""") and 'Yes' or 'No' FROM `tabCompany`
LIMIT 1""") and 'Yes' or 'No'
bootinfo.docs += frappe.db.sql("""select name, default_currency, cost_center, default_terms, bootinfo.docs += frappe.db.sql("""select name, default_currency, cost_center, default_terms,
default_letter_head, default_bank_account, enable_perpetual_inventory from `tabCompany`""", default_letter_head, default_bank_account, enable_perpetual_inventory from `tabCompany`""",

View File

@ -177,6 +177,9 @@ class DeliveryNote(SellingController):
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code)) frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
else: else:
chk_dupl_itm.append(f) chk_dupl_itm.append(f)
#Customer Provided parts will have zero valuation rate
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1
def validate_warehouse(self): def validate_warehouse(self):
super(DeliveryNote, self).validate_warehouse() super(DeliveryNote, self).validate_warehouse()

View File

@ -115,6 +115,8 @@ frappe.ui.form.on("Item", {
['is_stock_item', 'has_serial_no', 'has_batch_no'].forEach((fieldname) => { ['is_stock_item', 'has_serial_no', 'has_batch_no'].forEach((fieldname) => {
frm.set_df_property(fieldname, 'read_only', stock_exists); frm.set_df_property(fieldname, 'read_only', stock_exists);
}); });
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
}, },
validate: function(frm){ validate: function(frm){
@ -124,6 +126,10 @@ frappe.ui.form.on("Item", {
image: function() { image: function() {
refresh_field("image_view"); refresh_field("image_view");
}, },
is_customer_provided_item: function(frm) {
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
},
is_fixed_asset: function(frm) { is_fixed_asset: function(frm) {
frm.call({ frm.call({

File diff suppressed because it is too large Load Diff

View File

@ -128,6 +128,7 @@ class Item(WebsiteGenerator):
self.validate_uom_conversion_factor() self.validate_uom_conversion_factor()
self.validate_item_defaults() self.validate_item_defaults()
self.update_defaults_from_item_group() self.update_defaults_from_item_group()
self.validate_customer_provided_part()
if not self.get("__islocal"): if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@ -147,6 +148,14 @@ class Item(WebsiteGenerator):
if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')): if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
self.description = clean_html(self.description) self.description = clean_html(self.description)
def validate_customer_provided_part(self):
if self.is_customer_provided_item:
if self.is_purchase_item:
frappe.throw(_('"Customer Provided Item" cannot be Purchase Item also'))
if self.valuation_rate:
frappe.throw(_('"Customer Provided Item" cannot have Valuation Rate'))
self.default_material_request_type = "Customer Provided"
def add_price(self, price_list=None): def add_price(self, price_list=None):
'''Add a new price''' '''Add a new price'''
if not price_list: if not price_list:
@ -261,7 +270,7 @@ class Item(WebsiteGenerator):
"file_url": self.website_image, "file_url": self.website_image,
"attached_to_doctype": "Item", "attached_to_doctype": "Item",
"attached_to_name": self.name "attached_to_name": self.name
}).insert() }).save()
except IOError: except IOError:
self.website_image = None self.website_image = None
@ -972,7 +981,7 @@ def get_uom_conv_factor(uom, stock_uom):
value = "" value = ""
uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\ uom_details = frappe.db.sql("""select to_uom, from_uom, value from `tabUOM Conversion Factor`\
where to_uom in ({0}) where to_uom in ({0})
""".format(', '.join(['"' + frappe.db.escape(i, percent=False) + '"' for i in uoms])), as_dict=True) """.format(', '.join([frappe.db.escape(i, percent=False) for i in uoms])), as_dict=True)
for d in uom_details: for d in uom_details:
if d.from_uom == stock_uom and d.to_uom == uom: if d.from_uom == stock_uom and d.to_uom == uom:

View File

@ -378,7 +378,7 @@ def make_item_variant():
test_records = frappe.get_test_records('Item') test_records = frappe.get_test_records('Item')
def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None): def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None):
if not frappe.db.exists("Item", item_code): if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item") item = frappe.new_doc("Item")
item.item_code = item_code item.item_code = item_code
@ -387,6 +387,9 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None)
item.item_group = "All Item Groups" item.item_group = "All Item Groups"
item.is_stock_item = is_stock_item or 1 item.is_stock_item = is_stock_item or 1
item.valuation_rate = valuation_rate or 0.0 item.valuation_rate = valuation_rate or 0.0
item.is_purchase_item = is_purchase_item
item.is_customer_provided_item = is_customer_provided_item
item.customer = customer or ''
item.append("item_defaults", { item.append("item_defaults", {
"default_warehouse": warehouse or '_Test Warehouse - _TC', "default_warehouse": warehouse or '_Test Warehouse - _TC',
"company": "_Test Company" "company": "_Test Company"

View File

@ -35,6 +35,6 @@ def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
where alternative_item_code = %(item_code)s and item_code like %(txt)s where alternative_item_code = %(item_code)s and item_code like %(txt)s
and two_way = 1) limit {0}, {1} and two_way = 1) limit {0}, {1}
""".format(start, page_len), { """.format(start, page_len), {
"item_code": frappe.db.escape(filters.get('item_code')), "item_code": filters.get('item_code'),
"txt": "%%%s%%" % frappe.db.escape(txt) "txt": '%' + txt + '%'
}) })

View File

@ -40,6 +40,7 @@ frappe.ui.form.on('Material Request', {
refresh: function(frm) { refresh: function(frm) {
frm.events.make_custom_buttons(frm); frm.events.make_custom_buttons(frm);
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
}, },
make_custom_buttons: function(frm) { make_custom_buttons: function(frm) {
@ -61,6 +62,11 @@ frappe.ui.form.on('Material Request', {
() => frm.events.make_stock_entry(frm), __('Create')); () => frm.events.make_stock_entry(frm), __('Create'));
} }
if (frm.doc.material_request_type === "Customer Provided") {
frm.add_custom_button(__("Material Receipt"),
() => frm.events.make_stock_entry(frm), __("Make"));
}
if (frm.doc.material_request_type === "Purchase") { if (frm.doc.material_request_type === "Purchase") {
frm.add_custom_button(__('Purchase Order'), frm.add_custom_button(__('Purchase Order'),
() => frm.events.make_purchase_order(frm), __('Create')); () => frm.events.make_purchase_order(frm), __('Create'));
@ -259,6 +265,9 @@ frappe.ui.form.on('Material Request', {
} }
}); });
}, },
material_request_type: function(frm) {
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
},
}); });

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ class MaterialRequest(BuyingController):
from erpnext.controllers.status_updater import validate_status from erpnext.controllers.status_updater import validate_status
validate_status(self.status, validate_status(self.status,
["Draft", "Submitted", "Stopped", "Cancelled", "Pending", ["Draft", "Submitted", "Stopped", "Cancelled", "Pending",
"Partially Ordered", "Ordered", "Issued", "Transferred"]) "Partially Ordered", "Ordered", "Issued", "Transferred", "Received"])
validate_for_items(self) validate_for_items(self)
@ -154,7 +154,7 @@ class MaterialRequest(BuyingController):
for d in self.get("items"): for d in self.get("items"):
if d.name in mr_items: if d.name in mr_items:
if self.material_request_type in ("Material Issue", "Material Transfer"): if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
d.ordered_qty = flt(frappe.db.sql("""select sum(transfer_qty) d.ordered_qty = flt(frappe.db.sql("""select sum(transfer_qty)
from `tabStock Entry Detail` where material_request = %s from `tabStock Entry Detail` where material_request = %s
and material_request_item = %s and docstatus = 1""", and material_request_item = %s and docstatus = 1""",
@ -239,6 +239,18 @@ def update_item(obj, target, source_parent):
target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor
target.stock_qty = (target.qty * target.conversion_factor) target.stock_qty = (target.qty * target.conversion_factor)
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context)
list_context.update({
'show_sidebar': True,
'show_search': True,
'no_breadcrumbs': True,
'title': _('Material Request'),
})
return list_context
@frappe.whitelist() @frappe.whitelist()
def update_status(name, status): def update_status(name, status):
material_request = frappe.get_doc('Material Request', name) material_request = frappe.get_doc('Material Request', name)
@ -400,7 +412,7 @@ def make_stock_entry(source_name, target_doc=None):
target.transfer_qty = qty * obj.conversion_factor target.transfer_qty = qty * obj.conversion_factor
target.conversion_factor = obj.conversion_factor target.conversion_factor = obj.conversion_factor
if source_parent.material_request_type == "Material Transfer": if source_parent.material_request_type == "Material Transfer" or source_parent.material_request_type == "Customer Provided":
target.t_warehouse = obj.warehouse target.t_warehouse = obj.warehouse
else: else:
target.s_warehouse = obj.warehouse target.s_warehouse = obj.warehouse
@ -410,6 +422,9 @@ def make_stock_entry(source_name, target_doc=None):
if source.job_card: if source.job_card:
target.purpose = 'Material Transfer for Manufacture' target.purpose = 'Material Transfer for Manufacture'
if source.material_request_type == "Customer Provided":
target.purpose = "Material Receipt"
target.run_method("calculate_rate_and_amount") target.run_method("calculate_rate_and_amount")
target.set_job_card_data() target.set_job_card_data()

View File

@ -14,6 +14,8 @@ frappe.listview_settings['Material Request'] = {
return [__("Transfered"), "green", "per_ordered,=,100"]; return [__("Transfered"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Material Issue") { } else if (doc.material_request_type == "Material Issue") {
return [__("Issued"), "green", "per_ordered,=,100"]; return [__("Issued"), "green", "per_ordered,=,100"];
} else if (doc.material_request_type == "Customer Provided") {
return [__("Received"), "green", "per_ordered,=,100"];
} }
} }
} }

View File

@ -8,6 +8,7 @@ from __future__ import unicode_literals
import frappe, unittest, erpnext import frappe, unittest, erpnext
from frappe.utils import flt, today from frappe.utils import flt, today
from erpnext.stock.doctype.material_request.material_request import raise_work_orders from erpnext.stock.doctype.material_request.material_request import raise_work_orders
from erpnext.stock.doctype.item.test_item import create_item
class TestMaterialRequest(unittest.TestCase): class TestMaterialRequest(unittest.TestCase):
def setUp(self): def setUp(self):
@ -601,11 +602,25 @@ class TestMaterialRequest(unittest.TestCase):
mr = frappe.get_doc("Material Request", mr.name) mr = frappe.get_doc("Material Request", mr.name)
self.assertEqual(mr.per_ordered, 100) self.assertEqual(mr.per_ordered, 100)
def test_customer_provided_parts_mr(self):
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
mr = make_material_request(item_code='CUST-0987', material_request_type='Customer Provided')
se = make_stock_entry(mr.name)
se.insert()
se.submit()
self.assertEqual(se.get("items")[0].amount, 0)
self.assertEqual(se.get("items")[0].material_request, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.submit()
self.assertEqual(mr.per_ordered, 100)
def make_material_request(**args): def make_material_request(**args):
args = frappe._dict(args) args = frappe._dict(args)
mr = frappe.new_doc("Material Request") mr = frappe.new_doc("Material Request")
mr.material_request_type = args.material_request_type or "Purchase" mr.material_request_type = args.material_request_type or "Purchase"
mr.company = args.company or "_Test Company" mr.company = args.company or "_Test Company"
mr.customer = args.customer or '_Test Customer'
mr.append("items", { mr.append("items", {
"item_code": args.item_code or "_Test Item", "item_code": args.item_code or "_Test Item",
"qty": args.qty or 10, "qty": args.qty or 10,

View File

@ -171,7 +171,7 @@ class SerialNo(StockController):
where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""): where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""):
for item in frappe.db.sql("""select name, serial_no from `tab%s` for item in frappe.db.sql("""select name, serial_no from `tab%s`
where serial_no like '%%%s%%'""" % (dt[0], frappe.db.escape(old))): where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))):
serial_nos = map(lambda i: new if i.upper()==old.upper() else i, item[1].split('\n')) serial_nos = map(lambda i: new if i.upper()==old.upper() else i, item[1].split('\n'))
frappe.db.sql("""update `tab%s` set serial_no = %s frappe.db.sql("""update `tab%s` set serial_no = %s

Some files were not shown because too many files have changed in this diff Show More