diff --git a/.travis.yml b/.travis.yml index 869fe959c00..a8a0d826145 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ dist: trusty python: - "2.7" + - "3.6" + +env: + - TEST_TYPE="Server Side Test" + - TEST_TYPE="Patch Test" services: - mysql @@ -39,18 +44,8 @@ before_script: - bench start & - sleep 10 -jobs: - include: - - stage: test - script: - - set -e - - bench run-tests --app erpnext --coverage - after_script: - - coveralls -b apps/erpnext -d ../../sites/.coverage - env: Server Side Test - - # stage - script: - - wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz - - bench --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz --mariadb-root-password travis - - bench migrate - env: Patch Testing +script: + - bash $TRAVIS_BUILD_DIR/travis/run-tests.sh + +after_script: + - coveralls -b apps/erpnext -d ../../sites/.coverage diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 0fdf7031ab3..a06efa0ff75 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.38' +__version__ = '11.1.39' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js index 2a44cb3b52f..0acbe2009f5 100644 --- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js +++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.js @@ -43,8 +43,13 @@ frappe.ui.form.on('Bank Guarantee', { reference_docname: function(frm) { if (frm.doc.reference_docname && frm.doc.reference_doctype) { - let fields_to_fetch = ["project", "grand_total"]; + let fields_to_fetch = ["grand_total"]; let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier"; + + if (frm.doc.reference_doctype == "Sales Order") { + fields_to_fetch.push("project"); + } + fields_to_fetch.push(party_field); frappe.call({ method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 7f0f60ff02e..186f3fd1e60 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -350,7 +350,7 @@ def filter_pricing_rules(args, 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) \ + pricing_rules = list(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: @@ -373,7 +373,7 @@ 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) + filtered_rules = list(filter(lambda x: x[field]==args[field], pricing_rules)) if filtered_rules: break return filtered_rules or pricing_rules diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a836a1813e6..5393f5d37fb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -371,6 +371,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte me.frm.pos_print_format = r.message.print_format; } me.frm.script_manager.trigger("update_stock"); + if(me.frm.doc.taxes_and_charges) { + me.frm.script_manager.trigger("taxes_and_charges"); + } + frappe.model.set_default_values(me.frm.doc); me.set_dynamic_labels(); me.calculate_taxes_and_totals(); diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 25301967080..638e57ed2b9 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -22,7 +22,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): invoices = [] # create invoices for lower than single threshold tax rate - for _ in xrange(2): + for _ in range(2): pi = create_purchase_invoice(supplier = "Test TDS Supplier") pi.submit() invoices.append(pi) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index f22f3a10091..4bc29da2c7d 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -10,4 +10,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Check", "default": 1 }); + + frappe.query_reports["Balance Sheet"]["filters"].push({ + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check" + }); }); diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index b292bd33b9b..0f802d88cc4 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -45,8 +45,8 @@ def execute(filters=None): if(filters.get("show_cumulative")): last_total = period_data[0] - period_data[1] - - period_data[2] = period_data[0] - period_data[1] + + period_data[2] = period_data[0] - period_data[1] row += period_data totals[2] = totals[0] - totals[1] if filters["period"] != "Yearly" : @@ -60,7 +60,7 @@ def validate_filters(filters): frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) def get_columns(filters): - columns = [_(filters.get("budget_against")) + ":Link/%s:80"%(filters.get("budget_against")), _("Account") + ":Link/Account:80"] + columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] group_months = False if filters["period"] == "Monthly" else True @@ -71,7 +71,7 @@ def get_columns(filters): if filters["period"] == "Yearly": labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])] for label in labels: - columns.append(label+":Float:80") + columns.append(label+":Float:150") else: for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]: if group_months: @@ -79,20 +79,20 @@ def get_columns(filters): else: label = label % formatdate(from_date, format_string="MMM") - columns.append(label+":Float:80") + columns.append(label+":Float:150") if filters["period"] != "Yearly" : - return columns + [_("Total Budget") + ":Float:80", _("Total Actual") + ":Float:80", - _("Total Variance") + ":Float:80"] + return columns + [_("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150", + _("Total Variance") + ":Float:150"] else: return columns - + def get_cost_centers(filters): cond = "and 1=1" if filters.get("budget_against") == "Cost Center": cond = "order by lft" - return frappe.db.sql_list("""select name from `tab{tab}` where company=%s + return frappe.db.sql_list("""select name from `tab{tab}` where company=%s {cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company")) #Get cost center & target details @@ -109,7 +109,7 @@ def get_cost_center_target_details(filters): """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), (filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) - + #Get target distribution details of accounts of cost center def get_target_distribution_details(filters): @@ -118,7 +118,7 @@ def get_target_distribution_details(filters): from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1): target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation)) - + return target_details #Get actual details from gl entry @@ -129,7 +129,7 @@ def get_actual_details(name, filters): if filters.get("budget_against") == "Cost Center": cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"]) cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt) - + ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year, MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b @@ -159,7 +159,7 @@ def get_cost_center_account_month_map(filters): for ccd in cost_center_target_details: actual_details = get_actual_details(ccd.budget_against, filters) - + for month_id in range(1, 13): month = datetime.date(2013, month_id, 1).strftime('%B') cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\ @@ -172,7 +172,7 @@ def get_cost_center_account_month_map(filters): if ccd.monthly_distribution else 100.0/12 tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100 - + for ad in actual_details.get(ccd.account, []): if ad.month_name == month: tav_dict.actual += flt(ad.debit) - flt(ad.credit) diff --git a/erpnext/accounts/report/cash_flow/cash_flow.js b/erpnext/accounts/report/cash_flow/cash_flow.js index 391f57beac6..04221110930 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.js +++ b/erpnext/accounts/report/cash_flow/cash_flow.js @@ -15,4 +15,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "label": __("Accumulated Values"), "fieldtype": "Check" }); + + frappe.query_reports["Cash Flow"]["filters"].push({ + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check" + }); }); \ No newline at end of file diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index f048c1acda5..0f9813947a7 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -14,8 +14,8 @@ def execute(filters=None): if cint(frappe.db.get_single_value('Accounts Settings', 'use_custom_cash_flow')): from erpnext.accounts.report.cash_flow.custom_cash_flow import execute as execute_custom return execute_custom(filters=filters) - - period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, + + period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year, filters.periodicity, filters.accumulated_values, filters.company) cash_flow_accounts = get_cash_flow_accounts() @@ -25,18 +25,18 @@ def execute(filters=None): accumulated_values=filters.accumulated_values, ignore_closing_entries=True, ignore_accumulated_values_for_fy= True) expense = get_data(filters.company, "Expense", "Debit", period_list, filters=filters, accumulated_values=filters.accumulated_values, ignore_closing_entries=True, ignore_accumulated_values_for_fy= True) - + net_profit_loss = get_net_profit_loss(income, expense, period_list, filters.company) data = [] company_currency = frappe.get_cached_value('Company', filters.company, "default_currency") - + for cash_flow_account in cash_flow_accounts: section_data = [] data.append({ - "account_name": cash_flow_account['section_header'], + "account_name": cash_flow_account['section_header'], "parent_account": None, - "indent": 0.0, + "indent": 0.0, "account": cash_flow_account['section_header'] }) @@ -44,18 +44,18 @@ def execute(filters=None): # add first net income in operations section if net_profit_loss: net_profit_loss.update({ - "indent": 1, + "indent": 1, "parent_account": cash_flow_accounts[0]['section_header'] }) data.append(net_profit_loss) section_data.append(net_profit_loss) for account in cash_flow_account['account_types']: - account_data = get_account_type_based_data(filters.company, - account['account_type'], period_list, filters.accumulated_values) + account_data = get_account_type_based_data(filters.company, + account['account_type'], period_list, filters.accumulated_values, filters) account_data.update({ "account_name": account['label'], - "account": account['label'], + "account": account['label'], "indent": 1, "parent_account": cash_flow_account['section_header'], "currency": company_currency @@ -63,7 +63,7 @@ def execute(filters=None): data.append(account_data) section_data.append(account_data) - add_total_row_account(data, section_data, cash_flow_account['section_footer'], + add_total_row_account(data, section_data, cash_flow_account['section_footer'], period_list, company_currency) add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency) @@ -105,13 +105,15 @@ def get_cash_flow_accounts(): # combine all cash flow accounts for iteration return [operation_accounts, investing_accounts, financing_accounts] -def get_account_type_based_data(company, account_type, period_list, accumulated_values): +def get_account_type_based_data(company, account_type, period_list, accumulated_values, filters): data = {} total = 0 for period in period_list: start_date = get_start_date(period, accumulated_values, company) - amount = get_account_type_based_gl_data(company, start_date, period['to_date'], account_type) + amount = get_account_type_based_gl_data(company, start_date, + period['to_date'], account_type, filters) + if amount and account_type == "Depreciation": amount *= -1 @@ -121,14 +123,24 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_ data["total"] = total return data -def get_account_type_based_gl_data(company, start_date, end_date, account_type): +def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters): + cond = "" + + if filters.finance_book: + cond = " and finance_book = '%s'" %(frappe.db.escape(filters.finance_book)) + if filters.include_default_book_entries: + company_fb = frappe.db.get_value("Company", company, 'default_finance_book') + + cond = """ and finance_book in ('%s', '%s') + """ %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb)) + gl_sum = frappe.db.sql_list(""" select sum(credit) - sum(debit) from `tabGL Entry` where company=%s and posting_date >= %s and posting_date <= %s and voucher_type != 'Period Closing Voucher' - and account in ( SELECT name FROM tabAccount WHERE account_type = %s) - """, (company, start_date, end_date, account_type)) + and account in ( SELECT name FROM tabAccount WHERE account_type = %s) {cond} + """.format(cond=cond), (company, start_date, end_date, account_type)) return gl_sum[0] if gl_sum and gl_sum[0] else 0 @@ -154,7 +166,7 @@ def add_total_row_account(out, data, label, period_list, currency, consolidated key = period if consolidated else period['key'] total_row.setdefault(key, 0.0) total_row[key] += row.get(key, 0.0) - + total_row.setdefault("total", 0.0) total_row["total"] += row["total"] diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 7b373f0d9ae..e69a993e8ce 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -55,5 +55,10 @@ frappe.query_reports["Consolidated Financial Statement"] = { "fieldtype": "Check", "default": 0 }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check" + } ] } diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 1bb8580f644..ed000bbac4c 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -355,7 +355,8 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g "lft": root_lft, "rgt": root_rgt, "company": d.name, - "finance_book": filters.get("finance_book") + "finance_book": filters.get("finance_book"), + "company_fb": frappe.db.get_value("Company", d.name, 'default_finance_book') }, as_dict=True) @@ -386,7 +387,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions.append("gl.posting_date >= %(from_date)s") if filters.get("finance_book"): - additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") + if filters.get("include_default_book_entries"): + additional_conditions.append("finance_book in (%(finance_book)s, %(company_fb)s)") + else: + additional_conditions.append("finance_book in (%(finance_book)s)") return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index fe030c5f1ce..9765310fc9b 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -359,7 +359,8 @@ def set_gl_entries_by_account( "to_date": to_date, "cost_center": filters.cost_center, "project": filters.project, - "finance_book": filters.get("finance_book") + "finance_book": filters.get("finance_book"), + "company_fb": frappe.db.get_value("Company", company, 'default_finance_book') }, as_dict=True) @@ -393,7 +394,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions.append("cost_center in %(cost_center)s") if filters.get("finance_book"): - additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") + if filters.get("include_default_book_entries"): + additional_conditions.append("finance_book in (%(finance_book)s, %(company_fb)s)") + else: + additional_conditions.append("finance_book in (%(finance_book)s)") return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index e74c16af6fe..e162606b7ac 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -216,6 +216,11 @@ frappe.query_reports["General Ledger"] = { "fieldname": "show_opening_entries", "label": __("Show Opening Entries"), "fieldtype": "Check" + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check" } ] } diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 289689694c6..2c72ac0eed1 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -128,13 +128,16 @@ def get_gl_entries(filters): order_by_statement = "order by posting_date, voucher_type, voucher_no" if filters.get("group_by") == _("Group by Voucher (Consolidated)"): - group_by_statement = """group by voucher_type, voucher_no, account, - cost_center, against_voucher_type, against_voucher, posting_date""" + group_by_statement = "group by voucher_type, voucher_no, account, cost_center" select_fields = """, sum(debit) as debit, sum(credit) as credit, sum(debit_in_account_currency) as debit_in_account_currency, sum(credit_in_account_currency) as credit_in_account_currency""" + if filters.get("include_default_book_entries"): + filters['company_fb'] = frappe.db.get_value("Company", + filters.get("company"), 'default_finance_book') + gl_entries = frappe.db.sql( """ select @@ -190,7 +193,10 @@ def get_conditions(filters): conditions.append("project in %(project)s") if filters.get("finance_book"): - conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") + if filters.get("include_default_book_entries"): + conditions.append("finance_book in (%(finance_book)s, %(company_fb)s)") + else: + conditions.append("finance_book in (%(finance_book)s)") from frappe.desk.reportview import build_match_conditions match_conditions = build_match_conditions("GL Entry") diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js index 250e516d7d2..7741a45feeb 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js @@ -41,6 +41,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldname": "accumulated_values", "label": __("Accumulated Values"), "fieldtype": "Check" + }, + { + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), + "fieldtype": "Check" } ); }); diff --git a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py index 62843e74efc..a51c4276301 100644 --- a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py +++ b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py @@ -47,8 +47,8 @@ class TestSalesPaymentSummary(unittest.TestCase): pe.submit() mop = get_mode_of_payments(filters) - self.assertTrue('Credit Card' in mop.values()[0]) - self.assertTrue('Cash' in mop.values()[0]) + self.assertTrue('Credit Card' in list(mop.values())[0]) + self.assertTrue('Cash' in list(mop.values())[0]) # Cancel all Cash payment entry and check if this mode of payment is still fetched. payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Cash", "docstatus": 1}, fields=["name", "docstatus"]) @@ -57,8 +57,8 @@ class TestSalesPaymentSummary(unittest.TestCase): pe.cancel() mop = get_mode_of_payments(filters) - self.assertTrue('Credit Card' in mop.values()[0]) - self.assertTrue('Cash' not in mop.values()[0]) + self.assertTrue('Credit Card' in list(mop.values())[0]) + self.assertTrue('Cash' not in list(mop.values())[0]) def test_get_mode_of_payments_details(self): filters = get_filters() @@ -84,7 +84,7 @@ class TestSalesPaymentSummary(unittest.TestCase): mopd = get_mode_of_payment_details(filters) - mopd_values = mopd.values()[0] + mopd_values = list(mopd.values())[0] for mopd_value in mopd_values: if mopd_value[0] == "Credit Card": cc_init_amount = mopd_value[1] @@ -96,7 +96,7 @@ class TestSalesPaymentSummary(unittest.TestCase): pe.cancel() mopd = get_mode_of_payment_details(filters) - mopd_values = mopd.values()[0] + mopd_values = list(mopd.values())[0] for mopd_value in mopd_values: if mopd_value[0] == "Credit Card": cc_final_amount = mopd_value[1] diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 6b18c5d8731..5758b0bc6b1 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -105,7 +105,7 @@ def get_rootwise_opening_balances(filters, report_type): if filters.finance_book: fb_conditions = " and finance_book = %(finance_book)s" if filters.include_default_book_entries: - fb_conditions = " and (finance_book in (%(finance_book)s, %(company_fb)s) or finance_book is null)" + fb_conditions = " and (finance_book in (%(finance_book)s, %(company_fb)s))" additional_conditions += fb_conditions @@ -180,20 +180,28 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": d["opening_debit"] -= d["opening_credit"] - d["opening_credit"] = 0.0 - total_row["opening_debit"] += d["opening_debit"] + d["closing_debit"] -= d["closing_credit"] + + # For opening + check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit") + + # For closing + check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit") + if d["root_type"] == "Liability" or d["root_type"] == "Income": d["opening_credit"] -= d["opening_debit"] - d["opening_debit"] = 0.0 - total_row["opening_credit"] += d["opening_credit"] - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["closing_debit"] -= d["closing_credit"] - d["closing_credit"] = 0.0 - total_row["closing_debit"] += d["closing_debit"] - if d["root_type"] == "Liability" or d["root_type"] == "Income": d["closing_credit"] -= d["closing_debit"] - d["closing_debit"] = 0.0 - total_row["closing_credit"] += d["closing_credit"] + + # For opening + check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit") + + # For closing + check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit") + + total_row["opening_debit"] += d["opening_debit"] + total_row["closing_debit"] += d["closing_debit"] + total_row["opening_credit"] += d["opening_credit"] + total_row["closing_credit"] += d["closing_credit"] return total_row @@ -219,8 +227,6 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr if d.account_number else d.account_name) } - prepare_opening_and_closing(d) - for key in value_fields: row[key] = flt(d.get(key, 0.0), 3) @@ -295,22 +301,11 @@ def get_columns(): } ] -def prepare_opening_and_closing(d): - d["closing_debit"] = d["opening_debit"] + d["debit"] - d["closing_credit"] = d["opening_credit"] + d["credit"] +def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column): + # If opening debit has negetive value then move it to opening credit and vice versa. - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["opening_debit"] -= d["opening_credit"] - d["opening_credit"] = 0.0 - - if d["root_type"] == "Liability" or d["root_type"] == "Income": - d["opening_credit"] -= d["opening_debit"] - d["opening_debit"] = 0.0 - - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["closing_debit"] -= d["closing_credit"] - d["closing_credit"] = 0.0 - - if d["root_type"] == "Liability" or d["root_type"] == "Income": - d["closing_credit"] -= d["closing_debit"] - d["closing_debit"] = 0.0 + if d[dr_or_cr] < 0: + d[switch_to_column] = abs(d[dr_or_cr]) + d[dr_or_cr] = 0.0 + else: + d[switch_to_column] = 0.0 diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 72f5c627a71..45f7b30ae8a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -291,16 +291,19 @@ class Asset(AccountsController): def validate_expected_value_after_useful_life(self): for row in self.get('finance_books'): - accumulated_depreciation_after_full_schedule = max([d.accumulated_depreciation_amount - for d in self.get("schedules") if cint(d.finance_book_id) == row.idx]) + accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount + for d in self.get("schedules") if cint(d.finance_book_id) == row.idx] - asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule), - self.precision('gross_purchase_amount')) + if accumulated_depreciation_after_full_schedule: + accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule) - if row.expected_value_after_useful_life < asset_value_after_full_schedule: - frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") - .format(row.idx, asset_value_after_full_schedule)) + asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule), + self.precision('gross_purchase_amount')) + + if row.expected_value_after_useful_life < asset_value_after_full_schedule: + frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") + .format(row.idx, asset_value_after_full_schedule)) def validate_cancellation(self): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index ac3c3507027..56425a0dcb4 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -36,7 +36,7 @@ class AssetValueAdjustment(Document): fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \ get_depreciation_accounts(asset) - depreciation_cost_center, depreciation_series = frappe.get_cached_value('Company', asset.company, + depreciation_cost_center, depreciation_series = frappe.get_cached_value('Company', asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]) je = frappe.new_doc("Journal Entry") @@ -75,8 +75,8 @@ class AssetValueAdjustment(Document): rate_per_day = flt(d.value_after_depreciation) / flt(total_days) from_date = self.date else: - no_of_depreciations = len([e.name for e in asset.schedules - if (cint(s.finance_book_id) == d.idx and not e.journal_entry)]) + no_of_depreciations = len([s.name for s in asset.schedules + if (cint(s.finance_book_id) == d.idx and not s.journal_entry)]) value_after_depreciation = d.value_after_depreciation for data in asset.schedules: diff --git a/erpnext/assets/doctype/location/test_location.py b/erpnext/assets/doctype/location/test_location.py index 22d25b5e11c..c98b0b0936c 100644 --- a/erpnext/assets/doctype/location/test_location.py +++ b/erpnext/assets/doctype/location/test_location.py @@ -25,9 +25,12 @@ class TestLocation(unittest.TestCase): temp['features'][0]['properties']['feature_of'] = location formatted_locations.extend(temp['features']) - formatted_location_string = str(formatted_locations) test_location = frappe.get_doc('Location', 'Test Location Area') test_location.save() - self.assertEqual(formatted_location_string, str(json.loads(test_location.get('location'))['features'])) + test_location_features = json.loads(test_location.get('location'))['features'] + ordered_test_location_features = sorted(test_location_features, key=lambda x: x['properties']['feature_of']) + ordered_formatted_locations = sorted(formatted_locations, key=lambda x: x['properties']['feature_of']) + + self.assertEqual(ordered_formatted_locations, ordered_test_location_features) self.assertEqual(area, test_location.get('area')) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index 124910e35de..1c39d8818c4 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -40,7 +40,7 @@ def get_products_details(): products_response = call_mws_method(products.get_matching_product,marketplaceid=marketplace, asins=asin_list) - matching_products_list = products_response.parsed + matching_products_list = products_response.parsed for product in matching_products_list: skus = [row["sku"] for row in sku_asin if row["asin"]==product.ASIN] for sku in skus: @@ -116,7 +116,7 @@ def call_mws_method(mws_method, *args, **kwargs): mws_settings = frappe.get_doc("Amazon MWS Settings") max_retries = mws_settings.max_retry_limit - for x in xrange(0, max_retries): + for x in range(0, max_retries): try: response = mws_method(*args, **kwargs) return response diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js index 376d0c0a5a5..22ba5ad473e 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.js @@ -2,9 +2,8 @@ frappe.ui.form.on("Employee Attendance Tool", { refresh: function(frm) { frm.disable_save(); }, - + onload: function(frm) { - frm.doc.department = frm.doc.branch = frm.doc.company = "All"; frm.set_value("date", frappe.datetime.get_today()); erpnext.employee_attendance_tool.load_employees(frm); }, @@ -24,7 +23,7 @@ frappe.ui.form.on("Employee Attendance Tool", { company: function(frm) { erpnext.employee_attendance_tool.load_employees(frm); } - + }); diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index ea5f4bdecab..32fcee1abe4 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -17,12 +17,11 @@ def get_employees(date, department = None, branch = None, company = None): attendance_not_marked = [] attendance_marked = [] filters = {"status": "Active", "date_of_joining": ["<=", date]} - if department != "All": - filters["department"] = department - if branch != "All": - filters["branch"] = branch - if company != "All": - filters["company"] = company + + for field, value in {'department': department, + 'branch': branch, 'company': company}.items(): + if value: + filters[field] = value employee_list = frappe.get_list("Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name") marked_employee = {} diff --git a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js index e71ce1276b5..b73dcf8ac36 100644 --- a/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js +++ b/erpnext/hr/doctype/employee_benefit_application/employee_benefit_application.js @@ -2,20 +2,8 @@ // For license information, please see license.txt frappe.ui.form.on('Employee Benefit Application', { - setup: function(frm) { - if(!frm.doc.employee || !frm.doc.date) { - frappe.throw(__("Please select Employee and Date first")); - } else { - frm.set_query("earning_component", "employee_benefits", function() { - return { - query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components", - filters: {date: frm.doc.date, employee: frm.doc.employee} - }; - }); - } - }, - employee: function(frm) { + frm.trigger('set_earning_component'); var method, args; if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ method = "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining"; @@ -35,6 +23,21 @@ frappe.ui.form.on('Employee Benefit Application', { get_max_benefits(frm, method, args); } }, + + date: function(frm) { + frm.trigger('set_earning_component'); + }, + + set_earning_component: function(frm) { + if(!frm.doc.employee && !frm.doc.date) return; + frm.set_query("earning_component", "employee_benefits", function() { + return { + query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components", + filters: {date: frm.doc.date, employee: frm.doc.employee} + }; + }); + }, + payroll_period: function(frm) { var method, args; if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index ffd786836a5..d3b960d29eb 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -445,6 +445,8 @@ class SalarySlip(TransactionBase): if not overwrite and component_row.default_amount: amount += component_row.default_amount + else: + component_row.default_amount = amount component_row.amount = amount component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date diff --git a/erpnext/hr/report/salary_register/salary_register.py b/erpnext/hr/report/salary_register/salary_register.py index 3326ac7a6c8..9c45a628a3f 100644 --- a/erpnext/hr/report/salary_register/salary_register.py +++ b/erpnext/hr/report/salary_register/salary_register.py @@ -19,12 +19,12 @@ def execute(filters=None): data = [] for ss in salary_slips: row = [ss.name, ss.employee, ss.employee_name, ss.branch, ss.department, ss.designation, - ss.company, ss.start_date, ss.end_date, ss.leave_withut_pay, ss.payment_days] + 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') if not ss.department == None: columns[4] = columns[4].replace('-1','120') if not ss.designation == None: columns[5] = columns[5].replace('-1','120') - if not ss.leave_withut_pay == None: columns[9] = columns[9].replace('-1','130') + if not ss.leave_without_pay == None: columns[9] = columns[9].replace('-1','130') for e in earning_types: @@ -117,4 +117,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 \ No newline at end of file + return ss_ded_map diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 2f2c40ef2d4..b5abb733b01 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -18,6 +18,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty from frappe.utils.csvutils import getlink from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty from erpnext.utilities.transaction_base import validate_uom_is_integer +from six import text_type class OverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass @@ -591,10 +592,10 @@ def make_timesheet(production_order, company): @frappe.whitelist() def add_timesheet_detail(timesheet, args): - if isinstance(timesheet, unicode): + if isinstance(timesheet, text_type): timesheet = frappe.get_doc('Timesheet', timesheet) - if isinstance(args, unicode): + if isinstance(args, text_type): args = json.loads(args) timesheet.append('time_logs', args) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b6083ef44c0..ae06b5dba35 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -533,7 +533,7 @@ erpnext.patches.v11_0.create_department_records_for_each_company erpnext.patches.v11_0.make_location_from_warehouse erpnext.patches.v11_0.make_asset_finance_book_against_old_entries erpnext.patches.v11_0.check_buying_selling_in_currency_exchange -erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 +erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019 erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07 erpnext.patches.v11_0.rename_overproduction_percent_field erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom diff --git a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py index 01f84a03136..c7c76355400 100644 --- a/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py +++ b/erpnext/patches/v11_0/move_item_defaults_to_child_table_for_multicompany.py @@ -17,10 +17,8 @@ def execute(): frappe.reload_doc('stock', 'doctype', 'item_default') frappe.reload_doc('stock', 'doctype', 'item') - if frappe.db.a_row_exists('Item Default'): return - companies = frappe.get_all("Company") - if len(companies) == 1: + if len(companies) == 1 and not frappe.get_all("Item Default", limit=1): try: frappe.db.sql(''' INSERT INTO `tabItem Default` @@ -35,32 +33,64 @@ def execute(): except: pass else: - item_details = frappe.get_all("Item", fields=["name", "default_warehouse", "buying_cost_center", - "expense_account", "selling_cost_center", "income_account"], limit=100) + item_details = frappe.db.sql(""" SELECT name, default_warehouse, + buying_cost_center, expense_account, selling_cost_center, income_account + FROM tabItem + WHERE + name not in (select distinct parent from `tabItem Default`) and ifnull(disabled, 0) = 0""" + , as_dict=1) - for item in item_details: - item_defaults = [] + items_default_data = {} + for item_data in item_details: + for d in [["default_warehouse", "Warehouse"], ["expense_account", "Account"], + ["income_account", "Account"], ["buying_cost_center", "Cost Center"], + ["selling_cost_center", "Cost Center"]]: + if item_data.get(d[0]): + company = frappe.get_value(d[1], item_data.get(d[0]), "company", cache=True) - def insert_into_item_defaults(doc_field_name, doc_field_value, company): - for d in item_defaults: - if d.get("company") == company: - d[doc_field_name] = doc_field_value - return - item_defaults.append({ - "company": company, - doc_field_name: doc_field_value - }) + if item_data.name not in items_default_data: + items_default_data[item_data.name] = {} - for d in [ - ["default_warehouse", "Warehouse"], ["expense_account", "Account"], ["income_account", "Account"], - ["buying_cost_center", "Cost Center"], ["selling_cost_center", "Cost Center"] - ]: - if item.get(d[0]): - company = frappe.get_value(d[1], item.get(d[0]), "company", cache=True) - insert_into_item_defaults(d[0], item.get(d[0]), company) + company_wise_data = items_default_data[item_data.name] - doc = frappe.get_doc("Item", item.name) - doc.extend("item_defaults", item_defaults) + if company not in company_wise_data: + company_wise_data[company] = {} - for child_doc in doc.item_defaults: - child_doc.db_insert() \ No newline at end of file + default_data = company_wise_data[company] + default_data[d[0]] = item_data.get(d[0]) + + to_insert_data = [] + + # items_default_data data structure will be as follow + # { + # 'item_code 1': {'company 1': {'default_warehouse': 'Test Warehouse 1'}}, + # 'item_code 2': { + # 'company 1': {'default_warehouse': 'Test Warehouse 1'}, + # 'company 2': {'default_warehouse': 'Test Warehouse 1'} + # } + # } + + for item_code, companywise_item_data in items_default_data.items(): + for company, item_default_data in companywise_item_data.items(): + to_insert_data.append(( + frappe.generate_hash("", 10), + item_code, + 'Item', + 'item_defaults', + company, + item_default_data.get('default_warehouse'), + item_default_data.get('expense_account'), + item_default_data.get('income_account'), + item_default_data.get('buying_cost_center'), + item_default_data.get('selling_cost_center'), + )) + + if to_insert_data: + frappe.db.sql(''' + INSERT INTO `tabItem Default` + ( + `name`, `parent`, `parenttype`, `parentfield`, `company`, `default_warehouse`, + `expense_account`, `income_account`, `buying_cost_center`, `selling_cost_center` + ) + VALUES {} + '''.format(', '.join(['%s'] * len(to_insert_data))), tuple(to_insert_data)) \ No newline at end of file diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index f1179033bed..32f0428fcd8 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -103,8 +103,8 @@ class TestTimesheet(unittest.TestCase): { "billable": 1, "activity_type": "_Test Activity Type", - "from_type": now_datetime(), - "hours": 3, + "from_time": now_datetime(), + "to_time": now_datetime() + datetime.timedelta(hours=3), "company": "_Test Company" } ) @@ -113,8 +113,8 @@ class TestTimesheet(unittest.TestCase): { "billable": 1, "activity_type": "_Test Activity Type", - "from_type": now_datetime(), - "hours": 3, + "from_time": now_datetime(), + "to_time": now_datetime() + datetime.timedelta(hours=3), "company": "_Test Company" } ) diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index 929a13f6683..76379f1de2e 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -30,23 +30,23 @@ def get_columns(): "options": "Timesheet", "width": 150 }, - { - "label": _("Billable Hours"), - "fieldtype": "Float", - "fieldname": "total_billable_hours", - "width": 50 - }, { "label": _("Working Hours"), "fieldtype": "Float", "fieldname": "total_hours", - "width": 50 + "width": 150 + }, + { + "label": _("Billable Hours"), + "fieldtype": "Float", + "fieldname": "total_billable_hours", + "width": 150 }, { "label": _("Billing Amount"), "fieldtype": "Currency", "fieldname": "amount", - "width": 100 + "width": 150 } ] diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1a9ea8288c8..d0ebce8c429 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1290,7 +1290,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/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index df50884ce71..0202bbb872a 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -233,30 +233,32 @@ erpnext.SerialNoBatchSelector = Class.extend({ get_batch_fields: function() { var me = this; return [ - {fieldtype:'Section Break', label: __('Batches')}, - {fieldname: 'batches', fieldtype: 'Table', + { fieldtype: 'Section Break', label: __('Batches') }, + { + fieldname: 'batches', fieldtype: 'Table', fields: [ { - fieldtype:'Link', - fieldname:'batch_no', - options: 'Batch', - label: __('Select Batch'), - in_list_view:1, - get_query: function() { + 'fieldtype': 'Link', + 'read_only': 0, + 'fieldname': 'batch_no', + 'options': 'Batch', + 'label': __('Select Batch'), + 'in_list_view': 1, + get_query: function () { return { - filters: {item: me.item_code }, - query: 'erpnext.controllers.queries.get_batch_numbers' - }; + filters: { item: me.item_code }, + query: 'erpnext.controllers.queries.get_batch_numbers' + }; }, - onchange: function(e) { + change: function () { let val = this.get_value(); - if(val.length === 0) { + if (val.length === 0) { this.grid_row.on_grid_fields_dict .available_qty.set_value(0); return; } let selected_batches = this.grid.grid_rows.map((row) => { - if(row === this.grid_row) { + if (row === this.grid_row) { return ""; } @@ -264,12 +266,12 @@ erpnext.SerialNoBatchSelector = Class.extend({ return row.on_grid_fields_dict.batch_no.get_value(); } }); - if(selected_batches.includes(val)) { + if (selected_batches.includes(val)) { this.set_value(""); frappe.throw(__(`Batch ${val} already selected.`)); return; } - if(me.warehouse_details.name) { + if (me.warehouse_details.name) { frappe.call({ method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', args: { @@ -292,31 +294,32 @@ erpnext.SerialNoBatchSelector = Class.extend({ } }, { - fieldtype:'Float', - read_only:1, - fieldname:'available_qty', - label: __('Available'), - in_list_view:1, - default: 0, - onchange: function() { + 'fieldtype': 'Float', + 'read_only': 1, + 'fieldname': 'available_qty', + 'label': __('Available'), + 'in_list_view': 1, + 'default': 0, + change: function () { this.grid_row.on_grid_fields_dict.selected_qty.set_value('0'); } }, { - fieldtype:'Float', - fieldname:'selected_qty', - label: __('Qty'), - in_list_view:1, + 'fieldtype': 'Float', + 'read_only': 0, + 'fieldname': 'selected_qty', + 'label': __('Qty'), + 'in_list_view': 1, 'default': 0, - onchange: function(e) { + change: function () { var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value(); var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value(); var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value(); - if(batch_no.length === 0 && parseInt(selected_qty)!==0) { + if (batch_no.length === 0 && parseInt(selected_qty) !== 0) { frappe.throw(__("Please select a batch")); } - if(me.warehouse_details.type === 'Source Warehouse' && + if (me.warehouse_details.type === 'Source Warehouse' && parseFloat(available_qty) < parseFloat(selected_qty)) { this.set_value('0'); @@ -332,7 +335,7 @@ erpnext.SerialNoBatchSelector = Class.extend({ ], in_place_edit: true, data: this.data, - get_data: function() { + get_data: function () { return this.data; }, } diff --git a/erpnext/regional/report/datev/__init__.py b/erpnext/regional/report/datev/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js new file mode 100644 index 00000000000..1e000b673e6 --- /dev/null +++ b/erpnext/regional/report/datev/datev.js @@ -0,0 +1,32 @@ +frappe.query_reports["DATEV"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company") || frappe.defaults.get_global_default("Company"), + "reqd": 1 + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "default": frappe.datetime.month_start(), + "fieldtype": "Date", + "reqd": 1 + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "default": frappe.datetime.now_date(), + "fieldtype": "Date", + "reqd": 1 + } + ], + onload: function(query_report) { + query_report.page.add_inner_button("Download DATEV Export", () => { + const filters = JSON.stringify(query_report.get_values()); + window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`); + }); + } +}; diff --git a/erpnext/regional/report/datev/datev.json b/erpnext/regional/report/datev/datev.json new file mode 100644 index 00000000000..80a866cbf5c --- /dev/null +++ b/erpnext/regional/report/datev/datev.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 0, + "creation": "2019-04-24 08:45:16.650129", + "disabled": 0, + "icon": "octicon octicon-repo-pull", + "color": "#4CB944", + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "module": "Regional", + "name": "DATEV", + "owner": "Administrator", + "ref_doctype": "GL Entry", + "report_name": "DATEV", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py new file mode 100644 index 00000000000..50aed084aba --- /dev/null +++ b/erpnext/regional/report/datev/datev.py @@ -0,0 +1,373 @@ +# coding: utf-8 +""" +Provide a report and downloadable CSV according to the German DATEV format. + +- Query report showing only the columns that contain data, formatted nicely for + dispay to the user. +- CSV download functionality `download_datev_csv` that provides a CSV file with + all required columns. Used to import the data into the DATEV Software. +""" +from __future__ import unicode_literals +import json +from six import string_types +import frappe +from frappe import _ +import pandas as pd + + +def execute(filters=None): + """Entry point for frappe.""" + validate_filters(filters) + result = get_gl_entries(filters, as_dict=0) + columns = get_columns() + + return columns, result + + +def validate_filters(filters): + """Make sure all mandatory filters are present.""" + if not filters.get('company'): + frappe.throw(_('{0} is mandatory').format(_('Company'))) + + if not filters.get('from_date'): + frappe.throw(_('{0} is mandatory').format(_('From Date'))) + + if not filters.get('to_date'): + frappe.throw(_('{0} is mandatory').format(_('To Date'))) + + +def get_columns(): + """Return the list of columns that will be shown in query report.""" + columns = [ + { + "label": "Umsatz (ohne Soll/Haben-Kz)", + "fieldname": "Umsatz (ohne Soll/Haben-Kz)", + "fieldtype": "Currency", + }, + { + "label": "Soll/Haben-Kennzeichen", + "fieldname": "Soll/Haben-Kennzeichen", + "fieldtype": "Data", + }, + { + "label": "Kontonummer", + "fieldname": "Kontonummer", + "fieldtype": "Data", + }, + { + "label": "Gegenkonto (ohne BU-Schlüssel)", + "fieldname": "Gegenkonto (ohne BU-Schlüssel)", + "fieldtype": "Data", + }, + { + "label": "Belegdatum", + "fieldname": "Belegdatum", + "fieldtype": "Date", + }, + { + "label": "Buchungstext", + "fieldname": "Buchungstext", + "fieldtype": "Text", + }, + { + "label": "Beleginfo - Art 1", + "fieldname": "Beleginfo - Art 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 1", + "fieldname": "Beleginfo - Inhalt 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Art 2", + "fieldname": "Beleginfo - Art 2", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 2", + "fieldname": "Beleginfo - Inhalt 2", + "fieldtype": "Data", + } + ] + + return columns + + +def get_gl_entries(filters, as_dict): + """ + Get a list of accounting entries. + + Select GL Entries joined with Account and Party Account in order to get the + account numbers. Returns a list of accounting entries. + + Arguments: + filters -- dict of filters to be passed to the sql query + as_dict -- return as list of dicts [0,1] + """ + gl_entries = frappe.db.sql(""" + select + + /* either debit or credit amount; always positive */ + case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)', + + /* 'H' when credit, 'S' when debit */ + case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen', + + /* account number or, if empty, party account number */ + coalesce(acc.account_number, acc_pa.account_number) as 'Kontonummer', + + /* against number or, if empty, party against number */ + coalesce(acc_against.account_number, acc_against_pa.account_number) as 'Gegenkonto (ohne BU-Schlüssel)', + + gl.posting_date as 'Belegdatum', + gl.remarks as 'Buchungstext', + gl.voucher_type as 'Beleginfo - Art 1', + gl.voucher_no as 'Beleginfo - Inhalt 1', + gl.against_voucher_type as 'Beleginfo - Art 2', + gl.against_voucher as 'Beleginfo - Inhalt 2' + + from `tabGL Entry` gl + + /* Statistisches Konto (Debitoren/Kreditoren) */ + left join `tabParty Account` pa + on gl.against = pa.parent + and gl.company = pa.company + + /* Kontonummer */ + left join `tabAccount` acc + on gl.account = acc.name + + /* Gegenkonto-Nummer */ + left join `tabAccount` acc_against + on gl.against = acc_against.name + + /* Statistische Kontonummer */ + left join `tabAccount` acc_pa + on pa.account = acc_pa.name + + /* Statistische Gegenkonto-Nummer */ + left join `tabAccount` acc_against_pa + on pa.account = acc_against_pa.name + + where gl.company = %(company)s + and DATE(gl.posting_date) >= %(from_date)s + and DATE(gl.posting_date) <= %(to_date)s + order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) + + return gl_entries + + +def get_datev_csv(data): + """ + Fill in missing columns and return a CSV in DATEV Format. + + Arguments: + data -- array of dictionaries + """ + columns = [ + # All possible columns must tbe listed here, because DATEV requires them to + # be present in the CSV. + # --- + # Umsatz + "Umsatz (ohne Soll/Haben-Kz)", + "Soll/Haben-Kennzeichen", + "WKZ Umsatz", + "Kurs", + "Basis-Umsatz", + "WKZ Basis-Umsatz", + # Konto/Gegenkonto + "Kontonummer", + "Gegenkonto (ohne BU-Schlüssel)", + "BU-Schlüssel", + # Datum + "Belegdatum", + # Belegfelder + "Belegfeld 1", + "Belegfeld 2", + # Weitere Felder + "Skonto", + "Buchungstext", + # OPOS-Informationen + "Postensperre", + "Diverse Adressnummer", + "Geschäftspartnerbank", + "Sachverhalt", + "Zinssperre", + # Digitaler Beleg + "Beleglink", + # Beleginfo + "Beleginfo - Art 1", + "Beleginfo - Inhalt 1", + "Beleginfo - Art 2", + "Beleginfo - Inhalt 2", + "Beleginfo - Art 3", + "Beleginfo - Inhalt 3", + "Beleginfo - Art 4", + "Beleginfo - Inhalt 4", + "Beleginfo - Art 5", + "Beleginfo - Inhalt 5", + "Beleginfo - Art 6", + "Beleginfo - Inhalt 6", + "Beleginfo - Art 7", + "Beleginfo - Inhalt 7", + "Beleginfo - Art 8", + "Beleginfo - Inhalt 8", + # Kostenrechnung + "Kost 1 - Kostenstelle", + "Kost 2 - Kostenstelle", + "Kost-Menge", + # Steuerrechnung + "EU-Land u. UStID", + "EU-Steuersatz", + "Abw. Versteuerungsart", + # L+L Sachverhalt + "Sachverhalt L+L", + "Funktionsergänzung L+L", + # Funktion Steuerschlüssel 49 + "BU 49 Hauptfunktionstyp", + "BU 49 Hauptfunktionsnummer", + "BU 49 Funktionsergänzung", + # Zusatzinformationen + "Zusatzinformation - Art 1", + "Zusatzinformation - Inhalt 1", + "Zusatzinformation - Art 2", + "Zusatzinformation - Inhalt 2", + "Zusatzinformation - Art 3", + "Zusatzinformation - Inhalt 3", + "Zusatzinformation - Art 4", + "Zusatzinformation - Inhalt 4", + "Zusatzinformation - Art 5", + "Zusatzinformation - Inhalt 5", + "Zusatzinformation - Art 6", + "Zusatzinformation - Inhalt 6", + "Zusatzinformation - Art 7", + "Zusatzinformation - Inhalt 7", + "Zusatzinformation - Art 8", + "Zusatzinformation - Inhalt 8", + "Zusatzinformation - Art 9", + "Zusatzinformation - Inhalt 9", + "Zusatzinformation - Art 10", + "Zusatzinformation - Inhalt 10", + "Zusatzinformation - Art 11", + "Zusatzinformation - Inhalt 11", + "Zusatzinformation - Art 12", + "Zusatzinformation - Inhalt 12", + "Zusatzinformation - Art 13", + "Zusatzinformation - Inhalt 13", + "Zusatzinformation - Art 14", + "Zusatzinformation - Inhalt 14", + "Zusatzinformation - Art 15", + "Zusatzinformation - Inhalt 15", + "Zusatzinformation - Art 16", + "Zusatzinformation - Inhalt 16", + "Zusatzinformation - Art 17", + "Zusatzinformation - Inhalt 17", + "Zusatzinformation - Art 18", + "Zusatzinformation - Inhalt 18", + "Zusatzinformation - Art 19", + "Zusatzinformation - Inhalt 19", + "Zusatzinformation - Art 20", + "Zusatzinformation - Inhalt 20", + # Mengenfelder LuF + "Stück", + "Gewicht", + # Forderungsart + "Zahlweise", + "Forderungsart", + "Veranlagungsjahr", + "Zugeordnete Fälligkeit", + # Weitere Felder + "Skontotyp", + # Anzahlungen + "Auftragsnummer", + "Buchungstyp", + "USt-Schlüssel (Anzahlungen)", + "EU-Land (Anzahlungen)", + "Sachverhalt L+L (Anzahlungen)", + "EU-Steuersatz (Anzahlungen)", + "Erlöskonto (Anzahlungen)", + # Stapelinformationen + "Herkunft-Kz", + # Technische Identifikation + "Buchungs GUID", + # Kostenrechnung + "Kost-Datum", + # OPOS-Informationen + "SEPA-Mandatsreferenz", + "Skontosperre", + # Gesellschafter und Sonderbilanzsachverhalt + "Gesellschaftername", + "Beteiligtennummer", + "Identifikationsnummer", + "Zeichnernummer", + # OPOS-Informationen + "Postensperre bis", + # Gesellschafter und Sonderbilanzsachverhalt + "Bezeichnung SoBil-Sachverhalt", + "Kennzeichen SoBil-Buchung", + # Stapelinformationen + "Festschreibung", + # Datum + "Leistungsdatum", + "Datum Zuord. Steuerperiode", + # OPOS-Informationen + "Fälligkeit", + # Konto/Gegenkonto + "Generalumkehr (GU)", + # Steuersatz für Steuerschlüssel + "Steuersatz", + "Land" + ] + + empty_df = pd.DataFrame(columns=columns) + data_df = pd.DataFrame.from_records(data) + + result = empty_df.append(data_df) + result["Belegdatum"] = pd.to_datetime(result["Belegdatum"]) + + return result.to_csv( + sep=b';', + # European decimal seperator + decimal=',', + # Windows "ANSI" encoding + encoding='latin_1', + # format date as DDMM + date_format='%d%m', + # Windows line terminator + line_terminator=b'\r\n', + # Do not number rows + index=False, + # Use all columns defined above + columns=columns + ) + + +@frappe.whitelist() +def download_datev_csv(filters=None): + """ + Provide accounting entries for download in DATEV format. + + Validate the filters, get the data, produce the CSV file and provide it for + download. Can be called like this: + + GET /api/method/erpnext.regional.report.datev.datev.download_datev_csv + + Arguments / Params: + filters -- dict of filters to be passed to the sql query + """ + if isinstance(filters, string_types): + filters = json.loads(filters) + + validate_filters(filters) + data = get_gl_entries(filters, as_dict=1) + + filename = 'DATEV_Buchungsstapel_{}-{}_bis_{}'.format( + filters.get('company'), + filters.get('from_date'), + filters.get('to_date') + ) + + frappe.response['result'] = get_datev_csv(data) + frappe.response['doctype'] = filename + frappe.response['type'] = 'csv' diff --git a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py index e1b6c4db4f9..e903c9f00a4 100644 --- a/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py +++ b/erpnext/regional/report/fichier_des_ecritures_comptables_[fec]/fichier_des_ecritures_comptables_[fec].py @@ -69,13 +69,13 @@ def get_gl_entries(filters): gl_entries = frappe.db.sql(""" select - gl.posting_date as GlPostDate, gl.name as GlName, gl.account, gl.transaction_date, + gl.posting_date as GlPostDate, gl.name as GlName, gl.account, gl.transaction_date, sum(gl.debit) as debit, sum(gl.credit) as credit, sum(gl.debit_in_account_currency) as debitCurr, sum(gl.credit_in_account_currency) as creditCurr, - gl.voucher_type, gl.voucher_no, gl.against_voucher_type, - gl.against_voucher, gl.account_currency, gl.against, + gl.voucher_type, gl.voucher_no, gl.against_voucher_type, + gl.against_voucher, gl.account_currency, gl.against, gl.party_type, gl.party, - inv.name as InvName, inv.title as InvTitle, inv.posting_date as InvPostDate, + inv.name as InvName, inv.title as InvTitle, inv.posting_date as InvPostDate, pur.name as PurName, pur.title as PurTitle, pur.posting_date as PurPostDate, jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle, pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle, @@ -84,7 +84,7 @@ def get_gl_entries(filters): emp.employee_name, emp.name as empName, stu.title as student_name, stu.name as stuName, member_name, mem.name as memName - + from `tabGL Entry` gl left join `tabSales Invoice` inv on gl.voucher_no = inv.name left join `tabPurchase Invoice` pur on gl.voucher_no = pur.name @@ -124,7 +124,7 @@ def get_result_as_list(data, filters): if account_number[0] is not None: CompteNum = account_number[0] else: - frappe.throw(_("Account number for account {0} is not available.
Please setup your Chart of Accounts correctly.").format(account.name)) + frappe.throw(_("Account number for account {0} is not available.
Please setup your Chart of Accounts correctly.").format(d.get("account"))) if d.get("party_type") == "Customer": CompAuxNum = d.get("cusName") diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js index dcb81cb087e..dfdf9dc0958 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.js @@ -29,7 +29,20 @@ frappe.query_reports["HSN-wise-summary of outward supplies"] = { "placeholder":"Company GSTIN", "options": [""], "width": "80" - } + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "width": "80" + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "width": "80" + }, + ], onload: (report) => { fetch_gstins(report); diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index e938e29c440..222dfa1eb78 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -88,7 +88,9 @@ def get_conditions(filters): for opts in (("company", " and company=%(company)s"), ("gst_hsn_code", " and gst_hsn_code=%(gst_hsn_code)s"), - ("company_gstin", " and company_gstin=%(company_gstin)s")): + ("company_gstin", " and company_gstin=%(company_gstin)s"), + ("from_date", " and posting_date >= %(from_date)s"), + ("to_date", "and posting_date <= %(to_date)s")): if filters.get(opts[0]): conditions += opts[1] diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 1a31db92cb3..02d293d3236 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -90,22 +90,29 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({ if (this.frm.doc.docstatus===0) { this.frm.add_custom_button(__('Opportunity'), function() { - var setters = {}; - if(me.frm.doc.quotation_to == "Customer" && me.frm.doc.party_name) { - setters.customer = me.frm.doc.party_name || undefined; - } else if (me.frm.doc.quotation_to == "Lead" && me.frm.doc.party_name) { - setters.lead = me.frm.doc.party_name || undefined; - } erpnext.utils.map_current_doc({ method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation", source_doctype: "Opportunity", target: me.frm, - setters: setters, + setters: [ + { + label: "Party", + fieldname: "party_name", + fieldtype: "Link", + options: me.frm.doc.quotation_to, + default: me.frm.doc.party_name || undefined + }, + { + label: "Opportunity Type", + fieldname: "opportunity_type", + fieldtype: "Link", + options: "Opportunity Type", + default: me.frm.doc.order_type || undefined + } + ], get_query_filters: { status: ["not in", ["Lost", "Closed"]], - company: me.frm.doc.company, - // cannot set opportunity_type as setter, as the fieldname is order_type - opportunity_type: me.frm.doc.order_type, + company: me.frm.doc.company } }) }, __("Get items from"), "btn-default"); diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index c344d266949..8b339ba82f2 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -161,6 +161,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): "Sales Team": { "doctype": "Sales Team", "add_if_empty": True + }, + "Payment Schedule": { + "doctype": "Payment Schedule", + "add_if_empty": True } }, target_doc, set_missing_values, ignore_permissions=ignore_permissions) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 6cdb4f88840..53b3e73f5e2 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -219,13 +219,19 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( method: "erpnext.selling.doctype.quotation.quotation.make_sales_order", source_doctype: "Quotation", target: me.frm, - setters: { - customer: me.frm.doc.customer || undefined - }, + setters: [ + { + label: "Customer", + fieldname: "party_name", + fieldtype: "Link", + options: "Customer", + default: me.frm.doc.customer || undefined + } + ], get_query_filters: { company: me.frm.doc.company, docstatus: 1, - status: ["!=", "Lost"], + status: ["!=", "Lost"] } }) }, __("Get items from")); diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 184c6bd70d6..a490082c42c 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -574,8 +574,8 @@ def make_delivery_note(source_name, target_doc=None): if item: target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \ - or item.get("selling_cost_center") \ - or item_group.get("selling_cost_center") + or item.get("buying_cost_center") \ + or item_group.get("buying_cost_center") target_doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { 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 3e6ee86df00..a16a77f1340 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -737,6 +737,17 @@ class POSCart { const customer = this.frm.doc.customer; this.customer_field.set_value(customer); + + if (this.numpad) { + const disable_btns = this.disable_numpad_control() + const enable_btns = [__('Rate'), __('Disc')] + + if (disable_btns) { + enable_btns.filter(btn => !disable_btns.includes(btn)) + } + + this.numpad.enable_buttons(enable_btns); + } } get_grand_total() { @@ -1507,6 +1518,16 @@ class NumberPad { } } + enable_buttons(btns) { + btns.forEach((btn) => { + const $btn = this.get_btn(btn); + $btn.prop("disabled", false) + $btn.hover(() => { + $btn.css('cursor','pointer'); + }) + }) + } + set_class() { for (const btn in this.add_class) { const class_name = this.add_class[btn]; diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index 01b4734bf58..bc8c7493d5c 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -20,8 +20,7 @@ class DeliveryTrip(Document): # Google Maps returns distances in meters by default self.default_distance_uom = frappe.db.get_single_value("Global Defaults", "default_distance_unit") or "Meter" self.uom_conversion_factor = frappe.db.get_value("UOM Conversion Factor", - {"from_uom": "Meter", "to_uom": self.default_distance_uom}, - "value") + {"from_uom": "Meter", "to_uom": self.default_distance_uom}, "value") def validate(self): self.validate_stop_addresses() @@ -139,7 +138,7 @@ class DeliveryTrip(Document): # Include last leg in the final distance calculation self.uom = self.default_distance_uom total_distance = sum([leg.get("distance", {}).get("value", 0.0) - for leg in directions.get("legs")]) # in meters + for leg in directions.get("legs")]) # in meters self.total_distance = total_distance * self.uom_conversion_factor else: idx += len(route) - 1 @@ -358,8 +357,12 @@ def notify_customers(delivery_trip): email_recipients = [] for stop in delivery_trip.delivery_stops: - contact_info = frappe.db.get_value("Contact", stop.contact, - ["first_name", "last_name", "email_id", "gender"], as_dict=1) + contact_info = frappe.db.get_value("Contact", stop.contact, ["first_name", "last_name", "email_id"], as_dict=1) + + context.update({"items": []}) + if stop.delivery_note: + items = frappe.get_all("Delivery Note Item", filters={"parent": stop.delivery_note, "docstatus": 1}, fields=["*"]) + context.update({"items": items}) if contact_info and contact_info.email_id: context.update(stop.as_dict()) @@ -369,9 +372,9 @@ def notify_customers(delivery_trip): dispatch_template = frappe.get_doc("Email Template", dispatch_template_name) frappe.sendmail(recipients=contact_info.email_id, - subject=dispatch_template.subject, - message=frappe.render_template(dispatch_template.response, context), - attachments=get_attachments(stop)) + subject=dispatch_template.subject, + message=frappe.render_template(dispatch_template.response, context), + attachments=get_attachments(stop)) stop.db_set("email_sent_to", contact_info.email_id) email_recipients.append(contact_info.email_id) @@ -388,9 +391,7 @@ def get_attachments(delivery_stop): return [] dispatch_attachment = frappe.db.get_single_value("Delivery Settings", "dispatch_attachment") - attachments = frappe.attach_print("Delivery Note", - delivery_stop.delivery_note, - file_name="Delivery Note", - print_format=dispatch_attachment) + attachments = frappe.attach_print("Delivery Note", delivery_stop.delivery_note, + file_name="Delivery Note", print_format=dispatch_attachment) return [attachments] diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 16fd811fee3..c5697eda7a8 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -67,8 +67,6 @@ class Item(WebsiteGenerator): from frappe.model.naming import set_name_by_naming_series set_name_by_naming_series(self) self.item_code = self.name - elif not self.item_code: - msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1) self.item_code = strip(self.item_code) self.name = self.item_code diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index fe1319103a8..a0fc3f34e20 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -186,6 +186,7 @@ frappe.ui.form.on('Material Request', { var values = d.get_values(); if(!values) return; values["company"] = frm.doc.company; + if(!frm.doc.company) frappe.throw(__("Company field is required")); frappe.call({ method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", args: values, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 5c1f573c457..3866c934a8d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -8,7 +8,7 @@ from frappe.utils import flt, cint, nowdate from frappe import throw, _ import frappe.defaults -from frappe.utils import getdate +from frappe.utils import getdate, cint from erpnext.controllers.buying_controller import BuyingController from erpnext.accounts.utils import get_account_currency from frappe.desk.notifications import clear_doctype_notifications @@ -128,7 +128,7 @@ class PurchaseReceipt(BuyingController): self.company, self.base_grand_total) self.update_prevdoc_status() - if self.per_billed < 100: + if cint(self.per_billed) < 100: self.update_billing_status() else: self.status = "Completed" diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index c19228fea8b..dfcce5f820b 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -562,7 +562,7 @@ class TestStockEntry(unittest.TestCase): for d in stock_entry.get("items"): if d.item_code != "_Test FG Item 2": rm_cost += flt(d.amount) - fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount + fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items")))[0].amount self.assertEqual(fg_cost, flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2)) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js index b7461c485f6..264642856da 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js @@ -10,8 +10,23 @@ frappe.query_reports["Total Stock Summary"] = { "fieldtype": "Select", "width": "80", "reqd": 1, - "options": ["","Warehouse", "Company"], - "default": "Warehouse" + "options": ["", "Warehouse", "Company"], + "change": function() { + let group_by = frappe.query_report.get_filter_value("group_by") + let company_filter = frappe.query_report.get_filter("company") + if (group_by == "Company") { + company_filter.df.reqd = 0; + company_filter.df.hidden = 1; + frappe.query_report.set_filter_value("company", ""); + company_filter.refresh(); + } + else { + company_filter.df.reqd = 1; + company_filter.df.hidden = 0; + company_filter.refresh(); + frappe.query_report.refresh(); + } + } }, { "fieldname": "company", @@ -23,4 +38,4 @@ frappe.query_reports["Total Stock Summary"] = { "reqd": 1 }, ] -} +} \ No newline at end of file diff --git a/erpnext/templates/pages/search_help.py b/erpnext/templates/pages/search_help.py index 4a4b0dbd934..cd767b3099c 100644 --- a/erpnext/templates/pages/search_help.py +++ b/erpnext/templates/pages/search_help.py @@ -5,6 +5,7 @@ from jinja2 import utils from html2text import html2text from frappe.utils import sanitize_html from frappe.utils.global_search import search +from six import text_type def get_context(context): context.no_cache = 1 @@ -12,7 +13,7 @@ def get_context(context): query = str(utils.escape(sanitize_html(frappe.form_dict.q))) context.title = _('Help Results for') context.query = query - + context.route = '/search_help' d = frappe._dict() d.results_sections = get_help_results_sections(query) @@ -73,7 +74,7 @@ def prepare_api_results(api, topics_data): for topic in topics_data: route = api.base_url + '/' + (api.post_route + '/' if api.post_route else "") for key in api.post_route_key_list.split(','): - route += unicode(topic[key]) + route += text_type(topic[key]) results.append(frappe._dict({ 'title': topic[api.post_title_key], diff --git a/travis/run-tests.sh b/travis/run-tests.sh new file mode 100755 index 00000000000..7cfd64833b7 --- /dev/null +++ b/travis/run-tests.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +if [[ $TEST_TYPE == 'Server Side Test' ]]; then + bench run-tests --app erpnext --coverage + +elif [[ $TEST_TYPE == 'Patch Test' ]]; then + wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz + bench --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz --mariadb-root-password travis + bench migrate +fi