diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 2a375ae2ee..ee4ca0c791 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '11.1.16'
+__version__ = '11.1.17'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/dashboard_chart_source/__init__.py b/erpnext/accounts/dashboard_chart_source/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/__init__.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js
new file mode 100644
index 0000000000..eebd2dbd34
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.js
@@ -0,0 +1,45 @@
+frappe.dashboard_chart_sources["Account Balance Timeline"] = {
+ method_path: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get",
+ filters: [
+ {
+ fieldname: "company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ fieldname: "account",
+ label: __("Account"),
+ fieldtype: "Link",
+ options: "Account",
+ reqd: 1
+ },
+ {
+ fieldname: "timespan",
+ label: __("Period"),
+ fieldtype: "Select",
+ options: [
+ {value: "Last Year", label: __("Last Year")},
+ {value: "Last Quarter", label: __("Last Quarter")},
+ {value: "Last Month", label: __("Last Month")},
+ {value: "Last Week", label: __("Last Week")}
+ ],
+ reqd: 1
+ },
+ {
+ fieldname: "timegrain",
+ label: __("Periodicity"),
+ fieldtype: "Select",
+ options: [
+ {value: "Quarterly", label: __("Quarterly")},
+ {value: "Monthly", label: __("Monthly")},
+ {value: "Weekly", label: __("Weekly")},
+ {value: "Daily", label: __("Daily")}
+ ],
+ reqd: 1
+ },
+ ],
+ is_time_series: true
+};
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.json b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.json
new file mode 100644
index 0000000000..b7ea601564
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.json
@@ -0,0 +1,13 @@
+{
+ "config": "{\n \"method_path\": \"erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get\",\n\t\"filters\": [\n\t\t{\n\t\t\t\"fieldname\": \"company\",\n\t\t\t\"label\": \"Company\",\n\t\t\t\"fieldtype\": \"Link\",\n\t\t\t\"options\": \"Company\",\n\t\t\t\"reqd\": 1\n\t\t},\n\t\t{\n\t\t\t\"fieldname\": \"account\",\n\t\t\t\"label\": \"Account\",\n\t\t\t\"fieldtype\": \"Link\",\n\t\t\t\"options\": \"Account\",\n\t\t\t\"reqd\": 1\n\t\t},\n\t\t{\n\t\t\t\"fieldname\": \"timespan\",\n\t\t\t\"label\": \"Period\",\n\t\t\t\"fieldtype\": \"Select\",\n\t\t\t\"options\": [\n\t\t\t\t{\"value\": \"Last Year\", \"label\": \"Last Year\"},\n\t\t\t\t{\"value\": \"Last Quarter\", \"label\": \"Last Quarter\"},\n\t\t\t\t{\"value\": \"Last Month\", \"label\": \"Last Month\"},\n\t\t\t\t{\"value\": \"Last Week\", \"label\": \"Last Week\"}\n\t\t\t],\n\t\t\t\"reqd\": 1\n\t\t},\n\t\t{\n\t\t\t\"fieldname\": \"timegrain\",\n\t\t\t\"label\": \"Periodicity\",\n\t\t\t\"fieldtype\": \"Select\",\n\t\t\t\"options\": [\n\t\t\t\t{\"value\": \"Quarterly\", \"label\": \"Quarterly\"},\n\t\t\t\t{\"value\": \"Monthly\", \"label\": \"Monthly\"},\n\t\t\t\t{\"value\": \"Weekly\", \"label\": \"Weekly\"},\n\t\t\t\t{\"value\": \"Daily\", \"label\": \"Daily\"}\n\t\t\t],\n\t\t\t\"reqd\": 1\n\t\t}\n\t],\n\t\"is_time_series\": true\n}\n",
+ "creation": "2019-02-06 07:57:10.377718",
+ "docstatus": 0,
+ "doctype": "Dashboard Chart Source",
+ "idx": 0,
+ "modified": "2019-03-15 16:14:26.505986",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Account Balance Timeline",
+ "owner": "Administrator",
+ "source_name": "Account Balance Timeline"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
new file mode 100644
index 0000000000..52c202386c
--- /dev/null
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+from itertools import groupby
+from operator import itemgetter
+import frappe
+from frappe.core.page.dashboard.dashboard import cache_source
+from frappe.utils import add_to_date, date_diff, getdate, nowdate
+from erpnext.accounts.report.general_ledger.general_ledger import execute
+
+from frappe.utils.nestedset import get_descendants_of
+
+@frappe.whitelist()
+@cache_source
+def get(filters=None):
+ timespan = filters.get("timespan")
+ timegrain = filters.get("timegrain")
+ account = filters.get("account")
+ company = filters.get("company")
+
+ from_date = get_from_date_from_timespan(timespan)
+ to_date = nowdate()
+
+ # fetch dates to plot
+ dates = get_dates_from_timegrain(from_date, to_date, timegrain)
+
+ # get all the entries for this account and its descendants
+ gl_entries = get_gl_entries(account, to_date)
+
+ # compile balance values
+ result = build_result(account, dates, gl_entries)
+
+ return {
+ "labels": [r[0].strftime('%Y-%m-%d') for r in result],
+ "datasets": [{
+ "name": account,
+ "values": [r[1] for r in result]
+ }]
+ }
+
+def build_result(account, dates, gl_entries):
+ result = [[getdate(date), 0.0] for date in dates]
+ root_type = frappe.db.get_value('Account', account, 'root_type')
+
+ # start with the first date
+ date_index = 0
+
+ # get balances in debit
+ for entry in gl_entries:
+
+ # entry date is after the current pointer, so move the pointer forward
+ while getdate(entry.posting_date) > result[date_index][0]:
+ date_index += 1
+
+ result[date_index][1] += entry.debit - entry.credit
+
+ # if account type is credit, switch balances
+ if root_type not in ('Asset', 'Expense'):
+ for r in result:
+ r[1] = -1 * r[1]
+
+ # for balance sheet accounts, the totals are cumulative
+ if root_type in ('Asset', 'Liability', 'Equity'):
+ for i, r in enumerate(result):
+ if i < 0:
+ r[1] = r[1] + result[i-1][1]
+
+ return result
+
+def get_gl_entries(account, to_date):
+ child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
+ child_accounts.append(account)
+
+ return frappe.db.get_all('GL Entry',
+ fields = ['posting_date', 'debit', 'credit'],
+ filters = [
+ dict(posting_date = ('<', to_date)),
+ dict(account = ('in', child_accounts))
+ ],
+ order_by = 'posting_date asc')
+
+def get_from_date_from_timespan(timespan):
+ days = months = years = 0
+ if "Last Week" == timespan:
+ days = -7
+ if "Last Month" == timespan:
+ months = -1
+ elif "Last Quarter" == timespan:
+ months = -3
+ elif "Last Year" == timespan:
+ years = -1
+ return add_to_date(None, years=years, months=months, days=days,
+ as_string=True, as_datetime=True)
+
+def get_dates_from_timegrain(from_date, to_date, timegrain):
+ days = months = years = 0
+ if "Daily" == timegrain:
+ days = 1
+ elif "Weekly" == timegrain:
+ days = 7
+ elif "Monthly" == timegrain:
+ months = 1
+ elif "Quarterly" == timegrain:
+ months = 3
+
+ dates = [from_date]
+ while dates[-1] <= to_date:
+ dates.append(add_to_date(dates[-1], years=years, months=months, days=days))
+ return dates
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index e305652fbd..32485a3469 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -2,9 +2,9 @@ from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.utils import date_diff, add_months, today, getdate, add_days, flt
+from frappe.utils import date_diff, add_months, today, getdate, add_days, flt, get_last_day
from erpnext.accounts.utils import get_account_currency
-from erpnext.accounts.general_ledger import make_gl_entries
+from frappe.email import sendmail_to_system_managers
def validate_service_stop_date(doc):
''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
@@ -33,47 +33,49 @@ def validate_service_stop_date(doc):
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):
+ # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
+ if not start_date:
+ start_date = add_months(today(), -1)
+ if not end_date:
+ end_date = add_days(today(), -1)
+
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
- select distinct parent from `tabPurchase Invoice Item` where service_start_date<=%s and service_end_date>=%s
+ select distinct parent from `tabPurchase Invoice Item`
+ where service_start_date<=%s and service_end_date>=%s
and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
- ''', (end_date or today(), start_date or add_months(today(), -1)))
+ ''', (end_date, start_date))
# For each invoice, book deferred expense
for invoice in invoices:
doc = frappe.get_doc("Purchase Invoice", invoice)
- book_deferred_income_or_expense(doc, start_date, end_date)
+ book_deferred_income_or_expense(doc, end_date)
def convert_deferred_revenue_to_income(start_date=None, end_date=None):
+ # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
+ if not start_date:
+ start_date = add_months(today(), -1)
+ if not end_date:
+ end_date = add_days(today(), -1)
+
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
- select distinct parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s
+ select distinct parent from `tabSales Invoice Item`
+ where service_start_date<=%s and service_end_date>=%s
and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
- ''', (end_date or today(), start_date or add_months(today(), -1)))
+ ''', (end_date, start_date))
- # For each invoice, book deferred revenue
for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice)
- book_deferred_income_or_expense(doc, start_date, end_date)
+ book_deferred_income_or_expense(doc, end_date)
+
+def get_booking_dates(doc, item, posting_date=None):
+ if not posting_date:
+ posting_date = add_days(today(), -1)
+
+ last_gl_entry = False
-def get_booking_dates(doc, item, start_date=None, end_date=None):
deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
- last_gl_entry, skip = False, False
-
- booking_end_date = getdate(add_days(today(), -1) if not end_date else end_date)
- if booking_end_date < item.service_start_date or \
- (item.service_stop_date and booking_end_date.month > item.service_stop_date.month):
- return None, None, None, True
- elif booking_end_date >= item.service_end_date:
- last_gl_entry = True
- booking_end_date = item.service_end_date
- elif item.service_stop_date and item.service_stop_date <= booking_end_date:
- last_gl_entry = True
- booking_end_date = item.service_stop_date
-
- booking_start_date = getdate(add_months(today(), -1) if not start_date else start_date)
- booking_start_date = booking_start_date \
- if booking_start_date > item.service_start_date else item.service_start_date
prev_gl_entry = frappe.db.sql('''
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
@@ -81,17 +83,28 @@ def get_booking_dates(doc, item, start_date=None, end_date=None):
order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
- if not prev_gl_entry and item.service_start_date < booking_start_date:
- booking_start_date = item.service_start_date
- elif prev_gl_entry:
- booking_start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
- skip = True if booking_start_date > booking_end_date else False
+ if prev_gl_entry:
+ start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
+ else:
+ start_date = item.service_start_date
- return last_gl_entry, booking_start_date, booking_end_date, skip
+ end_date = get_last_day(start_date)
+ if end_date >= item.service_end_date:
+ end_date = item.service_end_date
+ last_gl_entry = True
+ elif item.service_stop_date and end_date >= item.service_stop_date:
+ end_date = item.service_stop_date
+ last_gl_entry = True
-def calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total_booking_days):
- account_currency = get_account_currency(item.expense_account)
+ if end_date > getdate(posting_date):
+ end_date = posting_date
+ if getdate(start_date) <= getdate(end_date):
+ return start_date, end_date, last_gl_entry
+ else:
+ return None, None, None
+
+def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
deferred_account = "deferred_revenue_account"
@@ -123,28 +136,15 @@ def calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total
return amount, base_amount
-def book_deferred_income_or_expense(doc, start_date=None, end_date=None):
- # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
- # start_date: 1st of the last month or the start date
- # end_date: end_date or today-1
+def book_deferred_income_or_expense(doc, posting_date=None):
enable_check = "enable_deferred_revenue" \
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
- gl_entries = []
- for item in doc.get('items'):
- if not item.get(enable_check): continue
-
- skip = False
- last_gl_entry, booking_start_date, booking_end_date, skip = \
- get_booking_dates(doc, item, start_date, end_date)
-
- if skip: continue
- total_days = date_diff(item.service_end_date, item.service_start_date) + 1
- total_booking_days = date_diff(booking_end_date, booking_start_date) + 1
+ def _book_deferred_revenue_or_expense(item):
+ start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
+ if not (start_date and end_date): return
account_currency = get_account_currency(item.expense_account)
- amount, base_amount = calculate_amount_and_base_amount(doc, item, last_gl_entry, total_days, total_booking_days)
-
if doc.doctype == "Sales Invoice":
against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account
@@ -152,36 +152,62 @@ def book_deferred_income_or_expense(doc, start_date=None, end_date=None):
against, project = doc.supplier, item.project
credit_account, debit_account = item.deferred_expense_account, item.expense_account
- # GL Entry for crediting the amount in the deferred expense
- gl_entries.append(
- doc.get_gl_dict({
- "account": credit_account,
- "against": against,
- "credit": base_amount,
- "credit_in_account_currency": amount,
- "cost_center": item.cost_center,
- "voucher_detail_no": item.name,
- 'posting_date': booking_end_date,
- 'project': project
- }, account_currency)
- )
- # GL Entry to debit the amount from the expense
- gl_entries.append(
- doc.get_gl_dict({
- "account": debit_account,
- "against": against,
- "debit": base_amount,
- "debit_in_account_currency": amount,
- "cost_center": item.cost_center,
- "voucher_detail_no": item.name,
- 'posting_date': booking_end_date,
- 'project': project
- }, account_currency)
- )
+ total_days = date_diff(item.service_end_date, item.service_start_date) + 1
+ total_booking_days = date_diff(end_date, start_date) + 1
+
+ amount, base_amount = calculate_amount(doc, item, last_gl_entry,
+ total_days, total_booking_days, account_currency)
+
+ make_gl_entries(doc, credit_account, debit_account, against,
+ amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
+
+ if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
+ _book_deferred_revenue_or_expense(item)
+
+
+ for item in doc.get('items'):
+ if item.get(enable_check):
+ _book_deferred_revenue_or_expense(item)
+
+def make_gl_entries(doc, credit_account, debit_account, against,
+ amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
+ # GL Entry for crediting the amount in the deferred expense
+ from erpnext.accounts.general_ledger import make_gl_entries
+
+ gl_entries = []
+ gl_entries.append(
+ doc.get_gl_dict({
+ "account": credit_account,
+ "against": against,
+ "credit": base_amount,
+ "credit_in_account_currency": amount,
+ "cost_center": cost_center,
+ "voucher_detail_no": voucher_detail_no,
+ 'posting_date': posting_date,
+ 'project': project
+ }, account_currency)
+ )
+ # GL Entry to debit the amount from the expense
+ gl_entries.append(
+ doc.get_gl_dict({
+ "account": debit_account,
+ "against": against,
+ "debit": base_amount,
+ "debit_in_account_currency": amount,
+ "cost_center": cost_center,
+ "voucher_detail_no": voucher_detail_no,
+ 'posting_date': posting_date,
+ 'project': project
+ }, account_currency)
+ )
+
if gl_entries:
try:
make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True)
frappe.db.commit()
except:
frappe.db.rollback()
- frappe.log_error(message = frappe.get_traceback(), title = _("Error while processing deferred accounting for {0}").format(doc.name))
\ No newline at end of file
+ title = _("Error while processing deferred accounting for {0}").format(doc.name)
+ traceback = frappe.get_traceback()
+ frappe.log_error(message=traceback , title=title)
+ sendmail_to_system_managers(title, traceback)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 427f3dba20..3d9604d159 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -98,6 +98,8 @@ class Account(NestedSet):
ancestors = get_root_company(self.company)
if ancestors:
+ if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
+ return
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
else:
descendants = get_descendants_of('Company', self.company)
@@ -110,6 +112,8 @@ class Account(NestedSet):
["company", "name"], as_dict=True):
acc_name_map[d["company"]] = d["name"]
+ if not acc_name_map: return
+
for company in descendants:
doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True
diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js
index 03c98fadf2..dc4c69d9e8 100644
--- a/erpnext/accounts/doctype/account/account_tree.js
+++ b/erpnext/accounts/doctype/account/account_tree.js
@@ -23,6 +23,10 @@ frappe.treeview_settings["Account"] = {
if(r.message) {
let root_company = r.message.length ? r.message[0] : "";
me.page.fields_dict.root_company.set_value(root_company);
+
+ frappe.db.get_value("Company", {"name": company}, "allow_account_creation_against_child_company", (r) => {
+ frappe.flags.ignore_root_company_validation = r.allow_account_creation_against_child_company;
+ });
}
}
});
@@ -133,9 +137,10 @@ frappe.treeview_settings["Account"] = {
{
label:__("Add Child"),
condition: function(node) {
- return frappe.boot.user.can_create.indexOf("Account") !== -1 &&
- !frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value() &&
- node.expandable && !node.hide_add;
+ return frappe.boot.user.can_create.indexOf("Account") !== -1
+ && (!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value()
+ || frappe.flags.ignore_root_company_validation)
+ && node.expandable && !node.hide_add;
},
click: function() {
var me = frappe.treeview_settings['Account'].treeview;
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index fc6f4e4eca..4ee55736fe 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -146,7 +146,7 @@ def _make_test_records(verbose):
# related to Account Inventory Integration
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
-
+
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, None, None],
@@ -183,13 +183,17 @@ def get_inventory_account(company, warehouse=None):
return account
def create_account(**kwargs):
- account = frappe.get_doc(dict(
- doctype = "Account",
- account_name = kwargs.get('account_name'),
- account_type = kwargs.get('account_type'),
- parent_account = kwargs.get('parent_account'),
- company = kwargs.get('company')
- ))
-
- account.save()
- return account.name
+ account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
+ if account:
+ return account
+ else:
+ account = frappe.get_doc(dict(
+ doctype = "Account",
+ account_name = kwargs.get('account_name'),
+ account_type = kwargs.get('account_type'),
+ parent_account = kwargs.get('parent_account'),
+ company = kwargs.get('company')
+ ))
+
+ account.save()
+ return account.name
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 945d5d47f5..be5407b926 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -23,6 +23,7 @@
"columns": 0,
"default": "1",
"description": "If enabled, the system will post accounting entries for inventory automatically.",
+ "fetch_if_empty": 0,
"fieldname": "auto_accounting_for_stock",
"fieldtype": "Check",
"hidden": 1,
@@ -55,6 +56,7 @@
"collapsible": 0,
"columns": 0,
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
+ "fetch_if_empty": 0,
"fieldname": "acc_frozen_upto",
"fieldtype": "Date",
"hidden": 0,
@@ -87,6 +89,7 @@
"collapsible": 0,
"columns": 0,
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
+ "fetch_if_empty": 0,
"fieldname": "frozen_accounts_modifier",
"fieldtype": "Link",
"hidden": 0,
@@ -121,6 +124,7 @@
"columns": 0,
"default": "Billing Address",
"description": "Address used to determine Tax Category in transactions.",
+ "fetch_if_empty": 0,
"fieldname": "determine_address_tax_category_from",
"fieldtype": "Select",
"hidden": 0,
@@ -154,6 +158,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
@@ -186,6 +191,7 @@
"collapsible": 0,
"columns": 0,
"description": "Role that is allowed to submit transactions that exceed credit limits set.",
+ "fetch_if_empty": 0,
"fieldname": "credit_controller",
"fieldtype": "Link",
"hidden": 0,
@@ -218,6 +224,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"hidden": 0,
@@ -250,6 +257,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "make_payment_via_journal_entry",
"fieldtype": "Check",
"hidden": 0,
@@ -283,6 +291,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
"fieldname": "unlink_payment_on_cancellation_of_invoice",
"fieldtype": "Check",
"hidden": 0,
@@ -316,6 +325,41 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
+ "fieldname": "unlink_advance_payment_on_cancelation_of_order",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Unlink Advance Payment on Cancelation of Order",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "default": "1",
+ "fetch_if_empty": 0,
"fieldname": "book_asset_depreciation_entry_automatically",
"fieldtype": "Check",
"hidden": 0,
@@ -348,6 +392,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "allow_cost_center_in_entry_of_bs_account",
"fieldtype": "Check",
"hidden": 0,
@@ -381,6 +426,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
"fieldname": "add_taxes_from_item_tax_template",
"fieldtype": "Check",
"hidden": 0,
@@ -413,6 +459,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "print_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -445,6 +492,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "show_inclusive_tax_in_print",
"fieldtype": "Check",
"hidden": 0,
@@ -477,6 +525,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
@@ -508,6 +557,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "show_payment_schedule_in_print",
"fieldtype": "Check",
"hidden": 0,
@@ -540,6 +590,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "currency_exchange_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -573,6 +624,7 @@
"collapsible": 0,
"columns": 0,
"default": "1",
+ "fetch_if_empty": 0,
"fieldname": "allow_stale",
"fieldtype": "Check",
"hidden": 0,
@@ -607,6 +659,7 @@
"columns": 0,
"default": "1",
"depends_on": "eval:doc.allow_stale==0",
+ "fetch_if_empty": 0,
"fieldname": "stale_days",
"fieldtype": "Int",
"hidden": 0,
@@ -639,6 +692,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fetch_if_empty": 0,
"fieldname": "report_settings_sb",
"fieldtype": "Section Break",
"hidden": 0,
@@ -673,6 +727,7 @@
"columns": 0,
"default": "0",
"description": "Only select if you have setup Cash Flow Mapper documents",
+ "fetch_if_empty": 0,
"fieldname": "use_custom_cash_flow",
"fieldtype": "Check",
"hidden": 0,
@@ -700,17 +755,15 @@
}
],
"has_web_view": 0,
- "hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-cog",
"idx": 1,
- "image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
- "modified": "2019-01-07 00:42:34.510150",
+ "modified": "2019-04-06 12:28:43.026250",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -776,10 +829,9 @@
],
"quick_entry": 1,
"read_only": 0,
- "read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py
index 917276293d..fae5213f23 100644
--- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py
+++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py
@@ -23,36 +23,36 @@ class BankReconciliation(Document):
journal_entries = frappe.db.sql("""
- select
- "Journal Entry" as payment_document, t1.name as payment_entry,
- t1.cheque_no as cheque_number, t1.cheque_date,
+ select
+ "Journal Entry" as payment_document, t1.name as payment_entry,
+ t1.cheque_no as cheque_number, t1.cheque_date,
sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit,
- t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
+ t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
t2.parent = t1.name and t2.account = %s and t1.docstatus=1
- and t1.posting_date >= %s and t1.posting_date <= %s
+ and t1.posting_date >= %s and t1.posting_date <= %s
and ifnull(t1.is_opening, 'No') = 'No' {0}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""".format(condition), (self.bank_account, self.from_date, self.to_date), as_dict=1)
payment_entries = frappe.db.sql("""
- select
- "Payment Entry" as payment_document, name as payment_entry,
- reference_no as cheque_number, reference_date as cheque_date,
- if(paid_from=%(account)s, paid_amount, "") as credit,
- if(paid_from=%(account)s, "", received_amount) as debit,
+ select
+ "Payment Entry" as payment_document, name as payment_entry,
+ reference_no as cheque_number, reference_date as cheque_date,
+ if(paid_from=%(account)s, paid_amount, 0) as credit,
+ if(paid_from=%(account)s, 0, received_amount) as debit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s {0}
- order by
+ order by
posting_date ASC, name DESC
- """.format(condition),
+ """.format(condition),
{"account":self.bank_account, "from":self.from_date, "to":self.to_date}, as_dict=1)
pos_entries = []
@@ -79,8 +79,12 @@ class BankReconciliation(Document):
for d in entries:
row = self.append('payment_entries', {})
- amount = d.debit if d.debit else d.credit
- d.amount = fmt_money(amount, 2, d.account_currency) + " " + (_("Dr") if d.debit else _("Cr"))
+
+ amount = flt(d.get('debit', 0)) - flt(d.get('credit', 0))
+
+ formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
+ d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
+
d.pop("credit")
d.pop("debit")
d.pop("account_currency")
@@ -103,10 +107,10 @@ class BankReconciliation(Document):
d.clearance_date = None
frappe.db.set_value(d.payment_document, d.payment_entry, "clearance_date", d.clearance_date)
- frappe.db.sql("""update `tab{0}` set clearance_date = %s, modified = %s
- where name=%s""".format(d.payment_document),
+ frappe.db.sql("""update `tab{0}` set clearance_date = %s, modified = %s
+ where name=%s""".format(d.payment_document),
(d.clearance_date, nowdate(), d.payment_entry))
-
+
clearance_date_updated = True
if clearance_date_updated:
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 3a17ce5b4b..00aba41559 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -52,11 +52,6 @@ class JournalEntry(AccountsController):
self.update_loan()
self.update_inter_company_jv()
- def before_print(self):
- self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Journal Entry",
- "voucher_no": self.name} ,
- fields=["account", "party_type", "party", "debit", "credit", "remarks"]
- )
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 818b950336..b8073ce065 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -70,11 +70,6 @@ class PaymentEntry(AccountsController):
self.update_advance_paid()
self.update_expense_claim()
- def before_print(self):
- self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Payment Entry",
- "voucher_no": self.name} ,
- fields=["account", "party_type", "party", "debit", "credit", "remarks"]
- )
def on_cancel(self):
self.setup_party_account_field()
@@ -754,7 +749,7 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
- total_amount = outstanding_amount = exchange_rate = None
+ total_amount = outstanding_amount = exchange_rate = bill_no = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(ref_doc.company)
@@ -788,6 +783,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
+ bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = flt(ref_doc.get("total_sanctioned_amount")) \
- flt(ref_doc.get("total_amount+reimbursed")) - flt(ref_doc.get("total_advance_amount"))
@@ -804,7 +800,8 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"due_date": ref_doc.get("due_date"),
"total_amount": total_amount,
"outstanding_amount": outstanding_amount,
- "exchange_rate": exchange_rate
+ "exchange_rate": exchange_rate,
+ "bill_no": bill_no
})
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index e64ad28cda..4f17e9f995 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -155,7 +155,6 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
def set_default_profile(pos_profile, company):
modified = now()
user = frappe.session.user
- company = frappe.db.escape(company)
if pos_profile and company:
frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 1e11c4e163..01b935c999 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -119,7 +119,7 @@ class PricingRule(Document):
#--------------------------------------------------------------------------------
@frappe.whitelist()
-def apply_pricing_rule(args):
+def apply_pricing_rule(args, doc=None):
"""
args = {
"items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
@@ -139,6 +139,7 @@ def apply_pricing_rule(args):
"ignore_pricing_rule": "something"
}
"""
+
if isinstance(args, string_types):
args = json.loads(args)
@@ -161,10 +162,11 @@ def apply_pricing_rule(args):
for item in item_list:
args_copy = copy.deepcopy(args)
args_copy.update(item)
- data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'))
+ data = get_pricing_rule_for_item(args_copy, item.get('price_list_rate'), doc=doc)
out.append(data)
- if set_serial_nos_based_on_fifo and not args.get('is_return'):
- out.append(get_serial_no_for_item(args_copy))
+ if not item.get("serial_no") and set_serial_nos_based_on_fifo and not args.get('is_return'):
+ out[0].update(get_serial_no_for_item(args_copy))
+
return out
def get_serial_no_for_item(args):
@@ -182,6 +184,12 @@ def get_serial_no_for_item(args):
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rules
+ if isinstance(doc, string_types):
+ doc = json.loads(doc)
+
+ if doc:
+ doc = frappe.get_doc(doc)
+
if (args.get('is_free_item') or
args.get("parenttype") == "Material Request"): return {}
@@ -227,11 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
if pricing_rules:
rules = []
- item_details.discount_percentage = 0
- item_details.discount_amount = 0
for pricing_rule in pricing_rules:
if not pricing_rule or pricing_rule.get('suggestion'): continue
+ item_details.validate_applied_rule = pricing_rule.get("validate_applied_rule", 0)
+
rules.append(get_pricing_rule_details(args, pricing_rule))
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
continue
@@ -243,8 +251,8 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None):
item_details.has_pricing_rule = 1
# if discount is applied on the rate and not on price list rate
- if price_list_rate:
- set_discount_amount(price_list_rate, item_details)
+ # if price_list_rate:
+ # set_discount_amount(price_list_rate, item_details)
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules])
@@ -264,7 +272,7 @@ def get_pricing_rule_details(args, pricing_rule):
'pricing_rule': pricing_rule.name,
'rate_or_discount': pricing_rule.rate_or_discount,
'margin_type': pricing_rule.margin_type,
- 'item_code': pricing_rule.item_code,
+ 'item_code': pricing_rule.item_code or args.get("item_code"),
'child_docname': args.get('child_docname')
})
@@ -296,6 +304,9 @@ def apply_price_discount_pricing_rule(pricing_rule, item_details, args):
discount_field = "{0}_on_rate".format(field)
item_details[discount_field].append(pricing_rule.get(field, 0))
else:
+ if field not in item_details:
+ item_details.setdefault(field, 0)
+
item_details[field] += (pricing_rule.get(field, 0)
if pricing_rule else args.get(field, 0))
@@ -308,6 +319,7 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
+ from erpnext.accounts.doctype.pricing_rule.utils import get_apply_on_and_items
for d in pricing_rules.split(','):
if not d: continue
pricing_rule = frappe.get_doc('Pricing Rule', d)
@@ -327,6 +339,11 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
item_details.remove_free_item = (item_code if pricing_rule.get('same_item')
else pricing_rule.get('free_item'))
+ if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
+ apply_on, items = get_apply_on_and_items(pricing_rule, item_details)
+ item_details.apply_on = apply_on
+ item_details.applied_on_items = ','.join(items)
+
item_details.pricing_rules = ''
return item_details
@@ -368,35 +385,6 @@ def make_pricing_rule(doctype, docname):
return doc
-@frappe.whitelist()
-def get_free_items(pricing_rules, item_row):
- if isinstance(item_row, string_types):
- item_row = json.loads(item_row)
-
- free_items = []
- pricing_rules = list(set(pricing_rules.split(',')))
-
- for d in pricing_rules:
- pr_doc = frappe.get_doc('Pricing Rule', d)
- if pr_doc.price_or_product_discount == 'Product':
- item = (item_row.get('item_code') if pr_doc.same_item
- else pr_doc.free_item)
- if not item: return free_items
-
- doc = frappe.get_doc('Item', item)
-
- free_items.append({
- 'item_code': item,
- 'item_name': doc.item_name,
- 'description': doc.description,
- 'qty': pr_doc.free_qty,
- 'uom': pr_doc.free_item_uom,
- 'rate': pr_doc.free_item_rate or 0,
- 'is_free_item': 1
- })
-
- return free_items
-
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
items = [filters.get('value')]
if filters.get('apply_on') != 'Item Code':
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index e12771b8ba..d3db130f3a 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -4,490 +4,531 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe
-import json
-import copy
+import frappe, copy, json
from frappe import throw, _
+from six import string_types
from frappe.utils import flt, cint, get_datetime
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor
class MultiplePricingRuleConflict(frappe.ValidationError): pass
+apply_on_table = {
+ 'Item Code': 'items',
+ 'Item Group': 'item_groups',
+ 'Brand': 'brands'
+}
+
def get_pricing_rules(args, doc=None):
- pricing_rules = []
- values = {}
+ pricing_rules = []
+ values = {}
- for apply_on in ['Item Code', 'Item Group', 'Brand']:
- pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
- if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
- break
+ for apply_on in ['Item Code', 'Item Group', 'Brand']:
+ pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
+ if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
+ break
- rules = []
+ rules = []
- if not pricing_rules: return []
+ if not pricing_rules: return []
- if apply_multiple_pricing_rules(pricing_rules):
- for pricing_rule in pricing_rules:
- pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
- if pricing_rule:
- rules.append(pricing_rule)
- else:
- rules.append(filter_pricing_rules(args, pricing_rules, doc))
+ if apply_multiple_pricing_rules(pricing_rules):
+ for pricing_rule in pricing_rules:
+ pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
+ if pricing_rule:
+ rules.append(pricing_rule)
+ else:
+ pricing_rule = filter_pricing_rules(args, pricing_rules, doc)
+ if pricing_rule:
+ rules.append(pricing_rule)
- return rules
+ return rules
def _get_pricing_rules(apply_on, args, values):
- apply_on_field = frappe.scrub(apply_on)
+ apply_on_field = frappe.scrub(apply_on)
- if not args.get(apply_on_field): return []
+ if not args.get(apply_on_field): return []
- child_doc = '`tabPricing Rule {0}`'.format(apply_on)
+ child_doc = '`tabPricing Rule {0}`'.format(apply_on)
- conditions = item_variant_condition = item_conditions = ""
- values[apply_on_field] = args.get(apply_on_field)
- if apply_on_field in ['item_code', 'brand']:
- item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(child_doc=child_doc,
- apply_on_field = apply_on_field)
+ conditions = item_variant_condition = item_conditions = ""
+ values[apply_on_field] = args.get(apply_on_field)
+ if apply_on_field in ['item_code', 'brand']:
+ item_conditions = "{child_doc}.{apply_on_field}= %({apply_on_field})s".format(child_doc=child_doc,
+ apply_on_field = apply_on_field)
- if apply_on_field == 'item_code':
- if "variant_of" not in args:
- args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
+ if apply_on_field == 'item_code':
+ if "variant_of" not in args:
+ args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
- if args.variant_of:
- item_variant_condition = ' or {child_doc}.item_code=%(variant_of)s '.format(child_doc=child_doc)
- values['variant_of'] = args.variant_of
- elif apply_on_field == 'item_group':
- item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
+ if args.variant_of:
+ item_variant_condition = ' or {child_doc}.item_code=%(variant_of)s '.format(child_doc=child_doc)
+ values['variant_of'] = args.variant_of
+ elif apply_on_field == 'item_group':
+ item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
- conditions += get_other_conditions(conditions, values, args)
- warehouse_conditions = _get_tree_conditions(args, "Warehouse", '`tabPricing Rule`')
- if warehouse_conditions:
- warehouse_conditions = " and {0}".format(warehouse_conditions)
+ conditions += get_other_conditions(conditions, values, args)
+ warehouse_conditions = _get_tree_conditions(args, "Warehouse", '`tabPricing Rule`')
+ if warehouse_conditions:
+ warehouse_conditions = " and {0}".format(warehouse_conditions)
- if not args.price_list: args.price_list = None
+ if not args.price_list: args.price_list = None
- conditions += " and ifnull(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')"
- values["price_list"] = args.get("price_list")
+ conditions += " and ifnull(`tabPricing Rule`.for_price_list, '') in (%(price_list)s, '')"
+ values["price_list"] = args.get("price_list")
- pricing_rules = frappe.db.sql("""select `tabPricing Rule`.*,
- {child_doc}.{apply_on_field}, {child_doc}.uom
- from `tabPricing Rule`, {child_doc}
+ pricing_rules = frappe.db.sql("""select `tabPricing Rule`.*,
+ {child_doc}.{apply_on_field}, {child_doc}.uom
+ from `tabPricing Rule`, {child_doc}
where ({item_conditions} or (`tabPricing Rule`.apply_rule_on_other is not null
- and `tabPricing Rule`.{apply_on_other_field}=%({apply_on_field})s) {item_variant_condition})
- and {child_doc}.parent = `tabPricing Rule`.name
+ and `tabPricing Rule`.{apply_on_other_field}=%({apply_on_field})s) {item_variant_condition})
+ and {child_doc}.parent = `tabPricing Rule`.name
and `tabPricing Rule`.disable = 0 and
- `tabPricing Rule`.{transaction_type} = 1 {warehouse_cond} {conditions}
+ `tabPricing Rule`.{transaction_type} = 1 {warehouse_cond} {conditions}
order by `tabPricing Rule`.priority desc,
- `tabPricing Rule`.name desc""".format(
- child_doc = child_doc,
- apply_on_field = apply_on_field,
+ `tabPricing Rule`.name desc""".format(
+ child_doc = child_doc,
+ apply_on_field = apply_on_field,
item_conditions = item_conditions,
item_variant_condition = item_variant_condition,
transaction_type = args.transaction_type,
- warehouse_cond = warehouse_conditions,
- apply_on_other_field = "other_{0}".format(apply_on_field),
+ warehouse_cond = warehouse_conditions,
+ apply_on_other_field = "other_{0}".format(apply_on_field),
conditions = conditions), values, as_dict=1) or []
- return pricing_rules
+ return pricing_rules
def apply_multiple_pricing_rules(pricing_rules):
- apply_multiple_rule = [d.apply_multiple_pricing_rules
- for d in pricing_rules if d.apply_multiple_pricing_rules]
+ apply_multiple_rule = [d.apply_multiple_pricing_rules
+ for d in pricing_rules if d.apply_multiple_pricing_rules]
- if not apply_multiple_rule: return False
+ if not apply_multiple_rule: return False
- if (apply_multiple_rule
- and len(apply_multiple_rule) == len(pricing_rules)):
- return True
+ if (apply_multiple_rule
+ and len(apply_multiple_rule) == len(pricing_rules)):
+ return True
def _get_tree_conditions(args, parenttype, table, allow_blank=True):
- field = frappe.scrub(parenttype)
- condition = ""
- if args.get(field):
- if not frappe.flags.tree_conditions:
- frappe.flags.tree_conditions = {}
- key = (parenttype, args.get(field))
- if key in frappe.flags.tree_conditions:
- return frappe.flags.tree_conditions[key]
+ field = frappe.scrub(parenttype)
+ condition = ""
+ if args.get(field):
+ if not frappe.flags.tree_conditions:
+ frappe.flags.tree_conditions = {}
+ key = (parenttype, args.get(field))
+ if key in frappe.flags.tree_conditions:
+ return frappe.flags.tree_conditions[key]
- try:
- lft, rgt = frappe.db.get_value(parenttype, args.get(field), ["lft", "rgt"])
- except TypeError:
- frappe.throw(_("Invalid {0}").format(args.get(field)))
+ try:
+ lft, rgt = frappe.db.get_value(parenttype, args.get(field), ["lft", "rgt"])
+ except TypeError:
+ frappe.throw(_("Invalid {0}").format(args.get(field)))
- parent_groups = frappe.db.sql_list("""select name from `tab%s`
- where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+ parent_groups = frappe.db.sql_list("""select name from `tab%s`
+ where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
- if parent_groups:
- if allow_blank: parent_groups.append('')
- condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
- table=table,
- field=field,
- parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
- )
+ if parent_groups:
+ if allow_blank: parent_groups.append('')
+ condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
+ table=table,
+ field=field,
+ parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
+ )
- frappe.flags.tree_conditions[key] = condition
- return condition
+ frappe.flags.tree_conditions[key] = condition
+ return condition
def get_other_conditions(conditions, values, args):
- for field in ["company", "customer", "supplier", "campaign", "sales_partner"]:
- if args.get(field):
- conditions += " and ifnull(`tabPricing Rule`.{0}, '') in (%({1})s, '')".format(field, field)
- values[field] = args.get(field)
- else:
- conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field)
+ for field in ["company", "customer", "supplier", "campaign", "sales_partner"]:
+ if args.get(field):
+ conditions += " and ifnull(`tabPricing Rule`.{0}, '') in (%({1})s, '')".format(field, field)
+ values[field] = args.get(field)
+ else:
+ conditions += " and ifnull(`tabPricing Rule`.{0}, '') = ''".format(field)
- for parenttype in ["Customer Group", "Territory", "Supplier Group"]:
- group_condition = _get_tree_conditions(args, parenttype, '`tabPricing Rule`')
- if group_condition:
- conditions += " and " + group_condition
+ for parenttype in ["Customer Group", "Territory", "Supplier Group"]:
+ group_condition = _get_tree_conditions(args, parenttype, '`tabPricing Rule`')
+ if group_condition:
+ conditions += " and " + group_condition
- if args.get("transaction_date"):
- conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01')
- and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
- values['transaction_date'] = args.get('transaction_date')
+ if args.get("transaction_date"):
+ conditions += """ and %(transaction_date)s between ifnull(`tabPricing Rule`.valid_from, '2000-01-01')
+ and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')"""
+ values['transaction_date'] = args.get('transaction_date')
- return conditions
+ return conditions
def filter_pricing_rules(args, pricing_rules, doc=None):
- if not isinstance(pricing_rules, list):
- pricing_rules = [pricing_rules]
+ if not isinstance(pricing_rules, list):
+ pricing_rules = [pricing_rules]
- original_pricing_rule = copy.copy(pricing_rules)
+ original_pricing_rule = copy.copy(pricing_rules)
- # filter for qty
- if pricing_rules:
- stock_qty = flt(args.get('stock_qty'))
- amount = flt(args.get('price_list_rate')) * flt(args.get('qty'))
+ # filter for qty
+ if pricing_rules:
+ stock_qty = flt(args.get('stock_qty'))
+ amount = flt(args.get('price_list_rate')) * flt(args.get('qty'))
- if pricing_rules[0].apply_rule_on_other:
- field = frappe.scrub(pricing_rules[0].apply_rule_on_other)
+ if pricing_rules[0].apply_rule_on_other:
+ field = frappe.scrub(pricing_rules[0].apply_rule_on_other)
- if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
+ if (field and pricing_rules[0].get('other_' + field) != args.get(field)): return
- pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name)
+ pr_doc = frappe.get_doc('Pricing Rule', pricing_rules[0].name)
- if pricing_rules[0].mixed_conditions and doc:
- stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc)
+ if pricing_rules[0].mixed_conditions and doc:
+ stock_qty, amount = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
- elif pricing_rules[0].is_cumulative:
- items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
- data = get_qty_amount_data_for_cumulative(pr_doc, args, items)
+ elif pricing_rules[0].is_cumulative:
+ items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
+ data = get_qty_amount_data_for_cumulative(pr_doc, args, items)
- if data:
- stock_qty += data[0]
- amount += data[1]
+ if data:
+ stock_qty += data[0]
+ amount += data[1]
- if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc:
- pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or []
- else:
- pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args)
+ if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc:
+ pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or []
+ else:
+ pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args)
- if not pricing_rules:
- for d in original_pricing_rule:
- if not d.threshold_percentage: continue
+ if not pricing_rules:
+ for d in original_pricing_rule:
+ if not d.threshold_percentage: continue
- msg = validate_quantity_and_amount_for_suggestion(d, stock_qty,
- amount, args.get('item_code'), args.get('transaction_type'))
+ msg = validate_quantity_and_amount_for_suggestion(d, stock_qty,
+ amount, args.get('item_code'), args.get('transaction_type'))
- if msg:
- return {'suggestion': msg, 'item_code': args.get('item_code')}
+ if msg:
+ return {'suggestion': msg, 'item_code': args.get('item_code')}
- # add variant_of property in pricing rule
- for p in pricing_rules:
- if p.item_code and args.variant_of:
- p.variant_of = args.variant_of
- else:
- p.variant_of = None
+ # add variant_of property in pricing rule
+ for p in pricing_rules:
+ if p.item_code and args.variant_of:
+ p.variant_of = args.variant_of
+ else:
+ p.variant_of = None
- # find pricing rule with highest priority
- if pricing_rules:
- max_priority = max([cint(p.priority) for p in pricing_rules])
- if max_priority:
- pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
+ # find pricing rule with highest priority
+ if pricing_rules:
+ max_priority = max([cint(p.priority) for p in pricing_rules])
+ if max_priority:
+ pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
- # apply internal priority
- all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory",
- "supplier", "supplier_group", "campaign", "sales_partner", "variant_of"]
+ # apply internal priority
+ all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory",
+ "supplier", "supplier_group", "campaign", "sales_partner", "variant_of"]
- if len(pricing_rules) > 1:
- for field_set in [["item_code", "variant_of", "item_group", "brand"],
- ["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]:
- remaining_fields = list(set(all_fields) - set(field_set))
- if if_all_rules_same(pricing_rules, remaining_fields):
- pricing_rules = apply_internal_priority(pricing_rules, field_set, args)
- break
+ if len(pricing_rules) > 1:
+ for field_set in [["item_code", "variant_of", "item_group", "brand"],
+ ["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]:
+ remaining_fields = list(set(all_fields) - set(field_set))
+ if if_all_rules_same(pricing_rules, remaining_fields):
+ pricing_rules = apply_internal_priority(pricing_rules, field_set, args)
+ break
- if pricing_rules and not isinstance(pricing_rules, list):
- pricing_rules = list(pricing_rules)
+ if pricing_rules and not isinstance(pricing_rules, list):
+ pricing_rules = list(pricing_rules)
- if len(pricing_rules) > 1:
- rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
- if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
- pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \
- or pricing_rules
+ if len(pricing_rules) > 1:
+ rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
+ if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
+ pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \
+ or pricing_rules
- if len(pricing_rules) > 1 and not args.for_shopping_cart:
- frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
- .format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict)
- elif pricing_rules:
- return pricing_rules[0]
+ if len(pricing_rules) > 1 and not args.for_shopping_cart:
+ frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
+ .format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict)
+ elif pricing_rules:
+ return pricing_rules[0]
def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type):
- fieldname, msg = '', ''
- type_of_transaction = 'purcahse' if transaction_type == "buying" else "sale"
+ fieldname, msg = '', ''
+ type_of_transaction = 'purcahse' if transaction_type == "buying" else "sale"
- for field, value in {'min_qty': qty, 'min_amt': amount}.items():
- if (args.get(field) and value < args.get(field)
- and (args.get(field) - cint(args.get(field) * args.threshold_percentage * 0.01)) <= value):
- fieldname = field
+ for field, value in {'min_qty': qty, 'min_amt': amount}.items():
+ if (args.get(field) and value < args.get(field)
+ and (args.get(field) - cint(args.get(field) * args.threshold_percentage * 0.01)) <= value):
+ fieldname = field
- for field, value in {'max_qty': qty, 'max_amt': amount}.items():
- if (args.get(field) and value > args.get(field)
- and (args.get(field) + cint(args.get(field) * args.threshold_percentage * 0.01)) >= value):
- fieldname = field
+ for field, value in {'max_qty': qty, 'max_amt': amount}.items():
+ if (args.get(field) and value > args.get(field)
+ and (args.get(field) + cint(args.get(field) * args.threshold_percentage * 0.01)) >= value):
+ fieldname = field
- if fieldname:
- msg = _("""If you {0} {1} quantities of the item {2}, the scheme {3}
- will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
+ if fieldname:
+ msg = _("""If you {0} {1} quantities of the item {2}, the scheme {3}
+ will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description)
- if fieldname in ['min_amt', 'max_amt']:
- msg = _("""If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.
- """).format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
+ if fieldname in ['min_amt', 'max_amt']:
+ msg = _("""If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.
+ """).format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description)
- frappe.msgprint(msg)
+ frappe.msgprint(msg)
- return msg
+ return msg
def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None):
- rules = []
+ rules = []
- for rule in pricing_rules:
- status = False
- conversion_factor = 1
+ for rule in pricing_rules:
+ status = False
+ conversion_factor = 1
- if rule.get("uom"):
- conversion_factor = get_conversion_factor(rule.item_code, rule.uom).get("conversion_factor", 1)
+ if rule.get("uom"):
+ conversion_factor = get_conversion_factor(rule.item_code, rule.uom).get("conversion_factor", 1)
- if (flt(qty) >= (flt(rule.min_qty) * conversion_factor)
- and (flt(qty)<= (rule.max_qty * conversion_factor) if rule.max_qty else True)):
- status = True
+ if (flt(qty) >= (flt(rule.min_qty) * conversion_factor)
+ and (flt(qty)<= (rule.max_qty * conversion_factor) if rule.max_qty else True)):
+ status = True
- # if user has created item price against the transaction UOM
- if rule.get("uom") == args.get("uom"):
- conversion_factor = 1.0
+ # if user has created item price against the transaction UOM
+ if rule.get("uom") == args.get("uom"):
+ conversion_factor = 1.0
- if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
- and (flt(rate)<= (rule.max_amt * conversion_factor) if rule.max_amt else True)):
- status = True
- else:
- status = False
+ if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor)
+ and (flt(rate)<= (rule.max_amt * conversion_factor) if rule.max_amt else True)):
+ status = True
+ else:
+ status = False
- if status:
- rules.append(rule)
+ if status:
+ rules.append(rule)
- return rules
+ return rules
def if_all_rules_same(pricing_rules, fields):
- all_rules_same = True
- val = [pricing_rules[0].get(k) for k in fields]
- for p in pricing_rules[1:]:
- if val != [p.get(k) for k in fields]:
- all_rules_same = False
- break
+ all_rules_same = True
+ val = [pricing_rules[0].get(k) for k in fields]
+ for p in pricing_rules[1:]:
+ if val != [p.get(k) for k in fields]:
+ all_rules_same = False
+ break
- return all_rules_same
+ return all_rules_same
def apply_internal_priority(pricing_rules, field_set, args):
- filtered_rules = []
- for field in field_set:
- if args.get(field):
- filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules)
- if filtered_rules: break
+ filtered_rules = []
+ for field in field_set:
+ if args.get(field):
+ filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules)
+ if filtered_rules: break
- return filtered_rules or pricing_rules
+ return filtered_rules or pricing_rules
-def get_qty_and_rate_for_mixed_conditions(doc, pr_doc):
- sum_qty, sum_amt = [0, 0]
- items = get_pricing_rule_items(pr_doc) or []
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
+def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
+ sum_qty, sum_amt = [0, 0]
+ items = get_pricing_rule_items(pr_doc) or []
+ apply_on = frappe.scrub(pr_doc.get('apply_on'))
- if items and doc.get("items"):
- for row in doc.get('items'):
- if row.get(apply_on) not in items: continue
+ if items and doc.get("items"):
+ for row in doc.get('items'):
+ if row.get(apply_on) not in items: continue
- if pr_doc.mixed_conditions:
- sum_qty += row.stock_qty
- sum_amt += row.amount
+ if pr_doc.mixed_conditions:
+ amt = args.get('qty') * args.get("price_list_rate")
+ if args.get("item_code") != row.get("item_code"):
+ amt = row.get('qty') * row.get("price_list_rate")
- if pr_doc.is_cumulative:
- data = get_qty_amount_data_for_cumulative(pr_doc, doc, items)
+ sum_qty += row.get("stock_qty") or args.get("stock_qty")
+ sum_amt += amt
- if data and data[0]:
- sum_qty += data[0]
- sum_amt += data[1]
+ if pr_doc.is_cumulative:
+ data = get_qty_amount_data_for_cumulative(pr_doc, doc, items)
- return sum_qty, sum_amt
+ if data and data[0]:
+ sum_qty += data[0]
+ sum_amt += data[1]
+
+ return sum_qty, sum_amt
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
- for d in get_pricing_rule_items(pr_doc):
- for row in doc.items:
- if d == row.get(frappe.scrub(pr_doc.apply_on)):
- pricing_rules = filter_pricing_rules_for_qty_amount(row.stock_qty,
- row.amount, pricing_rules, row)
+ for d in get_pricing_rule_items(pr_doc):
+ for row in doc.items:
+ if d == row.get(frappe.scrub(pr_doc.apply_on)):
+ pricing_rules = filter_pricing_rules_for_qty_amount(row.get("stock_qty"),
+ row.get("amount"), pricing_rules, row)
- if pricing_rules and pricing_rules[0]:
- return pricing_rules
+ if pricing_rules and pricing_rules[0]:
+ return pricing_rules
def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
- sum_qty, sum_amt = [0, 0]
- doctype = doc.get('parenttype') or doc.doctype
+ sum_qty, sum_amt = [0, 0]
+ doctype = doc.get('parenttype') or doc.doctype
- date_field = ('transaction_date'
- if doc.get('transaction_date') else 'posting_date')
+ date_field = ('transaction_date'
+ if doc.get('transaction_date') else 'posting_date')
- child_doctype = '{0} Item'.format(doctype)
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
+ child_doctype = '{0} Item'.format(doctype)
+ apply_on = frappe.scrub(pr_doc.get('apply_on'))
- values = [pr_doc.valid_from, pr_doc.valid_upto]
- condition = ""
+ values = [pr_doc.valid_from, pr_doc.valid_upto]
+ condition = ""
- if pr_doc.warehouse:
- warehouses = get_child_warehouses(pr_doc.warehouse)
+ if pr_doc.warehouse:
+ warehouses = get_child_warehouses(pr_doc.warehouse)
- condition += """ and `tab{child_doc}`.warehouse in ({warehouses})
- """.format(child_doc=child_doctype, warehouses = ','.join(['%s'] * len(warehouses)))
+ condition += """ and `tab{child_doc}`.warehouse in ({warehouses})
+ """.format(child_doc=child_doctype, warehouses = ','.join(['%s'] * len(warehouses)))
- values.extend(warehouses)
+ values.extend(warehouses)
- if items:
- condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(child_doc = child_doctype,
- apply_on = apply_on, items = ','.join(['%s'] * len(items)))
+ if items:
+ condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(child_doc = child_doctype,
+ apply_on = apply_on, items = ','.join(['%s'] * len(items)))
- values.extend(items)
+ values.extend(items)
- data_set = frappe.db.sql(""" SELECT `tab{child_doc}`.stock_qty,
- `tab{child_doc}`.amount
- FROM `tab{child_doc}`, `tab{parent_doc}`
- WHERE
- `tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field}
- between %s and %s and `tab{parent_doc}`.docstatus = 1
- {condition} group by `tab{child_doc}`.name
- """.format(parent_doc = doctype,
- child_doc = child_doctype,
- condition = condition,
- date_field = date_field
- ), tuple(values), as_dict=1)
+ data_set = frappe.db.sql(""" SELECT `tab{child_doc}`.stock_qty,
+ `tab{child_doc}`.amount
+ FROM `tab{child_doc}`, `tab{parent_doc}`
+ WHERE
+ `tab{child_doc}`.parent = `tab{parent_doc}`.name and {date_field}
+ between %s and %s and `tab{parent_doc}`.docstatus = 1
+ {condition} group by `tab{child_doc}`.name
+ """.format(parent_doc = doctype,
+ child_doc = child_doctype,
+ condition = condition,
+ date_field = date_field
+ ), tuple(values), as_dict=1)
- for data in data_set:
- sum_qty += data.get('stock_qty')
- sum_amt += data.get('amount')
+ for data in data_set:
+ sum_qty += data.get('stock_qty')
+ sum_amt += data.get('amount')
- return [sum_qty, sum_amt]
+ return [sum_qty, sum_amt]
def validate_pricing_rules(doc):
- validate_pricing_rule_on_transactions(doc)
+ validate_pricing_rule_on_transactions(doc)
- if not doc.pricing_rules: return
+ for d in doc.items:
+ validate_pricing_rule_on_items(doc, d)
- for d in doc.items:
- validate_pricing_rule_on_items(doc, d)
+ doc.calculate_taxes_and_totals()
- doc.calculate_taxes_and_totals()
+def validate_pricing_rule_on_items(doc, item_row, do_not_validate = False):
+ value = 0
+ for pricing_rule in get_applied_pricing_rules(doc, item_row):
+ pr_doc = frappe.get_doc('Pricing Rule', pricing_rule)
-def validate_pricing_rule_on_items(doc, item_row):
- value = 0
- for pr_row in get_applied_pricing_rules(doc, item_row):
- pr_doc = frappe.get_doc('Pricing Rule', pr_row.pricing_rule)
+ if pr_doc.get('apply_on') == 'Transaction': continue
- if pr_doc.get('apply_on') == 'Transaction': continue
+ if pr_doc.get('price_or_product_discount') == 'Product':
+ apply_pricing_rule_for_free_items(doc, pr_doc)
+ else:
+ for field in ['discount_percentage', 'discount_amount', 'rate']:
+ if not pr_doc.get(field): continue
- if pr_doc.get('price_or_product_discount') == 'Product':
- apply_pricing_rule_for_free_items(doc, pr_doc)
- else:
- for field in ['discount_percentage', 'discount_amount', 'rate']:
- if not pr_doc.get(field): continue
-
- value += pr_doc.get(field)
- apply_pricing_rule(doc, pr_doc, pr_row, item_row, value)
+ value += pr_doc.get(field)
+ apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate)
def validate_pricing_rule_on_transactions(doc):
- conditions = "apply_on = 'Transaction'"
+ conditions = "apply_on = 'Transaction'"
- values = {}
- conditions = get_other_conditions(conditions, values, doc)
+ values = {}
+ conditions = get_other_conditions(conditions, values, doc)
- pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
- where {conditions} """.format(conditions = conditions), values, as_dict=1)
+ pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule`
+ where {conditions} """.format(conditions = conditions), values, as_dict=1)
- if pricing_rules:
- pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
- doc.total, pricing_rules)
+ if pricing_rules:
+ pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
+ doc.total, pricing_rules)
- for d in pricing_rules:
- if d.price_or_product_discount == 'Price':
- if d.apply_discount_on:
- doc.set('apply_discount_on', d.apply_discount_on)
+ for d in pricing_rules:
+ if d.price_or_product_discount == 'Price':
+ if d.apply_discount_on:
+ doc.set('apply_discount_on', d.apply_discount_on)
- for field in ['additional_discount_percentage', 'discount_amount']:
- if not d.get(field): continue
+ for field in ['additional_discount_percentage', 'discount_amount']:
+ if not d.get(field): continue
- pr_field = ('discount_percentage'
- if field == 'additional_discount_percentage' else field)
+ pr_field = ('discount_percentage'
+ if field == 'additional_discount_percentage' else field)
- if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
- frappe.msgprint(_("User has not applied rule on the invoice {0}")
- .format(doc.name))
- else:
- doc.set(field, d.get(pr_field))
- elif d.price_or_product_discount == 'Product':
- apply_pricing_rule_for_free_items(doc, d)
+ if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
+ frappe.msgprint(_("User has not applied rule on the invoice {0}")
+ .format(doc.name))
+ else:
+ doc.set(field, d.get(pr_field))
+ elif d.price_or_product_discount == 'Product':
+ apply_pricing_rule_for_free_items(doc, d)
def get_applied_pricing_rules(doc, item_row):
- return [d for d in doc.pricing_rules
- if d.child_docname == item_row.name]
+ return (item_row.get("pricing_rules").split(',')
+ if item_row.get("pricing_rules") else [])
def apply_pricing_rule_for_free_items(doc, pricing_rule):
- if pricing_rule.get('free_item'):
- items = [d.item_code for d in doc.items
- if d.item_code == (d.item_code
- if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
+ if pricing_rule.get('free_item'):
+ items = [d.item_code for d in doc.items
+ if d.item_code == (d.item_code
+ if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item]
- if not items:
- doc.append('items', {
- 'item_code': pricing_rule.get('free_item'),
- 'qty': pricing_rule.get('free_qty'),
- 'uom': pricing_rule.get('free_item_uom'),
- 'rate': pricing_rule.get('free_item_rate'),
- 'is_free_item': 1
- })
+ if not items:
+ doc.append('items', {
+ 'item_code': pricing_rule.get('free_item'),
+ 'qty': pricing_rule.get('free_qty'),
+ 'uom': pricing_rule.get('free_item_uom'),
+ 'rate': pricing_rule.get('free_item_rate'),
+ 'is_free_item': 1
+ })
- doc.set_missing_values()
+ doc.set_missing_values()
-def apply_pricing_rule(doc, pr_doc, pr_row, item_row, value):
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
- items = (get_pricing_rule_items(pr_doc)
- if pr_doc.mixed_conditions else [item_row.get(apply_on)])
+def apply_pricing_rule(doc, pr_doc, item_row, value, do_not_validate=False):
+ apply_on, items = get_apply_on_and_items(pr_doc, item_row)
- if pr_doc.apply_rule_on_other:
- apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
- items = [pr_doc.get(apply_on)]
+ rule_applied = {}
- rule_applied = 1
- if item_row.get(apply_on) in items:
- for field in ['discount_percentage', 'discount_amount', 'rate']:
- if not pr_doc.get(field): continue
+ for item in doc.get("items"):
+ if not item.pricing_rules:
+ item.pricing_rules = item_row.pricing_rules
- if not pr_doc.validate_applied_rule:
- item_row.set(field, value)
- elif item_row.get(field) < value:
- rule_applied = 0
- frappe.msgprint(_("Row {0}: user has not applied rule {1} on the item {2}")
- .format(item_row.idx, pr_doc.title, item_row.item_code))
+ if item.get(apply_on) in items:
+ for field in ['discount_percentage', 'discount_amount', 'rate']:
+ if not pr_doc.get(field): continue
- pr_row.rule_applied = rule_applied
+ key = (item.name, item.pricing_rules)
+ if not pr_doc.validate_applied_rule:
+ rule_applied[key] = 1
+ item.set(field, value)
+ elif item.get(field) < value:
+ if not do_not_validate and item.idx == item_row.idx:
+ rule_applied[key] = 0
+ frappe.msgprint(_("Row {0}: user has not applied rule {1} on the item {2}")
+ .format(item.idx, pr_doc.title, item.item_code))
+
+ if rule_applied and doc.get("pricing_rules"):
+ for d in doc.get("pricing_rules"):
+ key = (d.child_docname, d.pricing_rule)
+ if key in rule_applied:
+ d.rule_applied = 1
+
+def get_apply_on_and_items(pr_doc, item_row):
+ # for mixed or other items conditions
+ apply_on = frappe.scrub(pr_doc.get('apply_on'))
+ items = (get_pricing_rule_items(pr_doc)
+ if pr_doc.mixed_conditions else [item_row.get(apply_on)])
+
+ if pr_doc.apply_rule_on_other:
+ apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
+ items = [pr_doc.get(apply_on)]
+
+ return apply_on, items
def get_pricing_rule_items(pr_doc):
- apply_on = frappe.scrub(pr_doc.get('apply_on'))
- return [item.get(apply_on) for item in pr_doc.items] or []
\ No newline at end of file
+ apply_on = frappe.scrub(pr_doc.get('apply_on'))
+
+ pricing_rule_apply_on = apply_on_table.get(pr_doc.get('apply_on'))
+
+ return [item.get(apply_on) for item in pr_doc.get(pricing_rule_apply_on)] or []
+
+@frappe.whitelist()
+def validate_pricing_rule_for_different_cond(doc):
+ if isinstance(doc, string_types):
+ doc = json.loads(doc)
+
+ doc = frappe.get_doc(doc)
+ for d in doc.get("items"):
+ validate_pricing_rule_on_items(doc, d, True)
+
+ return doc
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index ac2ce8e6d8..6ca31e727d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -468,7 +468,7 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(
cur_frm.cscript.cost_center = function(doc, cdt, cdn){
var d = locals[cdt][cdn];
- if(d.idx == 1 && d.cost_center){
+ if(d.cost_center){
var cl = doc.items || [];
for(var i = 0; i < cl.length; i++){
if(!cl[i].cost_center) cl[i].cost_center = d.cost_center;
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 53eb73191d..896eab8a4a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -55,11 +55,6 @@ class PurchaseInvoice(BuyingController):
if not self.on_hold:
self.release_date = ''
- def before_print(self):
- self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Purchase Invoice",
- "voucher_no": self.name} ,
- fields=["account", "party_type", "party", "debit", "credit"]
- )
def invoice_is_blocked(self):
return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate()))
@@ -770,10 +765,6 @@ class PurchaseInvoice(BuyingController):
self.update_status_updater_args()
if not self.is_return:
- from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
- if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
- unlink_ref_doc_from_payment_entries(self)
-
self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index ce567eaa6d..94d0fc331b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -205,18 +205,11 @@ class SalesInvoice(SellingController):
def before_cancel(self):
self.update_time_sheet(None)
- def before_print(self):
- self.gl_entries = frappe.get_list("GL Entry",filters={"voucher_type": "Sales Invoice",
- "voucher_no": self.name} ,
- fields=["account", "party_type", "party", "debit", "credit"]
- )
def on_cancel(self):
- self.check_close_sales_order("sales_order")
+ super(SalesInvoice, self).on_cancel()
- from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
- if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
- unlink_ref_doc_from_payment_entries(self)
+ self.check_close_sales_order("sales_order")
if self.is_return and not self.update_billed_amount_in_sales_order:
# NOTE status updating bypassed for is_return
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 801d620223..bb18dfff32 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -14,8 +14,9 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_per
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname
-from erpnext.accounts.doctype.account.test_account import get_inventory_account
+from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
+from erpnext.stock.doctype.item.test_item import create_item
from six import iteritems
class TestSalesInvoice(unittest.TestCase):
def make(self):
@@ -1572,6 +1573,56 @@ class TestSalesInvoice(unittest.TestCase):
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
+ def test_deferred_revenue(self):
+ deferred_account = create_account(account_name="Deferred Revenue",
+ parent_account="Current Liabilities - _TC", company="_Test Company")
+
+ item = create_item("_Test Item for Deferred Accounting")
+ item.enable_deferred_revenue = 1
+ item.deferred_revenue_account = deferred_account
+ item.no_of_months = 12
+ item.save()
+
+ si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_submit=True)
+ si.items[0].enable_deferred_revenue = 1
+ si.items[0].service_start_date = "2019-01-10"
+ si.items[0].service_end_date = "2019-03-15"
+ si.items[0].deferred_revenue_account = deferred_account
+ si.save()
+ si.submit()
+
+ from erpnext.accounts.deferred_revenue import convert_deferred_revenue_to_income
+ convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-01-31")
+
+ expected_gle = [
+ [deferred_account, 33.85, 0.0, "2019-01-31"],
+ ["Sales - _TC", 0.0, 33.85, "2019-01-31"]
+ ]
+
+ self.check_gl_entries(si.name, expected_gle, "2019-01-10")
+
+ convert_deferred_revenue_to_income(start_date="2019-01-01", end_date="2019-03-31")
+
+ expected_gle = [
+ [deferred_account, 43.08, 0.0, "2019-02-28"],
+ ["Sales - _TC", 0.0, 43.08, "2019-02-28"],
+ [deferred_account, 23.07, 0.0, "2019-03-15"],
+ ["Sales - _TC", 0.0, 23.07, "2019-03-15"]
+ ]
+
+ self.check_gl_entries(si.name, expected_gle, "2019-01-31")
+
+ def check_gl_entries(self, voucher_no, expected_gle, posting_date):
+ gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
+ from `tabGL Entry`
+ where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
+ order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
+
+ for i, gle in enumerate(gl_entries):
+ self.assertEqual(expected_gle[i][0], gle.account)
+ self.assertEqual(expected_gle[i][1], gle.debit)
+ self.assertEqual(expected_gle[i][2], gle.credit)
+ self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
@@ -1669,4 +1720,4 @@ def get_outstanding_amount(against_voucher_type, against_voucher, account, party
if against_voucher_type == 'Purchase Invoice':
bal = bal * -1
- return bal
+ return bal
\ No newline at end of file
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
index c3274b9fb5..4550dedba9 100755
--- a/erpnext/accounts/page/pos/pos.js
+++ b/erpnext/accounts/page/pos/pos.js
@@ -333,6 +333,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
var me = this;
this.frm = {}
this.load_data(true);
+ this.frm.doc.offline_pos_name = '';
this.setup();
this.set_default_customer()
},
@@ -345,7 +346,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (load_doc) {
this.frm.doc = JSON.parse(localStorage.getItem('doc'));
- this.frm.doc.offline_pos_name = null;
}
$.each(this.meta, function (i, data) {
@@ -641,7 +641,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
me.list_customers_btn.toggleClass("view_customer");
me.pos_bill.show();
me.list_customers_btn.show();
- me.frm.doc.offline_pos_name = $(this).parents().attr('invoice-name')
+ me.frm.doc.offline_pos_name = $(this).parents().attr('invoice-name');
me.edit_record();
})
@@ -984,7 +984,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
}
if(!this.customer_doc.fields_dict.customer_pos_id.value) {
- this.customer_doc.set_value("customer_pos_id", $.now())
+ this.customer_doc.set_value("customer_pos_id", frappe.datetime.now_datetime())
}
},
@@ -1686,10 +1686,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
create_invoice: function () {
var me = this;
+ var existing_pos_list = [];
var invoice_data = {};
this.si_docs = this.get_doc_from_localstorage();
- if (this.frm.doc.offline_pos_name) {
+ if(this.si_docs) {
+ this.si_docs.forEach((row) => {
+ existing_pos_list.push(Object.keys(row));
+ });
+ }
+
+ if (this.frm.doc.offline_pos_name
+ && in_list(existing_pos_list, this.frm.doc.offline_pos_name)) {
this.update_invoice()
//to retrieve and set the default payment
invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc;
@@ -1698,8 +1706,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.frm.doc.paid_amount = this.frm.doc.net_total
this.frm.doc.outstanding_amount = 0
- } else {
- this.frm.doc.offline_pos_name = $.now();
+ } else if(!this.frm.doc.offline_pos_name) {
+ this.frm.doc.offline_pos_name = frappe.datetime.now_datetime();
this.frm.doc.posting_date = frappe.datetime.get_today();
this.frm.doc.posting_time = frappe.datetime.now_time();
this.frm.doc.pos_total_qty = this.frm.doc.qty_total;
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 686579be92..f25473e308 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -592,13 +592,17 @@ def get_party_shipping_address(doctype, name):
else:
return ''
-def get_partywise_advanced_payment_amount(party_type="Customer"):
+def get_partywise_advanced_payment_amount(party_type, posting_date = None):
+ cond = "1=1"
+ if posting_date:
+ cond = "posting_date <= '{0}'".format(posting_date)
+
data = frappe.db.sql(""" SELECT party, sum({0}) as amount
FROM `tabGL Entry`
WHERE
party_type = %s and against_voucher is null
- GROUP BY party"""
- .format(("credit") if party_type == "Customer" else "debit") , party_type)
+ and {1} GROUP BY party"""
+ .format(("credit") if party_type == "Customer" else "debit", cond) , party_type)
if data:
return frappe._dict(data)
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 8cb5ac1090..244aa8af6a 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -136,7 +136,8 @@ class AccountsReceivableSummary(ReceivablePayableReport):
partywise_total = self.get_partywise_total(party_naming_by, args)
- partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type")) or {}
+ partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type"),
+ self.filters.get("report_date")) or {}
for party, party_dict in iteritems(partywise_total):
row = [party]
@@ -144,7 +145,10 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row += [self.get_party_name(args.get("party_type"), party)]
row += [partywise_advance_amount.get(party, 0)]
- paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
+
+ paid_amt = 0
+ if party_dict.paid_amt > 0:
+ paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,
diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
index 9bf851df41..3ffb3ac1df 100644
--- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
+++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
@@ -14,13 +14,13 @@ def execute(filters=None):
def get_column():
return [
- _("Delivery Note") + ":Link/Delivery Note:120", _("Date") + ":Date:100",
+ _("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
+ _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
]
def get_args():
- return {'doctype': 'Delivery Note', 'party': 'customer',
+ return {'doctype': 'Delivery Note', 'party': 'customer',
'date': 'posting_date', 'order': 'name', 'order_by': 'desc'}
\ No newline at end of file
diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py
index 41ec9b7466..c238606443 100644
--- a/erpnext/accounts/report/non_billed_report.py
+++ b/erpnext/accounts/report/non_billed_report.py
@@ -12,14 +12,14 @@ def get_ordered_to_be_billed_data(args):
child_tab = doctype + " Item"
precision = get_field_precision(frappe.get_meta(child_tab).get_field("billed_amt"),
currency=get_default_currency()) or 2
-
+
project_field = get_project_field(doctype, party)
return frappe.db.sql("""
Select
- `{parent_tab}`.name, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
+ `{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
- (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
+ (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
(`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
from
diff --git a/erpnext/accounts/report/ordered_items_to_be_billed/ordered_items_to_be_billed.py b/erpnext/accounts/report/ordered_items_to_be_billed/ordered_items_to_be_billed.py
index 5765266f1c..ec0d2f39f3 100644
--- a/erpnext/accounts/report/ordered_items_to_be_billed/ordered_items_to_be_billed.py
+++ b/erpnext/accounts/report/ordered_items_to_be_billed/ordered_items_to_be_billed.py
@@ -14,13 +14,13 @@ def execute(filters=None):
def get_column():
return [
- _("Sales Order") + ":Link/Sales Order:120", _("Date") + ":Date:100",
+ _("Sales Order") + ":Link/Sales Order:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
+ _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
]
def get_args():
- return {'doctype': 'Sales Order', 'party': 'customer',
+ return {'doctype': 'Sales Order', 'party': 'customer',
'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'}
\ No newline at end of file
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 39706acde0..a0d8c5f0e4 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -133,6 +133,13 @@ def get_columns(filters):
"options": filters.get("based_on"),
"width": 300
},
+ {
+ "fieldname": "currency",
+ "label": _("Currency"),
+ "fieldtype": "Link",
+ "options": "Currency",
+ "hidden": 1
+ },
{
"fieldname": "income",
"label": _("Income"),
@@ -153,13 +160,6 @@ def get_columns(filters):
"fieldtype": "Currency",
"options": "currency",
"width": 120
- },
- {
- "fieldname": "currency",
- "label": _("Currency"),
- "fieldtype": "Link",
- "options": "Currency",
- "hidden": 1
}
]
@@ -191,4 +191,4 @@ def set_gl_entries_by_account(company, from_date, to_date, based_on, gl_entries_
for entry in gl_entries:
gl_entries_by_account.setdefault(entry.based_on, []).append(entry)
- return gl_entries_by_account
\ No newline at end of file
+ return gl_entries_by_account
diff --git a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py
index 5aed93d348..99d0a36813 100644
--- a/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py
+++ b/erpnext/accounts/report/purchase_order_items_to_be_billed/purchase_order_items_to_be_billed.py
@@ -14,13 +14,13 @@ def execute(filters=None):
def get_column():
return [
- _("Purchase Order") + ":Link/Purchase Order:120", _("Date") + ":Date:100",
+ _("Purchase Order") + ":Link/Purchase Order:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Suplier") + ":Link/Supplier:120", _("Suplier Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
+ _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
]
def get_args():
- return {'doctype': 'Purchase Order', 'party': 'supplier',
+ return {'doctype': 'Purchase Order', 'party': 'supplier',
'date': 'transaction_date', 'order': 'transaction_date', 'order_by': 'asc'}
diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
index 73e8b25a3a..5e8d7730b7 100644
--- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
+++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
@@ -14,7 +14,7 @@ def execute(filters=None):
def get_column():
return [
- _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Date") + ":Date:100",
+ _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index b2c1de57f2..cf4ec49a78 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -25,9 +25,6 @@ frappe.ui.form.on("Purchase Order", {
frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
- frm.set_indicator_formatter('pricing_rule',
- function(doc) { return (doc.rule_applied) ? "green" : "red" })
-
frm.set_query("blanket_order", "items", function() {
return {
filters: {
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index f1507364df..774156eda4 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -465,6 +465,33 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(se_items, supplied_items)
update_backflush_based_on("BOM")
+ def test_advance_payment_entry_unlink_against_purchase_order(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+ frappe.db.set_value("Accounts Settings", "Accounts Settings",
+ "unlink_advance_payment_on_cancelation_of_order", 1)
+
+ po_doc = create_purchase_order()
+
+ pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC")
+ pe.reference_no = "1"
+ pe.reference_date = nowdate()
+ pe.paid_from_account_currency = po_doc.currency
+ pe.paid_to_account_currency = po_doc.currency
+ pe.source_exchange_rate = 1
+ pe.target_exchange_rate = 1
+ pe.paid_amount = po_doc.grand_total
+ pe.save(ignore_permissions=True)
+ pe.submit()
+
+ po_doc = frappe.get_doc('Purchase Order', po_doc.name)
+ po_doc.cancel()
+
+ pe_doc = frappe.get_doc('Payment Entry', pe.name)
+ pe_doc.cancel()
+
+ frappe.db.set_value("Accounts Settings", "Accounts Settings",
+ "unlink_advance_payment_on_cancelation_of_order", 0)
+
def make_subcontracted_item(item_code):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py
index 6dc95b242c..8ce096499d 100644
--- a/erpnext/config/desktop.py
+++ b/erpnext/config/desktop.py
@@ -81,9 +81,9 @@ def get_data():
"description": "Sales pipeline, leads, opportunities and customers."
},
{
- "module_name": "Help Desk",
+ "module_name": "Support",
"category": "Modules",
- "label": _("Help Desk"),
+ "label": _("Support"),
"color": "#1abc9c",
"icon": "fa fa-check-square-o",
"type": "module",
diff --git a/erpnext/config/help_desk.py b/erpnext/config/help_desk.py
deleted file mode 100644
index c19dd423e6..0000000000
--- a/erpnext/config/help_desk.py
+++ /dev/null
@@ -1,82 +0,0 @@
-from __future__ import unicode_literals
-from frappe import _
-
-def get_data():
- return [
- {
- "label": _("Issues"),
- "items": [
- {
- "type": "doctype",
- "name": "Issue",
- "description": _("Support queries from customers."),
- "onboard": 1,
- },
- {
- "type": "doctype",
- "name": "Communication",
- "description": _("Communication log."),
- "onboard": 1,
- },
- ]
- },
- {
- "label": _("Warranty"),
- "items": [
- {
- "type": "doctype",
- "name": "Warranty Claim",
- "description": _("Warranty Claim against Serial No."),
- },
- {
- "type": "doctype",
- "name": "Serial No",
- "description": _("Single unit of an Item."),
- },
- ]
- },
- {
- "label": _("Service Level Agreement"),
- "items": [
- {
- "type": "doctype",
- "name": "Employee Group",
- "description": _("Support Team."),
- },
- {
- "type": "doctype",
- "name": "Service Level",
- "description": _("Service Level."),
- },
- {
- "type": "doctype",
- "name": "Service Level Agreement",
- "description": _("Service Level Agreement."),
- }
- ]
- },
- {
- "label": _("Reports"),
- "icon": "fa fa-list",
- "items": [
- {
- "type": "page",
- "name": "support-analytics",
- "label": _("Support Analytics"),
- "icon": "fa fa-bar-chart"
- },
- {
- "type": "report",
- "name": "Minutes to First Response for Issues",
- "doctype": "Issue",
- "is_query_report": True
- },
- {
- "type": "report",
- "name": "Support Hours",
- "doctype": "Issue",
- "is_query_report": True
- },
- ]
- },
- ]
\ No newline at end of file
diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py
index f517a5e7c0..0f009b826f 100644
--- a/erpnext/config/hr.py
+++ b/erpnext/config/hr.py
@@ -287,6 +287,11 @@ def get_data():
"name": "Employee Advance",
"dependencies": ["Employee"]
},
+ {
+ "type": "doctype",
+ "name": "Expense Claim",
+ "dependencies": ["Employee"]
+ },
{
"type": "doctype",
"name": "Loan Type",
@@ -296,6 +301,10 @@ def get_data():
"name": "Loan Application",
"dependencies": ["Employee"]
},
+ {
+ "type": "doctype",
+ "name": "Loan"
+ }
]
},
{
diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py
index 4a65af36a2..400f6be826 100644
--- a/erpnext/config/selling.py
+++ b/erpnext/config/selling.py
@@ -27,6 +27,13 @@ def get_data():
"onboard": 1,
"dependencies": ["Item", "Customer"],
},
+ {
+ "type": "doctype",
+ "name": "Sales Invoice",
+ "description": _("Invoices for Costumers."),
+ "onboard": 1,
+ "dependencies": ["Item", "Customer"],
+ },
{
"type": "doctype",
"name": "Sales Partner",
diff --git a/erpnext/config/support.py b/erpnext/config/support.py
index 3980b4210a..c19dd423e6 100644
--- a/erpnext/config/support.py
+++ b/erpnext/config/support.py
@@ -10,11 +10,13 @@ def get_data():
"type": "doctype",
"name": "Issue",
"description": _("Support queries from customers."),
+ "onboard": 1,
},
{
"type": "doctype",
"name": "Communication",
"description": _("Communication log."),
+ "onboard": 1,
},
]
},
@@ -33,6 +35,26 @@ def get_data():
},
]
},
+ {
+ "label": _("Service Level Agreement"),
+ "items": [
+ {
+ "type": "doctype",
+ "name": "Employee Group",
+ "description": _("Support Team."),
+ },
+ {
+ "type": "doctype",
+ "name": "Service Level",
+ "description": _("Service Level."),
+ },
+ {
+ "type": "doctype",
+ "name": "Service Level Agreement",
+ "description": _("Service Level Agreement."),
+ }
+ ]
+ },
{
"label": _("Reports"),
"icon": "fa fa-list",
@@ -57,24 +79,4 @@ def get_data():
},
]
},
- {
- "label": _("Service Level Agreement"),
- "items": [
- {
- "type": "doctype",
- "name": "Employee Group",
- "description": _("Support Team."),
- },
- {
- "type": "doctype",
- "name": "Service Level",
- "description": _("Service Level."),
- },
- {
- "type": "doctype",
- "name": "Service Level Agreement",
- "description": _("Service Level Agreement."),
- }
- ]
- },
- ]
+ ]
\ No newline at end of file
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index a4c6628ecd..7bb71ec9b5 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -119,6 +119,12 @@ class AccountsController(TransactionBase):
self.validate_non_invoice_documents_schedule()
def before_print(self):
+ if self.doctype in ['Journal Entry', 'Payment Entry', 'Sales Invoice', 'Purchase Invoice']:
+ self.gl_entries = frappe.get_list("GL Entry", filters={
+ "voucher_type": self.doctype,
+ "voucher_no": self.name
+ }, fields=["account", "party_type", "party", "debit", "credit", "remarks"])
+
if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
if self.get("group_same_items"):
@@ -276,7 +282,7 @@ class AccountsController(TransactionBase):
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
- if ret.get("pricing_rules"):
+ if ret.get("pricing_rules") and not ret.get("validate_applied_rule", 0):
# if user changed the discount percentage then set user's discount percentage ?
item.set("pricing_rules", ret.get("pricing_rules"))
item.set("discount_percentage", ret.get("discount_percentage"))
@@ -546,6 +552,19 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst)
+ def on_cancel(self):
+ from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
+
+ if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
+ if self.is_return: return
+
+ if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
+ unlink_ref_doc_from_payment_entries(self)
+
+ elif self.doctype in ["Sales Order", "Purchase Order"]:
+ if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'):
+ unlink_ref_doc_from_payment_entries(self)
+
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_tolerance_for
item_tolerance = {}
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index a1dfffe611..2dd1e61a5d 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -531,6 +531,8 @@ class BuyingController(StockController):
update_last_purchase_rate(self, is_submit = 1)
def on_cancel(self):
+ super(BuyingController, self).on_cancel()
+
if self.get('is_return'):
return
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 6151be0660..ecc961186a 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -153,7 +153,6 @@ standard_portal_menu_items = [
{"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role":"Customer"},
{"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"},
{"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"},
- {"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"},
{"title": _("Lab Test"), "route": "/lab-test", "reference_doctype": "Lab Test", "role":"Patient"},
{"title": _("Prescription"), "route": "/prescription", "reference_doctype": "Patient Encounter", "role":"Patient"},
{"title": _("Patient Appointment"), "route": "/patient-appointments", "reference_doctype": "Patient Appointment", "role":"Patient"},
@@ -246,8 +245,7 @@ scheduler_events = {
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
"erpnext.projects.doctype.project.project.hourly_reminder",
- "erpnext.projects.doctype.project.project.collect_project_status",
- "erpnext.support.doctype.issue.issue.update_support_timer",
+ "erpnext.projects.doctype.project.project.collect_project_status"
],
"daily": [
"erpnext.stock.reorder_item.reorder_item",
@@ -338,4 +336,4 @@ user_privacy_documents = [
'match_field': 'contact_email',
'personal_fields': ['contact_mobile', 'contact_display', 'customer_name'],
}
-]
\ No newline at end of file
+]
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index b50b534275..7c47d0e4c9 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -400,6 +400,19 @@ def get_leave_balance_on(employee, leave_type, date, allocation_records=None, do
return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed))
+def get_total_allocated_leaves(employee, leave_type, date):
+ filters= {
+ 'from_date': ['<=', date],
+ 'to_date': ['>=', date],
+ 'docstatus': 1,
+ 'leave_type': leave_type,
+ 'employee': employee
+ }
+
+ leave_allocation_records = frappe.db.get_all('Leave Allocation', filters=filters, fields=['total_leaves_allocated'])
+
+ return flt(leave_allocation_records[0]['total_leaves_allocated']) if leave_allocation_records else flt(0)
+
def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None):
leave_applications = frappe.db.sql("""
select name, employee, leave_type, from_date, to_date, total_leave_days
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 3fd266b4bb..eb7cb113fc 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -441,7 +441,7 @@ class SalarySlip(TransactionBase):
def calculate_net_pay(self):
if self.salary_structure:
self.calculate_component_amounts()
-
+
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
precision = frappe.defaults.get_global_default("currency_precision")
self.total_deduction = 0
@@ -452,10 +452,13 @@ class SalarySlip(TransactionBase):
self.set_loan_repayment()
- self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
+ self.net_pay = 0
+ if self.total_working_days:
+ self.net_pay = (flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))) * flt(self.payment_days / self.total_working_days)
+
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
-
+
if self.net_pay < 0:
frappe.throw(_("Net Pay cannnot be negative"))
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index ed44d639f5..95cb30b791 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -5,21 +5,21 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application \
- import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period
+ import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period, get_total_allocated_leaves
def execute(filters=None):
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
-
+
columns = get_columns(leave_types)
data = get_data(filters, leave_types)
-
+
return columns, data
-
+
def get_columns(leave_types):
columns = [
- _("Employee") + ":Link/Employee:150",
- _("Employee Name") + "::200",
+ _("Employee") + ":Link/Employee:150",
+ _("Employee Name") + "::200",
_("Department") +"::150"
]
@@ -27,18 +27,18 @@ def get_columns(leave_types):
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
-
+
return columns
-
+
def get_data(filters, leave_types):
user = frappe.session.user
allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date)
- active_employees = frappe.get_all("Employee",
- filters = { "status": "Active", "company": filters.company},
+ active_employees = frappe.get_all("Employee",
+ filters = { "status": "Active", "company": filters.company},
fields = ["name", "employee_name", "department", "user_id"])
-
+
data = []
for employee in active_employees:
leave_approvers = get_approvers(employee.department)
@@ -51,8 +51,7 @@ def get_data(filters, leave_types):
filters.from_date, filters.to_date)
# opening balance
- opening = get_leave_balance_on(employee.name, leave_type, filters.from_date,
- allocation_records_based_on_from_date.get(employee.name, frappe._dict()))
+ opening = get_total_allocated_leaves(employee.name, leave_type, filters.to_date)
# closing balance
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date,
@@ -61,7 +60,7 @@ def get_data(filters, leave_types):
row += [opening, leaves_taken, closing]
data.append(row)
-
+
return data
def get_approvers(department):
diff --git a/erpnext/hr/report/salary_register/salary_register.py b/erpnext/hr/report/salary_register/salary_register.py
index 0c421502f1..586ca67273 100644
--- a/erpnext/hr/report/salary_register/salary_register.py
+++ b/erpnext/hr/report/salary_register/salary_register.py
@@ -14,11 +14,11 @@ def execute(filters=None):
columns, earning_types, ded_types = get_columns(salary_slips)
ss_earning_map = get_ss_earning_map(salary_slips)
ss_ded_map = get_ss_ded_map(salary_slips)
-
+ doj_map = get_employee_doj_map()
data = []
for ss in salary_slips:
- row = [ss.name, ss.employee, ss.employee_name, ss.branch, ss.department, ss.designation,
+ row = [ss.name, ss.employee, ss.employee_name, doj_map.get(ss.employee), ss.branch, ss.department, ss.designation,
ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days]
if not ss.branch == None:columns[3] = columns[3].replace('-1','120')
@@ -44,17 +44,17 @@ def execute(filters=None):
def get_columns(salary_slips):
"""
columns = [
- _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140", _("Branch") + ":Link/Branch:120",
- _("Department") + ":Link/Department:120", _("Designation") + ":Link/Designation:120",
- _("Company") + ":Link/Company:120", _("Start Date") + "::80", _("End Date") + "::80", _("Leave Without Pay") + ":Float:130",
- _("Payment Days") + ":Float:120"
+ _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
+ _("Date of Joining") + "::80", _("Branch") + ":Link/Branch:120", _("Department") + ":Link/Department:120",
+ _("Designation") + ":Link/Designation:120", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
+ _("End Date") + "::80", _("Leave Without Pay") + ":Float:130", _("Payment Days") + ":Float:120"
]
"""
columns = [
- _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140", _("Branch") + ":Link/Branch:-1",
- _("Department") + ":Link/Department:-1", _("Designation") + ":Link/Designation:-1",
- _("Company") + ":Link/Company:120", _("Start Date") + "::80", _("End Date") + "::80", _("Leave Without Pay") + ":Float:-1",
- _("Payment Days") + ":Float:120"
+ _("Salary Slip ID") + ":Link/Salary Slip:150",_("Employee") + ":Link/Employee:120", _("Employee Name") + "::140",
+ _("Date of Joining") + "::80", _("Branch") + ":Link/Branch:-1", _("Department") + ":Link/Department:-1",
+ _("Designation") + ":Link/Designation:-1", _("Company") + ":Link/Company:120", _("Start Date") + "::80",
+ _("End Date") + "::80", _("Leave Without Pay") + ":Float:-1", _("Payment Days") + ":Float:120"
]
salary_components = {_("Earning"): [], _("Deduction"): []}
@@ -93,6 +93,16 @@ def get_conditions(filters):
return conditions, filters
+def get_employee_doj_map():
+ return frappe._dict(frappe.db.sql("""
+ SELECT
+ employee,
+ date_of_joining
+ FROM `tabEmployee`
+ WHERE
+ `status`='Active'
+ """))
+
def get_ss_earning_map(salary_slips):
ss_earnings = frappe.db.sql("""select parent, salary_component, amount
from `tabSalary Detail` where parent in (%s)""" %
@@ -115,4 +125,4 @@ def get_ss_ded_map(salary_slips):
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, [])
ss_ded_map[d.parent][d.salary_component] = flt(d.amount)
- return ss_ded_map
+ return ss_ded_map
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d882d229b5..7bdde548e2 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -586,10 +586,12 @@ execute:frappe.delete_doc('DocType', 'Notification Control')
erpnext.patches.v12_0.set_gst_category
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
erpnext.patches.v12_0.set_task_status
-erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019
+erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
erpnext.patches.v12_0.add_item_name_in_work_orders
erpnext.patches.v12_0.update_pricing_rule_fields
erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v12_0.rename_pricing_rule_child_doctypes
erpnext.patches.v12_0.move_target_distribution_from_parent_to_child
erpnext.patches.v12_0.stock_entry_enhancements
+erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
+erpnext.patches.v12_0.move_item_tax_to_item_tax_template
diff --git a/erpnext/patches/v11_0/make_italian_localization_fields.py b/erpnext/patches/v11_0/make_italian_localization_fields.py
index d9a7b35e77..29d25c9a2d 100644
--- a/erpnext/patches/v11_0/make_italian_localization_fields.py
+++ b/erpnext/patches/v11_0/make_italian_localization_fields.py
@@ -6,7 +6,6 @@ from erpnext.regional.italy.setup import make_custom_fields, setup_report
from erpnext.regional.italy import state_codes
import frappe
-
def execute():
company = frappe.get_all('Company', filters = {'country': 'Italy'})
if not company:
@@ -27,4 +26,12 @@ def execute():
frappe.db.sql("""
UPDATE tabAddress set {condition} country_code = UPPER(ifnull((select code
from `tabCountry` where name = `tabAddress`.country), ''))
+ where country_code is null and state_code is null
""".format(condition=condition))
+
+ frappe.db.sql("""
+ UPDATE `tabSales Invoice Item` si, `tabSales Order` so
+ set si.customer_po_no = so.po_no, si.customer_po_date = so.po_date
+ WHERE
+ si.sales_order = so.name and so.po_no is not null
+ """)
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index 68db59193c..575ebebadb 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -3,6 +3,8 @@ import json
from six import iteritems
def execute():
+ if "tax_type" not in frappe.db.get_table_columns("Item Tax"):
+ return
old_item_taxes = {}
item_tax_templates = {}
rename_template_to_untitled = []
@@ -40,7 +42,7 @@ def execute():
item.set("taxes", [])
item.append("taxes", {"item_tax_template": item_tax_template_name, "tax_category": ""})
item.save()
-
+
doctypes = [
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice',
'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 845d3a02c4..2686e58294 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -165,6 +165,13 @@ class Task(NestedSet):
self.update_nsm_model()
+ def update_status(self):
+ if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
+ from datetime import datetime
+ if self.exp_end_date < datetime.now().date():
+ self.db_set('status', 'Overdue')
+ self.update_project()
+
@frappe.whitelist()
def check_if_child_exists(name):
child_tasks = frappe.get_all("Task", filters={"parent_task": name})
@@ -196,10 +203,9 @@ def set_multiple_status(names, status):
task.save()
def set_tasks_as_overdue():
- frappe.db.sql("""update tabTask set `status`='Overdue'
- where exp_end_date is not null
- and exp_end_date < CURDATE()
- and `status` not in ('Completed', 'Cancelled')""")
+ tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Completed']]})
+ for task in tasks:
+ frappe.get_doc("Task", task.name).update_status()
@frappe.whitelist()
def get_children(doctype, parent, task=None, project=None, is_root=False):
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 375731517d..101d903bed 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -10,8 +10,8 @@ frappe.ui.form.on("Timesheet", {
filters:{
'status': 'Active'
}
- }
- }
+ };
+ };
frm.fields_dict['time_logs'].grid.get_field('task').get_query = function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
@@ -20,22 +20,26 @@ frappe.ui.form.on("Timesheet", {
'project': child.project,
'status': ["!=", "Cancelled"]
}
- }
- }
+ };
+ };
frm.fields_dict['time_logs'].grid.get_field('project').get_query = function() {
return{
filters: {
'company': frm.doc.company
}
- }
- }
+ };
+ };
},
onload: function(frm){
if (frm.doc.__islocal && frm.doc.time_logs) {
calculate_time_and_amount(frm);
}
+
+ if (frm.is_new()) {
+ set_employee_and_company(frm);
+ }
},
refresh: function(frm) {
@@ -58,7 +62,7 @@ frappe.ui.form.on("Timesheet", {
if ((row.from_time <= frappe.datetime.now_datetime()) && !row.completed) {
button = 'Resume Timer';
}
- })
+ });
frm.add_custom_button(__(button), function() {
var flag = true;
@@ -77,7 +81,7 @@ frappe.ui.form.on("Timesheet", {
erpnext.timesheet.timer(frm, row, timestamp);
flag = false;
}
- })
+ });
// If no activities found to start a timer, create new
if (flag) {
erpnext.timesheet.timer(frm);
@@ -94,7 +98,7 @@ frappe.ui.form.on("Timesheet", {
frappe.db.get_value('Company', { 'company_name' : frm.doc.company }, 'standard_working_hours')
.then(({ message }) => {
(frappe.working_hours = message.standard_working_hours || 0);
- });
+ });
},
make_invoice: function(frm) {
@@ -125,8 +129,8 @@ frappe.ui.form.on("Timesheet", {
frappe.set_route("Form", r.message.doctype, r.message.name);
}
}
- })
- })
+ });
+ });
dialog.show();
},
@@ -136,7 +140,7 @@ frappe.ui.form.on("Timesheet", {
frm: frm
});
},
-})
+});
frappe.ui.form.on("Timesheet Detail", {
time_logs_remove: function(frm) {
@@ -171,22 +175,22 @@ frappe.ui.form.on("Timesheet Detail", {
.find('[data-fieldname="timer"]')
.append(frappe.render_template("timesheet"));
frm.trigger("control_timer");
- })
+ });
},
hours: function(frm, cdt, cdn) {
- calculate_end_time(frm, cdt, cdn)
+ calculate_end_time(frm, cdt, cdn);
},
billing_hours: function(frm, cdt, cdn) {
- calculate_billing_costing_amount(frm, cdt, cdn)
+ calculate_billing_costing_amount(frm, cdt, cdn);
},
billing_rate: function(frm, cdt, cdn) {
- calculate_billing_costing_amount(frm, cdt, cdn)
+ calculate_billing_costing_amount(frm, cdt, cdn);
},
costing_rate: function(frm, cdt, cdn) {
- calculate_billing_costing_amount(frm, cdt, cdn)
+ calculate_billing_costing_amount(frm, cdt, cdn);
},
billable: function(frm, cdt, cdn) {
@@ -212,7 +216,7 @@ frappe.ui.form.on("Timesheet Detail", {
calculate_billing_costing_amount(frm, cdt, cdn);
}
}
- })
+ });
}
});
@@ -240,23 +244,23 @@ var calculate_end_time = function(frm, cdt, cdn) {
frm._setting_hours = true;
frappe.model.set_value(cdt, cdn, "to_time",
d.format(frappe.defaultDatetimeFormat)).then(() => {
- frm._setting_hours = false;
- });
+ frm._setting_hours = false;
+ });
}
}
-}
+};
var update_billing_hours = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
if(!child.billable) frappe.model.set_value(cdt, cdn, 'billing_hours', 0.0);
-}
+};
var update_time_rates = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
if(!child.billable){
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
}
-}
+};
var calculate_billing_costing_amount = function(frm, cdt, cdn){
var child = locals[cdt][cdn];
@@ -270,7 +274,7 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount);
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
calculate_time_and_amount(frm);
-}
+};
var calculate_time_and_amount = function(frm) {
var tl = frm.doc.time_logs || [];
@@ -294,4 +298,17 @@ var calculate_time_and_amount = function(frm) {
frm.set_value("total_hours", total_working_hr);
frm.set_value("total_billable_amount", total_billable_amount);
frm.set_value("total_costing_amount", total_costing_amount);
-}
\ No newline at end of file
+};
+
+// set employee (and company) to the one that's currently logged in
+const set_employee_and_company = function(frm) {
+ const options = { user_id: frappe.session.user };
+ const fields = ['name', 'company'];
+ frappe.db.get_value('Employee', options, fields).then(({ message }) => {
+ if (message) {
+ // there is an employee with the currently logged in user_id
+ frm.set_value("employee", message.name);
+ frm.set_value("company", message.company);
+ }
+ });
+};
diff --git a/erpnext/public/.gitignore b/erpnext/public/.gitignore
new file mode 100644
index 0000000000..3c3629e647
--- /dev/null
+++ b/erpnext/public/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index f64791325e..1bfcd6f47c 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -158,6 +158,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
};
});
}
+
+ if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) {
+ this.frm.set_indicator_formatter('pricing_rule', function(doc) {
+ return (doc.rule_applied) ? "green" : "red";
+ });
+ }
},
onload: function() {
var me = this;
@@ -422,6 +428,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
method: "erpnext.stock.get_item_details.get_item_details",
child: item,
args: {
+ doc: me.frm.doc,
args: {
item_code: item.item_code,
barcode: item.barcode,
@@ -456,7 +463,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
cost_center: item.cost_center,
tax_category: me.frm.doc.tax_category,
item_tax_template: item.item_tax_template,
- child_docname: item.name
+ child_docname: item.name,
}
},
@@ -482,7 +489,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}
},
() => me.conversion_factor(doc, cdt, cdn, true),
- () => me.update_free_items(item)
+ () => me.validate_pricing_rule(item)
]);
}
}
@@ -1116,7 +1123,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
callback: function(r) {
if (!r.exc && r.message) {
r.message.forEach(row_item => {
- me.update_free_items(row_item);
+ me.validate_pricing_rule(row_item);
});
me._set_values_for_item_list(r.message);
me.calculate_taxes_and_totals();
@@ -1139,14 +1146,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return this.frm.call({
method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.apply_pricing_rule",
- args: { args: args },
+ args: { args: args, doc: me.frm.doc },
callback: function(r) {
if (!r.exc && r.message) {
me._set_values_for_item_list(r.message);
if(item) me.set_gross_profit(item);
- if(calculate_taxes_and_totals) me.calculate_taxes_and_totals();
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
- me.update_free_items(item);
}
}
});
@@ -1200,7 +1205,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"warehouse": d.warehouse,
"serial_no": d.serial_no,
"price_list_rate": d.price_list_rate,
- "discount_percentage": d.discount_percentage || 0.0,
"conversion_factor": d.conversion_factor || 1.0
});
@@ -1241,6 +1245,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
// if pricing rule set as blank from an existing value, apply price_list
if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) {
me.apply_price_list(frappe.get_doc(d.doctype, d.name));
+ } else {
+ me.validate_pricing_rule(frappe.get_doc(d.doctype, d.name));
}
}
@@ -1288,41 +1294,29 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
});
},
- update_free_items: function(item) {
- var me = this;
+ validate_pricing_rule: function(item) {
+ let me = this;
+ const fields = ["discount_percentage", "discount_amount", "pricing_rules"];
if (item.pricing_rules) {
frappe.call({
- method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.get_free_items",
+ method: "erpnext.accounts.doctype.pricing_rule.utils.validate_pricing_rule_for_different_cond",
args: {
- pricing_rules: item.pricing_rules,
- item_row: item
+ doc: me.frm.doc
},
callback: function(r) {
- let items = [];
- let child = '';
-
- me.frm.doc.items.map(d => {
- items[d.item_code] = d;
- });
-
- if(r.message && r.message.length) {
- r.message.forEach(d => {
- // If free item is already exists
-
- if(d.item_code in items &&
- d.is_free_item && items[d.item_code].is_free_item) {
- child = items[d.item_code];
- } else {
- child = frappe.model.add_child(me.frm.doc, item.doctype, "items");
- }
-
- $.each(d, function(k, v) {
- child[k] = v;
+ if (r.message) {
+ r.message.items.forEach(d => {
+ me.frm.doc.items.forEach(row => {
+ if(d.name == row.name) {
+ fields.forEach(f => {
+ row[f] = d[f];
+ });
+ }
});
-
- me.frm.script_manager.trigger("price_list_rate", child.doctype, child.name);
});
+
+ me.trigger_price_list_rate();
}
}
});
@@ -1337,9 +1331,29 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.frm.doc.items = items;
refresh_field('items');
+ } else if(item.applied_on_items && item.apply_on) {
+ const applied_on_items = item.applied_on_items.split(',');
+ me.frm.doc.items.forEach(row => {
+ if(applied_on_items.includes(row[item.apply_on])) {
+ fields.forEach(f => {
+ row[f] = 0;
+ });
+ }
+ });
+
+ me.trigger_price_list_rate();
}
},
+ trigger_price_list_rate: function() {
+ var me = this;
+
+ this.frm.doc.items.forEach(child_row => {
+ me.frm.script_manager.trigger("price_list_rate",
+ child_row.doctype, child_row.name);
+ })
+ },
+
validate_company_and_party: function() {
var me = this;
var valid = true;
@@ -1379,7 +1393,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
callback: function(r) {
if(!r.exc) {
- me.frm.set_value("taxes", r.message);
+ for (let tax of r.message) {
+ me.frm.add_child("taxes", tax);
+ }
me.calculate_taxes_and_totals();
}
}
diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue
index cc0998212a..942c1ebdb3 100644
--- a/erpnext/public/js/hub/components/DetailView.vue
+++ b/erpnext/public/js/hub/components/DetailView.vue
@@ -31,7 +31,7 @@