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 @@ -
+

{{ title }}

diff --git a/erpnext/public/node_modules b/erpnext/public/node_modules deleted file mode 120000 index 20995099c3..0000000000 --- a/erpnext/public/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/shivammishra/Projects/ERPNext/yet-another-bench/apps/erpnext/node_modules \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index 2e9f53685e..d50eaebefc 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.utils import getdate from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.doctype.item.test_item import make_item @@ -12,10 +13,27 @@ import json class TestGSTR3BReport(unittest.TestCase): def test_gstr_3b_report(self): + + month_number_mapping = { + 1: "January", + 2: "February", + 3: "March", + 4: "April", + 5: "May", + 6: "June", + 7: "July", + 8: "August", + 9: "September", + 10: "October", + 11: "November", + 12: "December" + } + frappe.set_user("Administrator") frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") + frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'") make_company() make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) @@ -33,8 +51,8 @@ class TestGSTR3BReport(unittest.TestCase): "doctype": "GSTR 3B Report", "company": "_Test Company GST", "company_address": "_Test Address-Billing", - "year": "2019", - "month": "March" + "year": getdate().year, + "month": month_number_mapping.get(getdate().month) }).insert() output = json.loads(report.json_output) @@ -55,7 +73,6 @@ def make_sales_invoice(): income_account = 'Sales - _GST', expense_account = 'Cost of Goods Sold - _GST', cost_center = 'Main - _GST', - posting_date = '2019-03-10', do_not_save=1 ) @@ -77,7 +94,6 @@ def make_sales_invoice(): income_account = 'Sales - _GST', expense_account = 'Cost of Goods Sold - _GST', cost_center = 'Main - _GST', - posting_date = '2019-03-10', do_not_save=1 ) @@ -99,7 +115,6 @@ def make_sales_invoice(): income_account = 'Sales - _GST', expense_account = 'Cost of Goods Sold - _GST', cost_center = 'Main - _GST', - posting_date = '2019-03-10', do_not_save=1 ) @@ -122,7 +137,6 @@ def make_sales_invoice(): income_account = 'Sales - _GST', expense_account = 'Cost of Goods Sold - _GST', cost_center = 'Main - _GST', - posting_date = '2019-03-10', do_not_save=1 ) si3.submit() @@ -135,7 +149,6 @@ def create_purchase_invoices(): currency = 'INR', warehouse = 'Finished Goods - _GST', cost_center = 'Main - _GST', - posting_date = '2019-03-10', do_not_save=1, ) @@ -157,7 +170,6 @@ def create_purchase_invoices(): currency = 'INR', warehouse = 'Finished Goods - _GST', cost_center = 'Main - _GST', - posting_date = '2019-03-10', item = "Milk", do_not_save=1 ) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index f7057aa846..bad99ba3af 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -265,6 +265,7 @@ def make_custom_fields(update=True): 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Employee': [ dict(fieldname='ifsc_code', label='IFSC Code', fieldtype='Data', insert_after='bank_ac_no', print_hide=1, diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index c886ee9aa8..935077b7be 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -95,13 +95,12 @@ {{ doc.customer_data.last_name }} {%- else %} - {%- if doc.customer_data.is_public_administration %} - {{ doc.customer_data.fiscal_code }} - {%- else %} {{ doc.customer_address_data.country_code }} {{ doc.tax_id | replace("IT","") }} + {%- if doc.customer_data.fiscal_code %} + {{ doc.customer_data.fiscal_code }} {%- endif %} {{ doc.customer_name }} @@ -128,22 +127,42 @@ {{ format_float(doc.stamp_duty) }} {%- endif %} - {{ format_float(doc.grand_total) }} + {%- if doc.discount_amount %} + + {%- if doc.discount_amount > 0.0 %} + SC + {%- else %} + MG + {%- endif %} + {%- if doc.additional_discount_percentage > 0.0 %} + {{ format_float(doc.additional_discount_percentage) }} + {%- endif %} + {{ format_float(doc.discount_amount) }} + + {%- endif %} + {{ format_float(doc.rounded_total or doc.grand_total) }} VENDITA - {%- if doc.po_no %} - - {{ doc.po_no }} - {%- if doc.po_date %} - {{ doc.po_date }} - {%- endif %} - - {%- endif %} + {%- for po_no, po_date in doc.customer_po_data.items() %} + + {{ po_no }} + {{ po_date }} + + {%- endfor %} {%- if doc.is_return and doc.return_against_unamended %} {{ doc.return_against_unamended }} {%- endif %} + {%- for row in doc.e_invoice_items %} + {%- if row.delivery_note %} + + {{ row.delivery_note }} + {{ frappe.db.get_value('Delivery Note', row.delivery_note, 'posting_date') }} + {{ row.idx }} + + {%- endif %} + {%- endfor %} {%- if doc.shipping_address_data %} @@ -165,7 +184,11 @@ {{ item.stock_uom }} {{ format_float(item.price_list_rate or item.rate) }} {{ render_discount_or_margin(item) }} - {{ format_float(item.amount) }} + {%- if (item.discount_amount or item.rate_with_margin) %} + {{ format_float(item.net_amount) }} + {%- else %} + {{ format_float(item.amount) }} + {%- endif %} {{ format_float(item.tax_rate) }} {%- if item.tax_exemption_reason %} {{ item.tax_exemption_reason.split("-")[0] }} @@ -199,7 +222,9 @@ {{ payment_term.mode_of_payment_code.split("-")[0] }} {{ payment_term.due_date }} {{ format_float(payment_term.payment_amount) }} - {{ payment_term.bank_account_name }} + {%- if payment_term.bank_account_name %} + {{ payment_term.bank_account_name }} + {%- endif %} {%- if payment_term.bank_account_iban %} {{ payment_term.bank_account_iban }} {{ payment_term.bank_account_iban[5:10] }} diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js index 3457f7161e..586a52937b 100644 --- a/erpnext/regional/italy/sales_invoice.js +++ b/erpnext/regional/italy/sales_invoice.js @@ -3,15 +3,26 @@ erpnext.setup_e_invoice_button = (doctype) => { refresh: (frm) => { if(frm.doc.docstatus == 1) { frm.add_custom_button('Generate E-Invoice', () => { - var w = window.open( - frappe.urllib.get_full_url( - "/api/method/erpnext.regional.italy.utils.generate_single_invoice?" - + "docname=" + frm.doc.name - ) - ) - if (!w) { - frappe.msgprint(__("Please enable pop-ups")); return; - } + frm.call({ + method: "erpnext.regional.italy.utils.generate_single_invoice", + args: { + docname: frm.doc.name + }, + callback: function(r) { + frm.reload_doc(); + if(r.message) { + var w = window.open( + frappe.urllib.get_full_url( + "/api/method/erpnext.regional.italy.utils.download_e_invoice_file?" + + "file_name=" + r.message + ) + ) + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); return; + } + } + } + }); }); } } diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 2b6e3af52a..1526d6f62f 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -26,6 +26,22 @@ def make_custom_fields(update=True): print_hide=1, hidden=1, read_only=1, options="currency") ] + customer_po_fields = [ + dict(fieldname='customer_po_details', label='Customer PO', + fieldtype='Section Break', insert_after='image'), + dict(fieldname='customer_po_no', label='Customer PO No', + fieldtype='Data', insert_after='customer_po_details', + fetch_from = 'sales_order.po_no', + print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1), + dict(fieldname='customer_po_clm_brk', label='', + fieldtype='Column Break', insert_after='customer_po_no', + print_hide=1, read_only=1), + dict(fieldname='customer_po_date', label='Customer PO Date', + fieldtype='Date', insert_after='customer_po_clm_brk', + fetch_from = 'sales_order.po_date', + print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1) + ] + custom_fields = { 'Company': [ dict(fieldname='sb_e_invoicing', label='E-Invoicing', @@ -128,7 +144,7 @@ def make_custom_fields(update=True): 'Purchase Invoice Item': invoice_item_fields, 'Sales Order Item': invoice_item_fields, 'Delivery Note Item': invoice_item_fields, - 'Sales Invoice Item': invoice_item_fields, + 'Sales Invoice Item': invoice_item_fields + customer_po_fields, 'Quotation Item': invoice_item_fields, 'Purchase Order Item': invoice_item_fields, 'Purchase Receipt Item': invoice_item_fields, diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 52a5ddcfd9..876bdcf459 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -5,6 +5,7 @@ from frappe.utils import flt, cstr from erpnext.controllers.taxes_and_totals import get_itemised_tax from frappe import _ from frappe.core.doctype.file.file import remove_file +from six import string_types from frappe.desk.form.load import get_attachments from erpnext.regional.italy import state_codes @@ -82,6 +83,14 @@ def prepare_invoice(invoice, progressive_number): if item.tax_rate == 0.0 and item.tax_amount == 0.0: item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"] + customer_po_data = {} + for d in invoice.e_invoice_items: + if (d.customer_po_no and d.customer_po_date + and d.customer_po_no not in customer_po_data): + customer_po_data[d.customer_po_no] = d.customer_po_date + + invoice.customer_po_data = customer_po_data + return invoice def get_conditions(filters): @@ -134,6 +143,7 @@ def get_invoice_summary(items, taxes): idx=len(items)+1, item_code=reference_row.description, item_name=reference_row.description, + description=reference_row.description, rate=reference_row.tax_amount, qty=1.0, amount=reference_row.tax_amount, @@ -142,7 +152,7 @@ def get_invoice_summary(items, taxes): tax_amount=(reference_row.tax_amount * tax.rate) / 100, net_amount=reference_row.tax_amount, taxable_amount=reference_row.tax_amount, - item_tax_rate="{}", + item_tax_rate={tax.account_head: tax.rate}, charges=True ) ) @@ -150,10 +160,16 @@ def get_invoice_summary(items, taxes): #Check item tax rates if tax rate is zero. if tax.rate == 0: for item in items: - item_tax_rate = json.loads(item.item_tax_rate) - if tax.account_head in item_tax_rate: + item_tax_rate = item.item_tax_rate + if isinstance(item.item_tax_rate, string_types): + item_tax_rate = json.loads(item.item_tax_rate) + + if item_tax_rate and tax.account_head in item_tax_rate: key = cstr(item_tax_rate[tax.account_head]) - summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0, "tax_exemption_reason": "", "tax_exemption_law": ""}) + if key not in summary_data: + summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0, + "tax_exemption_reason": "", "tax_exemption_law": ""}) + summary_data[key]["tax_amount"] += item.tax_amount summary_data[key]["taxable_amount"] += item.net_amount if key == "0.0": @@ -198,19 +214,25 @@ def sales_invoice_validate(doc): else: doc.company_fiscal_regime = company_fiscal_regime + doc.company_tax_id = frappe.get_cached_value("Company", doc.company, 'tax_id') + doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, 'fiscal_code') if not doc.company_tax_id and not doc.company_fiscal_code: frappe.throw(_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company), title=_("E-Invoicing Information Missing")) #Validate customer details - customer_type, is_public_administration = frappe.db.get_value("Customer", doc.customer, ["customer_type", "is_public_administration"]) - if customer_type == _("Individual"): + customer = frappe.get_doc("Customer", doc.customer) + + if customer.customer_type == _("Individual"): + doc.customer_fiscal_code = customer.fiscal_code if not doc.customer_fiscal_code: frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing")) else: - if is_public_administration: + if customer.is_public_administration: + doc.customer_fiscal_code = customer.fiscal_code if not doc.customer_fiscal_code: frappe.throw(_("Please set Fiscal Code for the public administration '%s'" % doc.customer), title=_("E-Invoicing Information Missing")) else: + doc.tax_id = customer.tax_id if not doc.tax_id: frappe.throw(_("Please set Tax ID for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing")) @@ -276,13 +298,18 @@ def prepare_and_attach_invoice(doc, replace=False): def generate_single_invoice(docname): doc = frappe.get_doc("Sales Invoice", docname) + e_invoice = prepare_and_attach_invoice(doc, True) + return e_invoice.file_name + +@frappe.whitelist() +def download_e_invoice_file(file_name): content = None - with open(frappe.get_site_path('private', 'files', e_invoice.file_name), "r") as f: + with open(frappe.get_site_path('private', 'files', file_name), "r") as f: content = f.read() - frappe.local.response.filename = e_invoice.file_name + frappe.local.response.filename = file_name frappe.local.response.filecontent = content frappe.local.response.type = "download" diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 9246aa6724..9682768280 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -4,7 +4,7 @@ frappe.query_reports["GSTR-1"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -12,22 +12,22 @@ frappe.query_reports["GSTR-1"] = { "default": frappe.defaults.get_user_default("Company") }, { - "fieldname":"company_address", + "fieldname": "company_address", "label": __("Address"), "fieldtype": "Link", "options": "Address", - "get_query": function() { + "get_query": function () { var company = frappe.query_report.get_filter_value('company'); if (company) { return { "query": 'frappe.contacts.doctype.address.address.address_query', - "filters": { link_doctype: 'Company', link_name: company} + "filters": { link_doctype: 'Company', link_name: company } }; } } }, { - "fieldname":"from_date", + "fieldname": "from_date", "label": __("From Date"), "fieldtype": "Date", "reqd": 1, @@ -35,19 +35,34 @@ frappe.query_reports["GSTR-1"] = { "width": "80" }, { - "fieldname":"to_date", + "fieldname": "to_date", "label": __("To Date"), "fieldtype": "Date", "reqd": 1, "default": frappe.datetime.get_today() }, { - "fieldname":"type_of_business", + "fieldname": "type_of_business", "label": __("Type of Business"), "fieldtype": "Select", "reqd": 1, - "options": ["B2B", "B2C Large", "B2C Small","CDNR", "EXPORT"], + "options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"], "default": "B2B" } - ] + ], + onload: function (report) { + + report.page.add_inner_button(__("Download as Json"), function () { + var filters = report.get_values(); + + const args = { + cmd: 'erpnext.regional.report.gstr_1.gstr_1.get_json', + data: report.data, + report_name: report.report_name, + filters: filters + }; + + open_url_post(frappe.request.url, args); + }); + } } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index b01abce3cd..24bd6cf11c 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -4,9 +4,10 @@ from __future__ import unicode_literals import frappe, json from frappe import _ -from frappe.utils import flt, formatdate +from frappe.utils import flt, formatdate, now_datetime, getdate from datetime import date from six import iteritems +from erpnext.regional.doctype.gstr_3b_report.gstr_3b_report import get_period def execute(filters=None): return Gstr1Report(filters).run() @@ -38,7 +39,7 @@ class Gstr1Report(object): shipping_bill_date, reason_for_issuing_document """ - self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual" + # self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual" def run(self): self.get_columns() @@ -113,9 +114,14 @@ class Gstr1Report(object): if self.filters.get(opts[0]): conditions += opts[1] - customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type}) + # customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type}) if self.filters.get("type_of_business") == "B2B": + customers = frappe.get_all("Customer", + filters={ + "gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]] + }) + conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1 and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers])) @@ -124,6 +130,11 @@ class Gstr1Report(object): if not b2c_limit: frappe.throw(_("Please set B2C Limit in GST Settings.")) + customers = frappe.get_all("Customer", + filters={ + "gst_category": ["in", ["Unregistered"]] + }) + if self.filters.get("type_of_business") == "B2C Large": conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2) and grand_total > {0} and is_return != 1 and customer in ({1})""".\ @@ -494,3 +505,158 @@ class Gstr1Report(object): } ] self.columns = self.invoice_columns + self.tax_columns + self.other_columns + +@frappe.whitelist() +def get_json(): + data = frappe._dict(frappe.local.form_dict) + + del data["cmd"] + if "csrf_token" in data: + del data["csrf_token"] + + filters = json.loads(data["filters"]) + report_data = json.loads(data["data"]) + report_name = data["report_name"] + gstin = get_company_gstin_number(filters["company"]) + + fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) + + gst_json = {"gstin": "", "version": "GST2.2.9", + "hash": "hash", "gstin": gstin, "fp": fp} + + res = {} + if filters["type_of_business"] == "B2B": + for item in report_data: + res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item) + + out = get_b2b_json(res, gstin) + gst_json["b2b"] = out + elif filters["type_of_business"] == "B2C Large": + for item in report_data: + res.setdefault(item["place_of_supply"], []).append(item) + + out = get_b2cl_json(res, gstin) + gst_json["b2cl"] = out + elif filters["type_of_business"] == "EXPORT": + for item in report_data: + res.setdefault(item["export_type"], []).append(item) + + out = get_export_json(res) + gst_json["exp"] = out + + download_json_file(report_name, filters["type_of_business"], gst_json) + +def get_b2b_json(res, gstin): + inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, [] + for gst_in in res: + b2b_item, inv = {"ctin": gst_in, "inv": []}, [] + if not gst_in: continue + + for number, invoice in iteritems(res[gst_in]): + inv_item = get_basic_invoice_detail(invoice[0]) + inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]) + inv_item["rchrg"] = invoice[0]["reverse_charge"] + inv_item["inv_typ"] = inv_type.get(invoice[0].get("gst_category", ""),"") + + if inv_item["pos"]=="00": continue + inv_item["itms"] = [] + + for item in invoice: + inv_item["itms"].append(get_rate_and_tax_details(item, gstin)) + + inv.append(inv_item) + + if not inv: continue + b2b_item["inv"] = inv + out.append(b2b_item) + + return out + +def get_b2cl_json(res, gstin): + out = [] + for pos in res: + b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, [] + + for row in res[pos]: + inv_item = get_basic_invoice_detail(row) + if row.get("sale_from_bonded_wh"): + inv_item["inv_typ"] = "CBW" + + inv_item["itms"] = [get_rate_and_tax_details(row, gstin)] + + inv.append(inv_item) + + b2cl_item["inv"] = inv + out.append(b2cl_item) + + return out + +def get_export_json(res): + out = [] + for exp_type in res: + exp_item, inv = {"exp_typ": exp_type, "inv": []}, [] + + for row in res[exp_type]: + inv_item = get_basic_invoice_detail(row) + inv_item["itms"] = [{ + "txval": flt(row["taxable_value"], 2), + "rt": row["rate"] or 0, + "iamt": 0, + "csamt": 0 + }] + + inv.append(inv_item) + + exp_item["inv"] = inv + out.append(exp_item) + + return out + +def get_basic_invoice_detail(row): + return { + "inum": row["invoice_number"], + "idt": getdate(row["posting_date"]).strftime('%d-%m-%Y'), + "val": flt(row["invoice_value"], 2) + } + +def get_rate_and_tax_details(row, gstin): + itm_det = {"txval": flt(row["taxable_value"], 2), + "rt": row["rate"], + "csamt": (flt(row.get("cess_amount"), 2) or 0) + } + + # calculate rate + num = 1 if not row["rate"] else "%d%02d" % (row["rate"], 1) + rate = row.get("rate") or 0 + + # calculate tax amount added + tax = flt((row["taxable_value"]*rate)/100.0, 2) + frappe.errprint([tax, tax/2]) + if row.get("customer_gstin") and gstin[0:2] == row["customer_gstin"][0:2]: + itm_det.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)}) + else: + itm_det.update({"iamt": tax}) + + return {"num": int(num), "itm_det": itm_det} + +def get_company_gstin_number(company): + filters = [ + ["is_your_company_address", "=", 1], + ["Dynamic Link", "link_doctype", "=", "Company"], + ["Dynamic Link", "link_name", "=", company], + ["Dynamic Link", "parenttype", "=", "Address"], + ] + + gstin = frappe.get_all("Address", filters=filters, fields=["gstin"]) + + if gstin: + return gstin[0]["gstin"] + else: + frappe.throw(_("No GST No. found for the Company.")) + +def download_json_file(filename, report_type, data): + ''' download json content in a file ''' + frappe.response['filename'] = frappe.scrub("{0} {1}".format(filename, report_type)) + '.json' + frappe.response['filecontent'] = json.dumps(data) + frappe.response['content_type'] = 'application/json' + frappe.response['type'] = 'download' diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index c85065cf12..ad151bef00 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -97,6 +97,8 @@ class Quotation(SellingController): self.update_lead() def on_cancel(self): + super(Quotation, self).on_cancel() + #update enquiry status self.set_status(update=True) self.update_opportunity() diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 1ad4e9b864..7eab352fc0 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -183,6 +183,8 @@ class SalesOrder(SellingController): self.update_blanket_order() def on_cancel(self): + super(SalesOrder, self).on_cancel() + # Cannot cancel closed SO if self.status == 'Closed': frappe.throw(_("Closed order cannot be cancelled. Unclose to cancel.")) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 766e4e6ed0..3a1383f6c8 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals import frappe -from frappe.utils import flt, add_days +from frappe.utils import flt, add_days, nowdate import frappe.permissions import unittest from erpnext.selling.doctype.sales_order.sales_order \ @@ -13,7 +13,6 @@ from erpnext.controllers.accounts_controller import update_child_qty_rate import json from erpnext.selling.doctype.sales_order.sales_order import make_raw_material_request - class TestSalesOrder(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") @@ -703,6 +702,28 @@ class TestSalesOrder(unittest.TestCase): se.cancel() self.assertFalse(frappe.db.exists("Serial No", {"sales_order": so.name})) + def test_advance_payment_entry_unlink_against_sales_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", 0) + + so = make_sales_order() + + pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = so.currency + pe.paid_to_account_currency = so.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = so.grand_total + pe.save(ignore_permissions=True) + pe.submit() + + so_doc = frappe.get_doc('Sales Order', so.name) + + self.assertRaises(frappe.LinkExistsError, so_doc.cancel) + def test_request_for_raw_materials(self): from erpnext.stock.doctype.item.test_item import make_item item = make_item("_Test Finished Item", {"is_stock_item": 1, diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index c54430fd56..4c0f42d22e 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -233,9 +233,21 @@ erpnext.pos.PointOfSale = class PointOfSale { } else { this.update_item_in_frm(item, field, value) .then(() => { - // update cart - this.update_cart_data(item); - this.set_form_action(); + frappe.dom.unfreeze(); + frappe.run_serially([ + () => { + let items = this.frm.doc.items.map(item => item.name); + if (items && items.length > 0 && items.includes(item.name)) { + this.frm.doc.items.forEach(item_row => { + // update cart + this.on_qty_change(item_row); + }); + } else { + this.on_qty_change(item); + } + }, + () => this.post_qty_change(item) + ]); }); } return; @@ -251,7 +263,28 @@ erpnext.pos.PointOfSale = class PointOfSale { frappe.flags.hide_serial_batch_dialog = true; frappe.run_serially([ - () => this.frm.script_manager.trigger('item_code', item.doctype, item.name), + () => { + this.frm.script_manager.trigger('item_code', item.doctype, item.name) + .then(() => { + this.frm.script_manager.trigger('qty', item.doctype, item.name) + .then(() => { + frappe.run_serially([ + () => { + let items = this.frm.doc.items.map(i => i.name); + if (items && items.length > 0 && items.includes(item.name)) { + this.frm.doc.items.forEach(item_row => { + // update cart + this.on_qty_change(item_row); + }); + } else { + this.on_qty_change(item); + } + }, + () => this.post_qty_change(item) + ]); + }); + }); + }, () => { const show_dialog = item.has_serial_no || item.has_batch_no; @@ -261,14 +294,25 @@ erpnext.pos.PointOfSale = class PointOfSale { (item.has_serial_no) || (item.actual_batch_qty != item.actual_qty)) ) { // check has serial no/batch no and update cart this.select_batch_and_serial_no(item); - } else { - // update cart - this.update_cart_data(item); } } ]); } + on_qty_change(item) { + frappe.run_serially([ + () => this.update_cart_data(item), + ]); + } + + post_qty_change(item) { + this.cart.update_taxes_and_totals(); + this.cart.update_grand_total(); + this.cart.update_qty_total(); + this.cart.scroll_to_item(item.item_code); + this.set_form_action(); + } + select_batch_and_serial_no(row) { frappe.dom.unfreeze(); @@ -283,7 +327,8 @@ erpnext.pos.PointOfSale = class PointOfSale { frappe.model.clear_doc(item.doctype, item.name); } }, - () => this.update_cart_data(item) + () => this.update_cart_data(item), + () => this.post_qty_change(item) ]); }); }) @@ -300,9 +345,6 @@ erpnext.pos.PointOfSale = class PointOfSale { update_cart_data(item) { this.cart.add_item(item); - this.cart.update_taxes_and_totals(); - this.cart.update_grand_total(); - this.cart.update_qty_total(); frappe.dom.unfreeze(); } @@ -446,16 +488,15 @@ erpnext.pos.PointOfSale = class PointOfSale { } setup_company() { - this.company = frappe.sys_defaults.company; return new Promise(resolve => { - if(!this.company) { + if(!frappe.sys_defaults.company) { frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link", label: __("Select Company"), reqd: 1}, (data) => { this.company = data.company; resolve(this.company); }, __("Select Company")); } else { - resolve(this.company); + resolve(); } }) } @@ -509,7 +550,9 @@ erpnext.pos.PointOfSale = class PointOfSale { } set_pos_profile_data() { - this.frm.doc.company = this.company; + if (this.company) { + this.frm.doc.company = this.company; + } return new Promise(resolve => { return this.frm.call({ @@ -953,7 +996,6 @@ class POSCart { $item.appendTo(this.$cart_items); } this.highlight_item(item.item_code); - this.scroll_to_item(item.item_code); } update_item(item) { @@ -1206,7 +1248,10 @@ class POSItems { clearTimeout(this.last_search); this.last_search = setTimeout(() => { const search_term = e.target.value; - this.filter_items({ search_term }); + const item_group = this.item_group_field ? + this.item_group_field.get_value() : ''; + + this.filter_items({ search_term:search_term, item_group: item_group}); }, 300); }); diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index ced6540e21..7c3cf5b039 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -29,7 +29,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p batch_no = data.get("batch_no") if data.get("batch_no") else "" barcode = data.get("barcode") if data.get("barcode") else "" - item_code, condition = get_conditions(item_code, serial_no, batch_no, barcode) + condition = get_conditions(item_code, serial_no, batch_no, barcode) if pos_profile: condition += get_item_group_condition(pos_profile) @@ -86,7 +86,6 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p and {condition} limit {start}, {page_length}""".format (start=start,page_length=page_length,lft=lft, rgt=rgt, condition=condition), { - 'item_code': item_code, 'price_list': price_list, 'warehouse': warehouse } , as_dict=1) @@ -133,12 +132,10 @@ def search_serial_or_batch_or_barcode_number(search_value): def get_conditions(item_code, serial_no, batch_no, barcode): if serial_no or batch_no or barcode: - return frappe.db.escape(item_code), "i.name = %(item_code)s" + return "i.name = {0}".format(frappe.db.escape(item_code)) - condition = """(i.name like %(item_code)s - or i.item_name like %(item_code)s)""" - - return frappe.db.escape('%' + item_code + '%'), condition + return """(i.name like {item_code} + or i.item_name like {item_code})""".format(item_code = frappe.db.escape('%' + item_code + '%')) def get_item_group_condition(pos_profile): cond = "and 1=1" diff --git a/erpnext/setup/doctype/brand/test_records.json b/erpnext/setup/doctype/brand/test_records.json index 17b5a6b0a3..eeed9e7733 100644 --- a/erpnext/setup/doctype/brand/test_records.json +++ b/erpnext/setup/doctype/brand/test_records.json @@ -1,6 +1,6 @@ [ { - "brand": "_Test Brand", + "brand": "_Test Brand", "doctype": "Brand" }, { diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 77c371e0cd..dbd7c4149f 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -22,6 +22,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "details", "fieldtype": "Section Break", "hidden": 0, @@ -54,6 +55,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company_name", "fieldtype": "Data", "hidden": 0, @@ -88,6 +90,7 @@ "collapsible": 0, "columns": 0, "description": "", + "fetch_if_empty": 0, "fieldname": "abbr", "fieldtype": "Data", "hidden": 0, @@ -122,6 +125,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")", + "fetch_if_empty": 0, "fieldname": "change_abbr", "fieldtype": "Button", "hidden": 0, @@ -153,6 +157,7 @@ "bold": 1, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_group", "fieldtype": "Check", "hidden": 0, @@ -185,6 +190,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_finance_book", "fieldtype": "Link", "hidden": 0, @@ -218,6 +224,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "cb0", "fieldtype": "Column Break", "hidden": 0, @@ -248,6 +255,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "domain", "fieldtype": "Link", "hidden": 0, @@ -280,6 +288,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "parent_company", "fieldtype": "Link", "hidden": 0, @@ -313,6 +322,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sb_about", "fieldtype": "Section Break", "hidden": 0, @@ -345,6 +355,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company_logo", "fieldtype": "Attach Image", "hidden": 0, @@ -377,6 +388,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company_description", "fieldtype": "Text Editor", "hidden": 0, @@ -409,6 +421,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sales_settings", "fieldtype": "Section Break", "hidden": 0, @@ -441,6 +454,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "sales_monthly_history", "fieldtype": "Small Text", "hidden": 1, @@ -473,6 +487,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "transactions_annual_history", "fieldtype": "Code", "hidden": 1, @@ -505,6 +520,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "monthly_sales_target", "fieldtype": "Currency", "hidden": 0, @@ -538,6 +554,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_goals", "fieldtype": "Column Break", "hidden": 0, @@ -569,6 +586,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "total_monthly_sales", "fieldtype": "Currency", "hidden": 0, @@ -602,6 +620,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "charts_section", "fieldtype": "Section Break", "hidden": 0, @@ -633,6 +652,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_currency", "fieldtype": "Link", "hidden": 0, @@ -665,6 +685,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_letter_head", "fieldtype": "Link", "hidden": 0, @@ -698,6 +719,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_holiday_list", "fieldtype": "Link", "hidden": 0, @@ -731,6 +753,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "standard_working_hours", "fieldtype": "Float", "hidden": 0, @@ -763,6 +786,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_terms", "fieldtype": "Link", "hidden": 0, @@ -795,6 +819,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_10", "fieldtype": "Column Break", "hidden": 0, @@ -826,6 +851,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "country", "fieldtype": "Link", "hidden": 0, @@ -858,6 +884,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "create_chart_of_accounts_based_on", "fieldtype": "Select", "hidden": 0, @@ -892,6 +919,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", + "fetch_if_empty": 0, "fieldname": "chart_of_accounts", "fieldtype": "Select", "hidden": 0, @@ -926,6 +954,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Existing Company\"", + "fetch_if_empty": 0, "fieldname": "existing_company", "fieldtype": "Link", "hidden": 0, @@ -959,6 +988,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "tax_id", "fieldtype": "Data", "hidden": 0, @@ -991,6 +1021,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "date_of_establishment", "fieldtype": "Date", "hidden": 0, @@ -1023,6 +1054,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_settings", "fieldtype": "Section Break", "hidden": 0, @@ -1056,6 +1088,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_bank_account", "fieldtype": "Link", "hidden": 0, @@ -1091,6 +1124,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_cash_account", "fieldtype": "Link", "hidden": 0, @@ -1124,6 +1158,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_receivable_account", "fieldtype": "Link", "hidden": 0, @@ -1158,6 +1193,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "round_off_account", "fieldtype": "Link", "hidden": 0, @@ -1191,6 +1227,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "round_off_cost_center", "fieldtype": "Link", "hidden": 0, @@ -1224,6 +1261,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "write_off_account", "fieldtype": "Link", "hidden": 0, @@ -1257,6 +1295,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "discount_allowed_account", "fieldtype": "Link", "hidden": 0, @@ -1290,6 +1329,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "discount_received_account", "fieldtype": "Link", "hidden": 0, @@ -1323,6 +1363,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "exchange_gain_loss_account", "fieldtype": "Link", "hidden": 0, @@ -1356,6 +1397,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "unrealized_exchange_gain_loss_account", "fieldtype": "Link", "hidden": 0, @@ -1389,6 +1431,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break0", "fieldtype": "Column Break", "hidden": 0, @@ -1414,6 +1457,40 @@ "unique": 0, "width": "50%" }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.parent_company", + "fetch_if_empty": 0, + "fieldname": "allow_account_creation_against_child_company", + "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": "Allow Account Creation Against Child Company", + "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, @@ -1422,6 +1499,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_payable_account", "fieldtype": "Link", "hidden": 0, @@ -1456,6 +1534,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_employee_advance_account", "fieldtype": "Link", "hidden": 0, @@ -1490,6 +1569,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_expense_account", "fieldtype": "Link", "hidden": 0, @@ -1523,6 +1603,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_income_account", "fieldtype": "Link", "hidden": 0, @@ -1556,6 +1637,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_deferred_revenue_account", "fieldtype": "Link", "hidden": 0, @@ -1590,6 +1672,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_deferred_expense_account", "fieldtype": "Link", "hidden": 0, @@ -1624,6 +1707,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_payroll_payable_account", "fieldtype": "Link", "hidden": 0, @@ -1658,6 +1742,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "default_expense_claim_payable_account", "fieldtype": "Link", "hidden": 0, @@ -1691,6 +1776,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_22", "fieldtype": "Section Break", "hidden": 0, @@ -1723,6 +1809,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "cost_center", "fieldtype": "Link", "hidden": 0, @@ -1755,6 +1842,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_26", "fieldtype": "Column Break", "hidden": 0, @@ -1787,6 +1875,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "credit_limit", "fieldtype": "Currency", "hidden": 0, @@ -1822,6 +1911,7 @@ "collapsible": 0, "columns": 0, "depends_on": "", + "fetch_if_empty": 0, "fieldname": "payment_terms", "fieldtype": "Link", "hidden": 0, @@ -1856,6 +1946,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:!doc.__islocal", + "fetch_if_empty": 0, "fieldname": "auto_accounting_for_stock_settings", "fieldtype": "Section Break", "hidden": 0, @@ -1888,6 +1979,7 @@ "collapsible": 0, "columns": 0, "default": "1", + "fetch_if_empty": 0, "fieldname": "enable_perpetual_inventory", "fieldtype": "Check", "hidden": 0, @@ -1920,6 +2012,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "default_inventory_account", "fieldtype": "Link", "hidden": 0, @@ -1953,6 +2046,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "stock_adjustment_account", "fieldtype": "Link", "hidden": 0, @@ -1985,6 +2079,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_32", "fieldtype": "Column Break", "hidden": 0, @@ -2016,6 +2111,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "stock_received_but_not_billed", "fieldtype": "Link", "hidden": 0, @@ -2048,6 +2144,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "expenses_included_in_valuation", "fieldtype": "Link", "hidden": 0, @@ -2080,6 +2177,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "fixed_asset_depreciation_settings", "fieldtype": "Section Break", "hidden": 0, @@ -2112,6 +2210,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "accumulated_depreciation_account", "fieldtype": "Link", "hidden": 0, @@ -2145,6 +2244,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "depreciation_expense_account", "fieldtype": "Link", "hidden": 0, @@ -2178,6 +2278,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "series_for_depreciation_entry", "fieldtype": "Data", "hidden": 0, @@ -2210,6 +2311,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "expenses_included_in_asset_valuation", "fieldtype": "Link", "hidden": 0, @@ -2243,6 +2345,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_40", "fieldtype": "Column Break", "hidden": 0, @@ -2274,6 +2377,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "disposal_account", "fieldtype": "Link", "hidden": 0, @@ -2307,6 +2411,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "depreciation_cost_center", "fieldtype": "Link", "hidden": 0, @@ -2340,6 +2445,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "capital_work_in_progress_account", "fieldtype": "Link", "hidden": 0, @@ -2373,6 +2479,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "asset_received_but_not_billed", "fieldtype": "Link", "hidden": 0, @@ -2406,6 +2513,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "budget_detail", "fieldtype": "Section Break", "hidden": 0, @@ -2438,6 +2546,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "exception_budget_approver_role", "fieldtype": "Link", "hidden": 0, @@ -2472,6 +2581,7 @@ "collapsible": 0, "columns": 0, "description": "For reference only.", + "fetch_if_empty": 0, "fieldname": "company_info", "fieldtype": "Section Break", "hidden": 0, @@ -2503,6 +2613,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "date_of_incorporation", "fieldtype": "Date", "hidden": 0, @@ -2535,6 +2646,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "address_html", "fieldtype": "HTML", "hidden": 0, @@ -2566,6 +2678,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break1", "fieldtype": "Column Break", "hidden": 0, @@ -2599,6 +2712,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.date_of_incorporation", + "fetch_if_empty": 0, "fieldname": "date_of_commencement", "fieldtype": "Date", "hidden": 0, @@ -2631,6 +2745,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "phone_no", "fieldtype": "Data", "hidden": 0, @@ -2665,6 +2780,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "fax", "fieldtype": "Data", "hidden": 0, @@ -2699,6 +2815,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "email", "fieldtype": "Data", "hidden": 0, @@ -2733,6 +2850,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "website", "fieldtype": "Data", "hidden": 0, @@ -2767,6 +2885,7 @@ "collapsible": 0, "columns": 0, "description": "", + "fetch_if_empty": 0, "fieldname": "registration_info", "fieldtype": "Section Break", "hidden": 0, @@ -2801,6 +2920,7 @@ "collapsible": 0, "columns": 0, "description": "Company registration numbers for your reference. Tax numbers etc.", + "fetch_if_empty": 0, "fieldname": "registration_details", "fieldtype": "Code", "hidden": 0, @@ -2834,6 +2954,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "delete_company_transactions", "fieldtype": "Button", "hidden": 0, @@ -2866,6 +2987,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "lft", "fieldtype": "Int", "hidden": 1, @@ -2898,6 +3020,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "rgt", "fieldtype": "Int", "hidden": 1, @@ -2930,6 +3053,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "old_parent", "fieldtype": "Data", "hidden": 1, @@ -2969,7 +3093,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2019-01-15 13:29:54.510379", + "modified": "2019-03-26 17:15:50.390548", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 869b614f7f..0b61240035 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -177,7 +177,7 @@ class DeliveryNote(SellingController): frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code)) else: chk_dupl_itm.append(f) - #Customer Provided parts will have zero valuation rate + #Customer Provided parts will have zero valuation rate if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 @@ -223,6 +223,8 @@ class DeliveryNote(SellingController): self.make_gl_entries() def on_cancel(self): + super(DeliveryNote, self).on_cancel() + self.check_close_sales_order("against_sales_order") self.check_next_docstatus() diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 73c009c7fb..a1083051f1 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -118,7 +118,6 @@ class Item(WebsiteGenerator): self.validate_has_variants() self.validate_stock_exists_for_template_item() - self.validate_asset_exists_for_serialized_asset() self.validate_attributes() self.validate_variant_attributes() self.validate_website_image() @@ -128,6 +127,7 @@ class Item(WebsiteGenerator): self.validate_uom_conversion_factor() self.validate_item_defaults() self.validate_customer_provided_part() + self.validate_stock_for_has_batch_and_has_serial() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -759,12 +759,6 @@ class Item(WebsiteGenerator): frappe.throw( _('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item')) - def validate_asset_exists_for_serialized_asset(self): - if (not self.get("__islocal") and self.asset_exists() and - cint(self.has_serial_no) != cint(frappe.db.get_value('Item', self.name, 'has_serial_no'))): - frappe.throw(_("Asset is already exists against the item {0}, you cannot change the has serial no value") - .format(self.name)) - def asset_exists(self): if not hasattr(self, '_asset_created'): self._asset_created = frappe.db.get_all("Asset", @@ -791,6 +785,9 @@ class Item(WebsiteGenerator): d.conversion_factor = value def validate_attributes(self): + if not self.variant_based_on: + self.variant_based_on = 'Item Attribute' + if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute': attributes = [] if not self.attributes: @@ -813,7 +810,7 @@ class Item(WebsiteGenerator): variant = get_variant(self.variant_of, args, self.name) if variant: frappe.throw(_("Item variant {0} exists with same attributes") - .format(variant), ItemVariantExistsError) + .format(variant), ItemVariantExistsError) validate_item_variant_attributes(self, args) @@ -821,6 +818,11 @@ class Item(WebsiteGenerator): for d in self.attributes: d.variant_of = self.variant_of + def validate_stock_for_has_batch_and_has_serial(self): + if self.stock_ledger_created(): + for value in ["has_batch_no", "has_serial_no"]: + if frappe.db.get_value("Item", self.name, value) != self.get_value(value): + frappe.throw(_("Cannot change {0} as Stock Transaction for Item {1} exist.".format(value, self.name))) def get_timeline_data(doctype, name): '''returns timeline data based on stock ledger entry''' diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 1a94d5a680..da53d8d6b1 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -18,7 +18,7 @@ from erpnext.stock.get_item_details import get_item_details from six import iteritems test_ignore = ["BOM"] -test_dependencies = ["Warehouse", "Item Group", "Item Tax Template"] +test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand"] def make_item(item_code, properties=None): if frappe.db.exists("Item", item_code): @@ -513,3 +513,6 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, "company": "_Test Company" }) item.save() + else: + item = frappe.get_doc("Item", item_code) + return item diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index 0d60a5ca38..63e374f6d7 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -51,6 +51,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "description": "If blank, parent Warehouse Account or company default will be considered", "fieldname": "warehouse_name", "fieldtype": "Data", "hidden": 0, @@ -870,7 +871,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-29 06:26:48.647225", + "modified": "2018-08-29 06:26:49.647225", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 6d75314638..4d8022c24f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -667,28 +667,39 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa @frappe.whitelist() def get_pos_profile(company, pos_profile=None, user=None): - if pos_profile: - return frappe.get_cached_doc('POS Profile', pos_profile) + if pos_profile: return frappe.get_cached_doc('POS Profile', pos_profile) if not user: user = frappe.session['user'] + condition = "pfu.user = %(user)s AND pfu.default=1" + if user and company: + condition = "pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1" + pos_profile = frappe.db.sql("""SELECT pf.* FROM `tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu ON pf.name = pfu.parent WHERE - ( - (pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1) - OR (pfu.user = %(user)s AND pfu.default=1) - OR (ifnull(pfu.user, '') = '' AND pf.company = %(company)s) - ) AND pf.disabled = 0 - """, { + {cond} AND pf.disabled = 0 + """.format(cond = condition), { 'user': user, 'company': company }, as_dict=1) + if not pos_profile and company: + pos_profile = frappe.db.sql("""SELECT pf.* + FROM + `tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu + ON + pf.name = pfu.parent + WHERE + pf.company = %(company)s AND pf.disabled = 0 + """, { + 'company': company + }, as_dict=1) + return pos_profile and pos_profile[0] or None def get_serial_nos_by_fifo(args, sales_order=None): diff --git a/erpnext/stock/report/ordered_items_to_be_delivered/ordered_items_to_be_delivered.json b/erpnext/stock/report/ordered_items_to_be_delivered/ordered_items_to_be_delivered.json index 2dd5b7a4c6..aa5fd0f165 100644 --- a/erpnext/stock/report/ordered_items_to_be_delivered/ordered_items_to_be_delivered.json +++ b/erpnext/stock/report/ordered_items_to_be_delivered/ordered_items_to_be_delivered.json @@ -1,31 +1,32 @@ { - "add_total_row": 1, - "apply_user_permissions": 1, - "creation": "2018-01-09 18:38:23.540100", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2018-01-15 15:04:15.340151", - "modified_by": "Administrator", - "module": "Stock", - "name": "Ordered Items To Be Delivered", - "owner": "Administrator", - "query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`customer_name` as \"Customer Name::150\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project` as \"Project:Link/Project:120\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order Item`.`delivery_date` as \"Item Delivery Date:Date:120\",\n DATEDIFF(CURDATE(),`tabSales Order Item`.`delivery_date`) as \"Delay Days:Int:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order` JOIN `tabSales Order Item` \n LEFT JOIN `tabBin` ON (`tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse)\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status not in (\"Stopped\", \"Closed\")\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\norder by `tabSales Order`.transaction_date asc", - "ref_doctype": "Delivery Note", - "report_name": "Ordered Items To Be Delivered", - "report_type": "Query Report", + "add_total_row": 1, + "creation": "2018-01-09 18:38:23.540100", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2019-04-01 22:10:09.829361", + "modified_by": "Administrator", + "module": "Stock", + "name": "Ordered Items To Be Delivered", + "owner": "Administrator", + "prepared_report": 0, + "query": "select \n `tabSales Order`.`name` as \"Sales Order:Link/Sales Order:120\",\n `tabSales Order`.`status` as \"Status:Data:120\",\n `tabSales Order`.`customer` as \"Customer:Link/Customer:120\",\n `tabSales Order`.`customer_name` as \"Customer Name::150\",\n `tabSales Order`.`transaction_date` as \"Date:Date\",\n `tabSales Order`.`project` as \"Project:Link/Project:120\",\n `tabSales Order Item`.item_code as \"Item:Link/Item:120\",\n `tabSales Order Item`.qty as \"Qty:Float:140\",\n `tabSales Order Item`.delivered_qty as \"Delivered Qty:Float:140\",\n (`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0)) as \"Qty to Deliver:Float:140\",\n `tabSales Order Item`.base_rate as \"Rate:Float:140\",\n `tabSales Order Item`.base_amount as \"Amount:Float:140\",\n ((`tabSales Order Item`.qty - ifnull(`tabSales Order Item`.delivered_qty, 0))*`tabSales Order Item`.base_rate) as \"Amount to Deliver:Float:140\",\n `tabBin`.actual_qty as \"Available Qty:Float:120\",\n `tabBin`.projected_qty as \"Projected Qty:Float:120\",\n `tabSales Order Item`.`delivery_date` as \"Item Delivery Date:Date:120\",\n DATEDIFF(CURDATE(),`tabSales Order Item`.`delivery_date`) as \"Delay Days:Int:120\",\n `tabSales Order Item`.item_name as \"Item Name::150\",\n `tabSales Order Item`.description as \"Description::200\",\n `tabSales Order Item`.item_group as \"Item Group:Link/Item Group:120\",\n `tabSales Order Item`.warehouse as \"Warehouse:Link/Warehouse:200\"\nfrom\n `tabSales Order` JOIN `tabSales Order Item` \n LEFT JOIN `tabBin` ON (`tabBin`.item_code = `tabSales Order Item`.item_code\n and `tabBin`.warehouse = `tabSales Order Item`.warehouse)\nwhere\n `tabSales Order Item`.`parent` = `tabSales Order`.`name`\n and `tabSales Order`.docstatus = 1\n and `tabSales Order`.status not in (\"Stopped\", \"Closed\")\n and ifnull(`tabSales Order Item`.delivered_qty,0) < ifnull(`tabSales Order Item`.qty,0)\norder by `tabSales Order`.transaction_date asc", + "ref_doctype": "Delivery Note", + "report_name": "Ordered Items To Be Delivered", + "report_type": "Query Report", "roles": [ { "role": "Stock User" - }, + }, { "role": "Stock Manager" - }, + }, { "role": "Sales User" - }, + }, { "role": "Accounts User" } diff --git a/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json b/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json index 3c440f44ad..dfaa9ed6cc 100644 --- a/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json +++ b/erpnext/stock/report/purchase_order_items_to_be_received/purchase_order_items_to_be_received.json @@ -1,31 +1,32 @@ { - "add_total_row": 1, - "apply_user_permissions": 1, - "creation": "2013-02-22 18:01:55", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2017-02-24 20:04:20.699195", - "modified_by": "Administrator", - "module": "Stock", - "name": "Purchase Order Items To Be Received", - "owner": "Administrator", - "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n\t`tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order Item`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.qty as \"Qty:Float:100\",\n\t`tabPurchase Order Item`.received_qty as \"Received Qty:Float:100\", \n\t(`tabPurchase Order Item`.qty - ifnull(`tabPurchase Order Item`.received_qty, 0)) as \"Qty to Receive:Float:100\",\n `tabPurchase Order Item`.warehouse as \"Warehouse:Link/Warehouse:150\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n `tabPurchase Order Item`.brand as \"Brand::100\",\n\t`tabPurchase Order`.`company` as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status not in (\"Stopped\", \"Closed\")\n\tand ifnull(`tabPurchase Order Item`.received_qty, 0) < ifnull(`tabPurchase Order Item`.qty, 0)\norder by `tabPurchase Order`.transaction_date asc", - "ref_doctype": "Purchase Receipt", - "report_name": "Purchase Order Items To Be Received", - "report_type": "Query Report", + "add_total_row": 1, + "creation": "2013-02-22 18:01:55", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 3, + "is_standard": "Yes", + "modified": "2019-04-01 22:12:05.573343", + "modified_by": "Administrator", + "module": "Stock", + "name": "Purchase Order Items To Be Received", + "owner": "Administrator", + "prepared_report": 0, + "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`status` as \"Status:Data:120\",\n\t`tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order Item`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order`.`supplier_name` as \"Supplier Name::150\",\n\t`tabPurchase Order Item`.`project` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.qty as \"Qty:Float:100\",\n\t`tabPurchase Order Item`.received_qty as \"Received Qty:Float:100\", \n\t(`tabPurchase Order Item`.qty - ifnull(`tabPurchase Order Item`.received_qty, 0)) as \"Qty to Receive:Float:100\",\n `tabPurchase Order Item`.warehouse as \"Warehouse:Link/Warehouse:150\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\",\n `tabPurchase Order Item`.brand as \"Brand::100\",\n\t`tabPurchase Order`.`company` as \"Company:Link/Company:\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status not in (\"Stopped\", \"Closed\")\n\tand ifnull(`tabPurchase Order Item`.received_qty, 0) < ifnull(`tabPurchase Order Item`.qty, 0)\norder by `tabPurchase Order`.transaction_date asc", + "ref_doctype": "Purchase Receipt", + "report_name": "Purchase Order Items To Be Received", + "report_type": "Query Report", "roles": [ { "role": "Stock Manager" - }, + }, { "role": "Stock User" - }, + }, { "role": "Purchase User" - }, + }, { "role": "Accounts User" } diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 5d7c582609..0c71f9182e 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -225,16 +225,15 @@ def get_item_details(items, sle, filters): cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ % frappe.db.escape(filters.get("include_uom")) - item_codes = ', '.join(['"' + frappe.db.escape(i, percent=False) + '"' for i in items]) res = frappe.db.sql(""" select - item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field} + item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom %s from `tabItem` item - {cf_join} + %s where - item.name in ({item_codes}) and ifnull(item.disabled, 0) = 0 - """.format(cf_field=cf_field, cf_join=cf_join, item_codes=item_codes), as_dict=1) + item.name in (%s) and ifnull(item.disabled, 0) = 0 + """ % (cf_field, cf_join, ','.join(['%s'] *len(items))), items, as_dict=1) for item in res: item_details.setdefault(item.name, item) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 46c55d2ff3..20b5e45e19 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -113,7 +113,6 @@ def get_item_details(items, sl_entries, include_uom): cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ % frappe.db.escape(include_uom) - item_codes = ', '.join([frappe.db.escape(i, percent=False) for i in items]) res = frappe.db.sql(""" select item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field} @@ -122,7 +121,7 @@ def get_item_details(items, sl_entries, include_uom): {cf_join} where item.name in ({item_codes}) - """.format(cf_field=cf_field, cf_join=cf_join, item_codes=item_codes), as_dict=1) + """.format(cf_field=cf_field, cf_join=cf_join, item_codes=','.join(['%s'] *len(items))), items, as_dict=1) for item in res: item_details.setdefault(item.name, item) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index dba111d8d8..97b34e9401 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -1,7 +1,9 @@ frappe.ui.form.on("Issue", { onload: function(frm) { frm.email_field = "raised_by"; - set_time_to_resolve_and_response(frm); + if (frm.doc.service_level_agreement) { + set_time_to_resolve_and_response(frm); + } }, refresh: function (frm) { @@ -84,15 +86,16 @@ function set_time_to_resolve_and_response(frm) { const email_account = frm.fields_dict['email_account'].$wrapper; const time_to_respond = $(get_time_left_element(__('Time To Respond'), frm.doc.response_by)); - const time_to_resolve = $(get_time_left_element(__('Time To Resolve'), frm.doc.resolve_by)); + const time_to_resolve = $(get_time_left_element(__('Time To Resolve'), frm.doc.resolution_by)); time_to_respond.insertAfter(customer); time_to_resolve.insertAfter(email_account); } function get_time_left_element(label, timestamp) { + $('.'+ frappe.scrub(label) +'').remove(); return ` -
+