From 7a7add5001f65c74237c0e8d04daaa50be9a7866 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 18:36:57 +0200 Subject: [PATCH 01/38] fix(report view): explicitly set column width for --- .../regional/report/datev/datev_constants.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py index a059ed365a..e063703005 100644 --- a/erpnext/regional/report/datev/datev_constants.py +++ b/erpnext/regional/report/datev/datev_constants.py @@ -465,60 +465,71 @@ QUERY_REPORT_COLUMNS = [ "label": "Umsatz (ohne Soll/Haben-Kz)", "fieldname": "Umsatz (ohne Soll/Haben-Kz)", "fieldtype": "Currency", + "width": 100 }, { "label": "Soll/Haben-Kennzeichen", "fieldname": "Soll/Haben-Kennzeichen", "fieldtype": "Data", + "width": 100 }, { "label": "Konto", "fieldname": "Konto", "fieldtype": "Data", + "width": 100 }, { "label": "Gegenkonto (ohne BU-Schlüssel)", "fieldname": "Gegenkonto (ohne BU-Schlüssel)", "fieldtype": "Data", + "width": 100 }, { "label": "Belegdatum", "fieldname": "Belegdatum", "fieldtype": "Date", + "width": 100 }, { "label": "Belegfeld 1", "fieldname": "Belegfeld 1", "fieldtype": "Data", + "width": 150 }, { "label": "Buchungstext", "fieldname": "Buchungstext", "fieldtype": "Text", + "width": 300 }, { "label": "Beleginfo - Art 1", "fieldname": "Beleginfo - Art 1", "fieldtype": "Link", - "options": "DocType" + "options": "DocType", + "width": 100 }, { "label": "Beleginfo - Inhalt 1", "fieldname": "Beleginfo - Inhalt 1", "fieldtype": "Dynamic Link", - "options": "Beleginfo - Art 1" + "options": "Beleginfo - Art 1", + "width": 150 }, { "label": "Beleginfo - Art 2", "fieldname": "Beleginfo - Art 2", "fieldtype": "Link", - "options": "DocType" + "options": "DocType", + "width": 100 }, { "label": "Beleginfo - Inhalt 2", "fieldname": "Beleginfo - Inhalt 2", "fieldtype": "Dynamic Link", - "options": "Beleginfo - Art 2" + "options": "Beleginfo - Art 2", + "width": 150 } ] From 55c048f56c1b2ed91e467edd4700cf8da448c514 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 18:50:02 +0200 Subject: [PATCH 02/38] refactor: query meta data only once --- erpnext/regional/report/datev/datev.py | 35 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index a8e40cc493..02296a9356 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -8,17 +8,18 @@ Provide a report and downloadable CSV according to the German DATEV format. all required columns. Used to import the data into the DATEV Software. """ from __future__ import unicode_literals + import datetime import json -import zlib import zipfile import six +import frappe +import pandas as pd + +from frappe import _ from csv import QUOTE_NONNUMERIC from six import BytesIO from six import string_types -import frappe -from frappe import _ -import pandas as pd from .datev_constants import DataCategory from .datev_constants import Transactions from .datev_constants import DebtorsCreditors @@ -287,9 +288,7 @@ def get_datev_csv(data, filters, csv_class): def get_header(filters, csv_class): - coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts") - description = filters.get("voucher_type", csv_class.FORMAT_NAME) - coa_used = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "") + description = filters.get('voucher_type', csv_class.FORMAT_NAME) header = [ # DATEV format @@ -316,13 +315,13 @@ def get_header(filters, csv_class): # J = Imported by -- stays empty '', # K = Tax consultant number (Beraternummer) - frappe.get_value("DATEV Settings", filters.get("company"), "consultant_number"), + filters.get('consultant_number', '0000000'), # L = Tax client number (Mandantennummer) - frappe.get_value("DATEV Settings", filters.get("company"), "client_number"), + filters.get('client_number', '00000'), # M = Start of the fiscal year (Wirtschaftsjahresbeginn) frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"), # N = Length of account numbers (Sachkontenlänge) - '4', + '%d' % filters.get('acc_len', 4), # O = Transaction batch start date (YYYYMMDD) frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"), # P = Transaction batch end date (YYYYMMDD) @@ -348,7 +347,7 @@ def get_header(filters, csv_class): # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" '0', # V = Default currency, for example, "EUR" - '"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"), + '"%s"' % filters.get('default_currency', 'EUR'), # reserviert '', # Derivatskennzeichen @@ -358,7 +357,7 @@ def get_header(filters, csv_class): # reserviert '', # SKR - '"%s"' % coa_used, + '"%s"' % filters.get('skr', '04'), # Branchen-Lösungs-ID '', # reserviert @@ -389,6 +388,18 @@ def download_datev_csv(filters=None): validate(filters) + # set chart of accounts used + coa = frappe.get_value('Company', filters.get('company'), 'chart_of_accounts') + filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '') + + # set account number length + account_numbers = frappe.get_list('Account', fields=['account_number'], filters={'is_group': 0, 'account_number': ('!=', '')}) + filters['acc_len'] = max([len(a.account_number) for a in account_numbers]) + + filters['consultant_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'consultant_number') + filters['client_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'client_number') + filters['default_currency'] = frappe.get_value('Company', filters.get('company'), 'default_currency') + # This is where my zip will be written zip_buffer = BytesIO() # This is my zip file From 2976831560ddf9e16aae6b500f0e4f1b621e60d2 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 19:15:03 +0200 Subject: [PATCH 03/38] fix: truncate account names to max length --- erpnext/regional/report/datev/datev.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 02296a9356..51ddfc780d 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -227,9 +227,18 @@ def get_suppliers(filters): def get_account_names(filters): - return frappe.get_list("Account", - fields=["account_number as Konto", "name as Kontenbeschriftung"], - filters={"company": filters.get("company"), "is_group": "0"}) + return frappe.db.sql(""" + SELECT + + account_number as 'Konto', + LEFT(account_name, 40) as 'Kontenbeschriftung', + 'de-DE' as 'Sprach-ID' + + FROM `tabAccount` + WHERE company = %(company)s + AND is_group = 0 + AND account_number != '' + """, filters, as_dict=1) def get_datev_csv(data, filters, csv_class): From 53445aa25adc664d5eac55ba9e16a1eb4365ea38 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 19:15:49 +0200 Subject: [PATCH 04/38] fix: customer and supplier data --- erpnext/regional/report/datev/datev.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index 51ddfc780d..d7036c55af 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -131,8 +131,10 @@ def get_customers(filters): SELECT acc.account_number as 'Konto', - cus.customer_name as 'Name (Adressatentyp Unternehmen)', - case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp', + CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)', + CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', + CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', + CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', @@ -141,8 +143,7 @@ def get_customers(filters): con.email_id as 'E-Mail', coalesce(con.mobile_no, con.phone) as 'Telefon', cus.website as 'Internet', - cus.tax_id as 'Steuernummer', - ccl.credit_limit as 'Kreditlimit (Debitor)' + cus.tax_id as 'Steuernummer' FROM `tabParty Account` par @@ -161,10 +162,6 @@ def get_customers(filters): left join `tabContact` con on con.name = cus.customer_primary_contact - left join `tabCustomer Credit Limit` ccl - on ccl.parent = cus.name - and ccl.company = par.company - WHERE par.company = %(company)s AND par.parenttype = 'Customer'""", filters, as_dict=1) @@ -180,8 +177,10 @@ def get_suppliers(filters): SELECT acc.account_number as 'Konto', - sup.supplier_name as 'Name (Adressatentyp Unternehmen)', - case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp', + CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)', + CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)', + CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)', + CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp', adr.address_line1 as 'Straße', adr.pincode as 'Postleitzahl', adr.city as 'Ort', From 30d194d8a7eab77d056999aac61d00666518c005 Mon Sep 17 00:00:00 2001 From: barredterra Date: Mon, 11 May 2020 19:23:54 +0200 Subject: [PATCH 05/38] fix: hide transaction-specific for master data --- erpnext/regional/report/datev/datev.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index d7036c55af..7fec94e740 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -331,11 +331,11 @@ def get_header(filters, csv_class): # N = Length of account numbers (Sachkontenlänge) '%d' % filters.get('acc_len', 4), # O = Transaction batch start date (YYYYMMDD) - frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"), + frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # P = Transaction batch end date (YYYYMMDD) - frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), + frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # Q = Description (for example, "Sales Invoice") Max. 30 chars - '"{}"'.format(_(description)), + '"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # R = Diktatkürzel '', # S = Buchungstyp @@ -350,12 +350,12 @@ def get_header(filters, csv_class): # 40 = Kalkulatorik # 11 = Reserviert # 12 = Reserviert - '0', + '0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # U = Festschreibung # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" '0', # V = Default currency, for example, "EUR" - '"%s"' % filters.get('default_currency', 'EUR'), + '"%s"' % filters.get('default_currency', 'EUR') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # reserviert '', # Derivatskennzeichen From a66ce050ff0e52734d39b29f0936e3919b37e221 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 15 Jun 2020 16:03:12 +0530 Subject: [PATCH 06/38] fix: Minor fixes in loan --- erpnext/loan_management/doctype/loan/loan.py | 4 +++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 2 +- .../doctype/loan_security_price/loan_security_price.json | 5 ++++- .../loan_security_shortfall/loan_security_shortfall.py | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 76e10e5ddd..4e805d4a27 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -235,8 +235,10 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as @frappe.whitelist() def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1): loan_security_pledge_details = frappe.db.sql(""" - SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p + SELECT p.loan_security, sum(p.qty) as qty + FROM `tabLoan Security Pledge` lsp , `tabPledge` p WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1 + GROUP BY p.loan_security """,(loan), as_dict=1) unpledge_request = frappe.new_doc("Loan Security Unpledge") diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index c28994e280..9605045777 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -116,7 +116,7 @@ class LoanRepayment(AccountsController): def allocate_amounts(self, paid_entries): self.set('repayment_details', []) self.principal_amount_paid = 0 - interest_paid = 0 + interest_paid = self.amount_paid - self.penalty_amount if self.amount_paid - self.penalty_amount > 0 and paid_entries: interest_paid = self.amount_paid - self.penalty_amount diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json index db260a4a9e..a55b482bd6 100644 --- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json +++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "LM-LSP-.####", "creation": "2019-09-03 18:20:31.382887", "doctype": "DocType", @@ -46,6 +47,7 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Loan Security Price", + "options": "Company:company:default_currency", "reqd": 1 }, { @@ -79,7 +81,8 @@ "read_only": 1 } ], - "modified": "2019-10-26 09:46:46.069667", + "links": [], + "modified": "2020-06-11 03:41:33.900340", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Price", diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 308c4385d3..99cb0b3d62 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -19,7 +19,9 @@ def update_shortfall_status(loan, security_value): return if security_value >= loan_security_shortfall.shortfall_amount: - frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "status", "Completed") + frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, { + "status", "Completed", + "shortfall_value": loan_security_shortfall.shortfall_amount}) else: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "shortfall_amount", loan_security_shortfall.shortfall_amount - security_value) From d0d9b53361c57d362e7e975f16b48ccbfdc021a8 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 11:49:11 +0530 Subject: [PATCH 07/38] feat: added year filters --- .../salary_slip_deductions_report_filters.js | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/salary_slip_deductions_report_filters.js b/erpnext/public/js/salary_slip_deductions_report_filters.js index 242037991a..2b30e65075 100644 --- a/erpnext/public/js/salary_slip_deductions_report_filters.js +++ b/erpnext/public/js/salary_slip_deductions_report_filters.js @@ -11,8 +11,8 @@ erpnext.salary_slip_deductions_report_filters = { default: frappe.defaults.get_user_default("Company"), }, { - fieldname: "period", - label: __("Period"), + fieldname: "month", + label: __("Month"), fieldtype: "Select", reqd: 1 , options: [ @@ -31,6 +31,12 @@ erpnext.salary_slip_deductions_report_filters = { ], default: frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1 }, + { + fieldname:"year", + label: __("Year"), + fieldtype: "Select", + reqd: 1 + }, { fieldname: "department", label: __("Department"), @@ -43,5 +49,18 @@ erpnext.salary_slip_deductions_report_filters = { fieldtype: "Link", options: "Branch", } - ] + ], + + "onload": function() { + return frappe.call({ + method: "erpnext.regional.report.provident_fund_deductions.provident_fund_deductions.get_years", + callback: function(r) { + var year_filter = frappe.query_report.get_filter('year'); + year_filter.df.options = r.message; + year_filter.df.default = r.message.split("\n")[0]; + year_filter.refresh(); + year_filter.set_input(year_filter.df.default); + } + }); + } } \ No newline at end of file From f9ca29cebd0a0963ec58b8971283d7e0d2868f2b Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 11:49:47 +0530 Subject: [PATCH 08/38] Fix: if there is no component --- .../income_tax_deductions.py | 12 +++++++--- .../salary_payments_via_ecs.py | 8 ++++--- .../professional_tax_deductions.py | 5 +++- .../provident_fund_deductions.py | 24 +++++++++++++++---- 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py index 3bad5879bb..8a79416edb 100644 --- a/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py +++ b/erpnext/payroll/report/income_tax_deductions/income_tax_deductions.py @@ -6,8 +6,8 @@ import frappe, erpnext from frappe import _ def execute(filters=None): - columns = get_columns(filters) data = get_data(filters) + columns = get_columns(filters) if len(data) else [] return columns, data @@ -78,8 +78,11 @@ def get_conditions(filters): if filters.get("company"): conditions.append("sal.company = '%s' " % (filters["company"]) ) - if filters.get("period"): - conditions.append("month(sal.start_date) = '%s' " % (filters["period"])) + if filters.get("month"): + conditions.append("month(sal.start_date) = '%s' " % (filters["month"])) + + if filters.get("year"): + conditions.append("year(start_date) = '%s' " % (filters["year"])) return " and ".join(conditions) @@ -96,6 +99,9 @@ def get_data(filters): component_types = [comp_type[0] for comp_type in component_types] + if not len(component_types): + return [] + conditions = get_conditions(filters) entry = frappe.db.sql(""" select sal.employee, sal.employee_name, sal.posting_date, ded.salary_component, ded.amount,sal.gross_pay diff --git a/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py b/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py index 073bd91300..d09745c37b 100644 --- a/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py +++ b/erpnext/payroll/report/salary_payments_via_ecs/salary_payments_via_ecs.py @@ -84,9 +84,11 @@ def get_conditions(filters): if filters.get("company"): conditions.append("company = '%s' " % (filters["company"]) ) - if filters.get("period"): - conditions.append("month(start_date) = '%s' " % (filters["period"])) - conditions.append("year(start_date) = '%s' " % (frappe.utils.getdate().year)) + if filters.get("month"): + conditions.append("month(start_date) = '%s' " % (filters["month"])) + + if filters.get("year"): + conditions.append("year(start_date) = '%s' " % (filters["year"])) return " and ".join(conditions) diff --git a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py index 900fe963b4..acde68a942 100644 --- a/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py +++ b/erpnext/regional/report/professional_tax_deductions/professional_tax_deductions.py @@ -7,8 +7,8 @@ from frappe import _ from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import get_conditions def execute(filters=None): - columns = get_columns(filters) data = get_data(filters) + columns = get_columns(filters) if len(data) else [] return columns, data @@ -45,6 +45,9 @@ def get_data(filters): component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component` where component_type = 'Professional Tax' """)) + if not len(component_type_dict): + return [] + conditions = get_conditions(filters) entry = frappe.db.sql(""" select sal.employee, sal.employee_name, ded.salary_component, ded.amount diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py index 9f58957fed..a79931ca32 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py @@ -6,8 +6,8 @@ import frappe from frappe import _ def execute(filters=None): - columns = get_columns(filters) data = get_data(filters) + columns = get_columns(filters) if len(data) else [] return columns, data @@ -71,10 +71,13 @@ def get_conditions(filters): conditions.append("sal.branch = '%s' " % (filters["branch"]) ) if filters.get("company"): - conditions.append("sal.company = '%s' " % (filters["company"]) ) + conditions.append("sal.company = '%s' " % (filters["company"])) - if filters.get("period"): - conditions.append("month(sal.start_date) = '%s' " % (filters["period"])) + if filters.get("month"): + conditions.append("month(sal.start_date) = '%s' " % (filters["month"])) + + if filters.get("year"): + conditions.append("year(start_date) = '%s' " % (filters["year"])) if filters.get("mode_of_payment"): conditions.append("sal.mode_of_payment = '%s' " % (filters["mode_of_payment"])) @@ -114,6 +117,9 @@ def get_data(filters): component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component` where component_type in ('Provident Fund', 'Additional Provident Fund', 'Provident Fund Loan')""")) + if not len(component_type_dict): + return [] + entry = frappe.db.sql(""" select sal.name, sal.employee, sal.employee_name, ded.salary_component, ded.amount from `tabSalary Slip` sal, `tabSalary Detail` ded where sal.name = ded.parent @@ -150,4 +156,12 @@ def get_data(filters): data.append(employee) - return data \ No newline at end of file + return data + +@frappe.whitelist() +def get_years(): + year_list = frappe.db.sql_list("""select distinct YEAR(end_date) from `tabSalary Slip` ORDER BY YEAR(end_date) DESC""") + if not year_list: + year_list = [getdate().year] + + return "\n".join(str(year) for year in year_list) \ No newline at end of file From 4e37d25374766f314e154c334d84778d21ac767d Mon Sep 17 00:00:00 2001 From: Afshan Date: Tue, 23 Jun 2020 14:10:40 +0530 Subject: [PATCH 09/38] fix: removed condition that considered "Standard working hours" while creating "timesheet" as it was setting wrong time #20848 --- .../projects/doctype/timesheet/timesheet.js | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index defc18bf4e..5de2930c1c 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -162,19 +162,11 @@ frappe.ui.form.on("Timesheet Detail", { to_time: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; - var time_diff = (moment(child.to_time).diff(moment(child.from_time),"seconds")) / ( 60 * 60 * 24); - var std_working_hours = 0; if(frm._setting_hours) return; var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600; - std_working_hours = time_diff * frappe.working_hours; - - if (std_working_hours < hours && std_working_hours > 0) { - frappe.model.set_value(cdt, cdn, "hours", std_working_hours); - } else { - frappe.model.set_value(cdt, cdn, "hours", hours); - } + frappe.model.set_value(cdt, cdn, "hours", hours); }, time_logs_add: function(frm) { @@ -236,23 +228,12 @@ var calculate_end_time = function(frm, cdt, cdn) { let d = moment(child.from_time); if(child.hours) { - var time_diff = (moment(child.to_time).diff(moment(child.from_time),"seconds")) / (60 * 60 * 24); - var std_working_hours = 0; - var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600; - - std_working_hours = time_diff * frappe.working_hours; - - if (std_working_hours < hours && std_working_hours > 0) { - frappe.model.set_value(cdt, cdn, "hours", std_working_hours); - frappe.model.set_value(cdt, cdn, "to_time", d.add(hours, "hours").format(frappe.defaultDatetimeFormat)); - } else { - d.add(child.hours, "hours"); - frm._setting_hours = true; - frappe.model.set_value(cdt, cdn, "to_time", - d.format(frappe.defaultDatetimeFormat)).then(() => { - frm._setting_hours = false; - }); - } + d.add(child.hours, "hours"); + frm._setting_hours = true; + frappe.model.set_value(cdt, cdn, "to_time", + d.format(frappe.defaultDatetimeFormat)).then(() => { + frm._setting_hours = false; + }); } }; From ece9508eb5a02962a994fbaad0e989e323d3f56c Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 13:47:28 +0530 Subject: [PATCH 10/38] fix: test_tax_for_payroll_period --- erpnext/payroll/doctype/salary_slip/test_salary_slip.py | 1 + .../provident_fund_deductions/provident_fund_deductions.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index f42b9ad11d..be9a2d3728 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -271,6 +271,7 @@ class TestSalarySlip(unittest.TestCase): # as per assigned salary structure 40500 in monthly salary so 236000*5/100/12 frappe.db.sql("""delete from `tabPayroll Period`""") frappe.db.sql("""delete from `tabSalary Component`""") + frappe.db.sql("""delete from `tabAdditional Salary`""") payroll_period = create_payroll_period() diff --git a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py index a79931ca32..084890e54f 100644 --- a/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py +++ b/erpnext/regional/report/provident_fund_deductions/provident_fund_deductions.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import getdate from frappe import _ def execute(filters=None): From a81c54f23a310eea2abc7ecd6092c78e7e8aeaa3 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 24 Jun 2020 11:51:06 +0530 Subject: [PATCH 11/38] fix: 'Condition & Formula Help' appears twice --- erpnext/payroll/doctype/salary_structure/salary_structure.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js index ca458f976f..ad93a2fa4b 100755 --- a/erpnext/payroll/doctype/salary_structure/salary_structure.js +++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js @@ -35,7 +35,9 @@ frappe.ui.form.on('Salary Structure', { d.show() }); - frm.get_field("conditions_and_formula_variable_and_example").$wrapper.append(frm.doc.filters_html).append(help_button) + let help_button_wrapper = frm.get_field("conditions_and_formula_variable_and_example").$wrapper; + help_button_wrapper.empty(); + help_button_wrapper.append(frm.doc.filters_html).append(help_button) frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet) From a07a548622a3b4e4a9dc4868f21f2bde906c450b Mon Sep 17 00:00:00 2001 From: Afshan Date: Wed, 24 Jun 2020 13:07:59 +0530 Subject: [PATCH 12/38] fix: removed "standard working hours" also fixed test cases --- .../doctype/timesheet/test_timesheet.py | 46 ------------------- .../projects/doctype/timesheet/timesheet.py | 12 ----- erpnext/setup/doctype/company/company.json | 8 +--- 3 files changed, 1 insertion(+), 65 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 03b67b1023..a5ce44dcf2 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -140,52 +140,6 @@ class TestTimesheet(unittest.TestCase): settings.ignore_employee_time_overlap = initial_setting settings.save() - def test_timesheet_std_working_hours(self): - emp = make_employee("test_employee_6@salary.com") - - company = frappe.get_doc('Company', "_Test Company") - company.standard_working_hours = 8 - company.save() - - timesheet = frappe.new_doc("Timesheet") - timesheet.employee = emp - timesheet.company = '_Test Company' - timesheet.append( - 'time_logs', - { - "activity_type": "_Test Activity Type", - "from_time": now_datetime(), - "to_time": now_datetime() + datetime.timedelta(days= 4) - } - ) - timesheet.save() - - ts = frappe.get_doc('Timesheet', timesheet.name) - self.assertEqual(ts.total_hours, 32) - ts.submit() - ts.cancel() - - company = frappe.get_doc('Company', "_Test Company") - company.standard_working_hours = 0 - company.save() - - timesheet = frappe.new_doc("Timesheet") - timesheet.employee = emp - timesheet.company = '_Test Company' - timesheet.append( - 'time_logs', - { - "activity_type": "_Test Activity Type", - "from_time": now_datetime(), - "to_time": now_datetime() + datetime.timedelta(days= 4) - } - ) - timesheet.save() - - ts = frappe.get_doc('Timesheet', timesheet.name) - self.assertEqual(ts.total_hours, 96) - ts.submit() - ts.cancel() def make_salary_structure_for_timesheet(employee): salary_structure_name = "Timesheet Salary Structure Test" diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index e90821689b..7fe22bec4b 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -24,7 +24,6 @@ class Timesheet(Document): self.set_status() self.validate_dates() self.validate_time_logs() - self.calculate_std_hours() self.update_cost() self.calculate_total_amounts() self.calculate_percentage_billed() @@ -91,17 +90,6 @@ class Timesheet(Document): self.start_date = getdate(start_date) self.end_date = getdate(end_date) - def calculate_std_hours(self): - std_working_hours = frappe.get_value("Company", self.company, 'standard_working_hours') - - for time in self.time_logs: - if time.from_time and time.to_time: - if flt(std_working_hours) and date_diff(time.to_time, time.from_time): - time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time) - else: - if not time.hours: - time.hours = time_diff_in_hours(time.to_time, time.from_time) - def before_cancel(self): self.set_status() diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 020a93ff6a..ceae634a84 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -22,7 +22,6 @@ "default_letter_head", "default_holiday_list", "default_finance_book", - "standard_working_hours", "default_selling_terms", "default_buying_terms", "default_warehouse_for_sales_return", @@ -238,11 +237,6 @@ "label": "Default Holiday List", "options": "Holiday List" }, - { - "fieldname": "standard_working_hours", - "fieldtype": "Float", - "label": "Standard Working Hours" - }, { "fieldname": "default_warehouse_for_sales_return", "fieldtype": "Link", @@ -730,7 +724,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2020-03-21 18:09:53.534211", + "modified": "2020-06-24 12:45:31.462195", "modified_by": "Administrator", "module": "Setup", "name": "Company", From d3ec1c1d89bc5474775c283eb719b94e390ab335 Mon Sep 17 00:00:00 2001 From: vishdha Date: Tue, 24 Mar 2020 11:31:41 +0530 Subject: [PATCH 13/38] feat: Taxjar Integration Added --- .../doctype/taxjar_settings/__init__.py | 0 .../taxjar_settings/taxjar_settings.js | 9 + .../taxjar_settings/taxjar_settings.json | 110 ++++++++ .../taxjar_settings/taxjar_settings.py | 10 + .../taxjar_settings/test_taxjar_settings.py | 10 + .../taxjar_integration.py | 251 ++++++++++++++++++ erpnext/hooks.py | 14 +- erpnext/patches.txt | 1 + .../v12_0/add_taxjar_integration_field.py | 12 + erpnext/regional/united_states/setup.py | 16 ++ .../doctype/quotation/test_quotation.py | 2 - requirements.txt | 4 +- 12 files changed, 434 insertions(+), 5 deletions(-) create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_settings/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py create mode 100644 erpnext/erpnext_integrations/taxjar_integration.py create mode 100644 erpnext/patches/v12_0/add_taxjar_integration_field.py diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/__init__.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js new file mode 100644 index 0000000000..62d5709f51 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js @@ -0,0 +1,9 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('TaxJar Settings', { + is_sandbox: (frm) => { + frm.toggle_reqd("api_key", !frm.doc.is_sandbox); + frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox); + } +}); diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json new file mode 100644 index 0000000000..c0d60f7a31 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -0,0 +1,110 @@ +{ + "actions": [], + "creation": "2017-06-15 08:21:24.624315", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "is_sandbox", + "taxjar_calculate_tax", + "taxjar_create_transactions", + "credentials", + "api_key", + "cb_keys", + "sandbox_api_key", + "configuration", + "tax_account_head", + "configuration_cb", + "shipping_account_head" + ], + "fields": [ + { + "fieldname": "credentials", + "fieldtype": "Section Break", + "label": "Credentials" + }, + { + "fieldname": "api_key", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Live API Key", + "reqd": 1 + }, + { + "fieldname": "configuration", + "fieldtype": "Section Break", + "label": "Configuration" + }, + { + "fieldname": "tax_account_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Tax Account Head", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "shipping_account_head", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Shipping Account Head", + "options": "Account", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "is_sandbox", + "fieldtype": "Check", + "label": "Sandbox Mode" + }, + { + "fieldname": "sandbox_api_key", + "fieldtype": "Password", + "label": "Sandbox API Key" + }, + { + "fieldname": "configuration_cb", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "taxjar_create_transactions", + "fieldtype": "Check", + "label": "Create TaxJar Transaction" + }, + { + "default": "0", + "fieldname": "taxjar_calculate_tax", + "fieldtype": "Check", + "label": "Enable Tax Calculation" + }, + { + "fieldname": "cb_keys", + "fieldtype": "Column Break" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-04-30 04:38:03.311089", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "TaxJar Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py new file mode 100644 index 0000000000..7f5f0f0e7a --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class TaxJarSettings(Document): + pass diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py new file mode 100644 index 0000000000..7cdfd00956 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/test_taxjar_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestTaxJarSettings(unittest.TestCase): + pass diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py new file mode 100644 index 0000000000..633692dd24 --- /dev/null +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -0,0 +1,251 @@ +import traceback + +import pycountry +import taxjar + +import frappe +from erpnext import get_default_company +from frappe import _ +from frappe.contacts.doctype.address.address import get_company_address + +TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head") +SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head") +TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") +TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") +SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", + "FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO", + "SE", "SI", "SK", "US"] + + +def get_client(): + taxjar_settings = frappe.get_single("TaxJar Settings") + + if not taxjar_settings.is_sandbox: + api_key = taxjar_settings.api_key and taxjar_settings.get_password("api_key") + api_url = taxjar.DEFAULT_API_URL + else: + api_key = taxjar_settings.sandbox_api_key and taxjar_settings.get_password("sandbox_api_key") + api_url = taxjar.SANDBOX_API_URL + + if api_key and api_url: + return taxjar.Client(api_key=api_key, api_url=api_url) + + +def create_transaction(doc, method): + """Create an order transaction in TaxJar""" + + if not TAXJAR_CREATE_TRANSACTIONS: + return + + client = get_client() + + if not client: + return + + sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD]) + + if not sales_tax: + return + + tax_dict = get_tax_data(doc) + + if not tax_dict: + return + + tax_dict['transaction_id'] = doc.name + tax_dict['transaction_date'] = frappe.utils.today() + tax_dict['sales_tax'] = sales_tax + tax_dict['amount'] = doc.total + tax_dict['shipping'] + + try: + client.create_order(tax_dict) + except taxjar.exceptions.TaxJarResponseError as err: + frappe.throw(_(sanitize_error_response(err))) + except Exception as ex: + print(traceback.format_exc(ex)) + + +def delete_transaction(doc, method): + """Delete an existing TaxJar order transaction""" + + if not TAXJAR_CREATE_TRANSACTIONS: + return + + client = get_client() + + if not client: + return + + client.delete_order(doc.name) + + +def get_tax_data(doc): + from_address = get_company_address_details(doc) + from_shipping_state = from_address.get("state") + from_country_code = frappe.db.get_value("Country", from_address.country, "code") + from_country_code = from_country_code.upper() + + to_address = get_shipping_address_details(doc) + to_shipping_state = to_address.get("state") + to_country_code = frappe.db.get_value("Country", to_address.country, "code") + to_country_code = to_country_code.upper() + + if to_country_code not in SUPPORTED_COUNTRY_CODES: + return + + shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD]) + + if to_shipping_state is not None: + to_shipping_state = get_iso_3166_2_state_code(to_address) + + tax_dict = { + 'from_country': from_country_code, + 'from_zip': from_address.pincode, + 'from_state': from_shipping_state, + 'from_city': from_address.city, + 'from_street': from_address.address_line1, + 'to_country': to_country_code, + 'to_zip': to_address.pincode, + 'to_city': to_address.city, + 'to_street': to_address.address_line1, + 'to_state': to_shipping_state, + 'shipping': shipping, + 'amount': doc.net_total + } + + return tax_dict + + +def set_sales_tax(doc, method): + if not TAXJAR_CALCULATE_TAX: + return + + if not doc.items: + return + + # if the party is exempt from sales tax, then set all tax account heads to zero + sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \ + or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax") + + if sales_tax_exempted: + for tax in doc.taxes: + if tax.account_head == TAX_ACCOUNT_HEAD: + tax.tax_amount = 0 + break + + doc.run_method("calculate_taxes_and_totals") + return + + tax_dict = get_tax_data(doc) + + if not tax_dict: + # Remove existing tax rows if address is changed from a taxable state/country + setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD]) + return + + tax_data = validate_tax_request(tax_dict) + + if tax_data is not None: + if not tax_data.amount_to_collect: + setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD]) + elif tax_data.amount_to_collect > 0: + # Loop through tax rows for existing Sales Tax entry + # If none are found, add a row with the tax amount + for tax in doc.taxes: + if tax.account_head == TAX_ACCOUNT_HEAD: + tax.tax_amount = tax_data.amount_to_collect + + doc.run_method("calculate_taxes_and_totals") + break + else: + doc.append("taxes", { + "charge_type": "Actual", + "description": "Sales Tax", + "account_head": TAX_ACCOUNT_HEAD, + "tax_amount": tax_data.amount_to_collect + }) + + doc.run_method("calculate_taxes_and_totals") + + +def validate_tax_request(tax_dict): + """Return the sales tax that should be collected for a given order.""" + + client = get_client() + + if not client: + return + + try: + tax_data = client.tax_for_order(tax_dict) + except taxjar.exceptions.TaxJarResponseError as err: + frappe.throw(_(sanitize_error_response(err))) + else: + return tax_data + + +def get_company_address_details(doc): + """Return default company address details""" + + company_address = get_company_address(get_default_company()).company_address + + if not company_address: + frappe.throw(_("Please set a default company address")) + + company_address = frappe.get_doc("Address", company_address) + return company_address + + +def get_shipping_address_details(doc): + """Return customer shipping address details""" + + if doc.shipping_address_name: + shipping_address = frappe.get_doc("Address", doc.shipping_address_name) + else: + shipping_address = get_company_address_details(doc) + + return shipping_address + + +def get_iso_3166_2_state_code(address): + country_code = frappe.db.get_value("Country", address.get("country"), "code") + + error_message = _("""{0} is not a valid state! Check for typos or enter the ISO code for your state.""").format(address.get("state")) + state = address.get("state").upper().strip() + + # The max length for ISO state codes is 3, excluding the country code + if len(state) <= 3: + # PyCountry returns state code as {country_code}-{state-code} (e.g. US-FL) + address_state = (country_code + "-" + state).upper() + + states = pycountry.subdivisions.get(country_code=country_code.upper()) + states = [pystate.code for pystate in states] + + if address_state in states: + return state + + frappe.throw(_(error_message)) + else: + try: + lookup_state = pycountry.subdivisions.lookup(state) + except LookupError: + frappe.throw(_(error_message)) + else: + return lookup_state.code.split('-')[1] + + +def sanitize_error_response(response): + response = response.full_response.get("detail") + response = response.replace("_", " ") + + sanitized_responses = { + "to zip": "Zipcode", + "to city": "City", + "to state": "State", + "to country": "Country" + } + + for k, v in sanitized_responses.items(): + response = response.replace(k, v) + + return response diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a695896ed..835d92ef5c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -234,8 +234,15 @@ doc_events = { "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" }, "Sales Invoice": { - "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], - "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", + "on_submit": [ + "erpnext.regional.create_transaction_log", + "erpnext.regional.italy.utils.sales_invoice_on_submit", + "erpnext.erpnext_integrations.taxjar_integration.create_transaction" + ], + "on_cancel": [ + "erpnext.regional.italy.utils.sales_invoice_on_cancel", + "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" + ], "on_trash": "erpnext.regional.check_deletion_permission" }, "Purchase Invoice": { @@ -261,6 +268,9 @@ doc_events = { }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" + }, + ('Quotation', 'Sales Order', 'Sales Invoice'): { + 'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"] } } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 17fbcc2190..c7a7abf819 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -706,3 +706,4 @@ execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 +erpnext.patches.v12_0.add_taxjar_integration_field diff --git a/erpnext/patches/v12_0/add_taxjar_integration_field.py b/erpnext/patches/v12_0/add_taxjar_integration_field.py new file mode 100644 index 0000000000..4c823e13bd --- /dev/null +++ b/erpnext/patches/v12_0/add_taxjar_integration_field.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals + +import frappe +from erpnext.regional.united_states.setup import make_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters={'country': 'United States'}) + if not company: + return + + make_custom_fields() diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py index cae28bee8b..2b0ecafebc 100644 --- a/erpnext/regional/united_states/setup.py +++ b/erpnext/regional/united_states/setup.py @@ -14,6 +14,22 @@ def make_custom_fields(update=True): 'Supplier': [ dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id', label='Is IRS 1099 reporting required for supplier?') + ], + 'Sales Order': [ + dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges', + label='Is customer exempted from sales tax?') + ], + 'Sales Invoice': [ + dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_section', + label='Is customer exempted from sales tax?') + ], + 'Customer': [ + dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='represents_company', + label='Is customer exempted from sales tax?') + ], + 'Quotation': [ + dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges', + label='Is customer exempted from sales tax?') ] } create_custom_fields(custom_fields, update=update) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index ee6b429cca..b4c3d79f31 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -280,5 +280,3 @@ def make_quotation(**args): qo.submit() return qo - - diff --git a/requirements.txt b/requirements.txt index 9da537e493..cfd0ab8e07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,8 +4,10 @@ gocardless-pro==1.11.0 googlemaps==3.1.1 pandas==0.24.2 plaid-python==3.4.0 +pycountry==19.8.18 PyGithub==1.44.1 python-stdnum==1.12 +taxjar==1.9.0 +tweepy==3.8.0 Unidecode==1.1.1 WooCommerce==2.1.1 -tweepy==3.8.0 \ No newline at end of file From 7b3700b3ece495af116b79094d46e2fa81211d38 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 25 Jun 2020 13:29:14 +0530 Subject: [PATCH 14/38] fix: handle custom statuses for the pause SLA configuration (#22349) * fix: handle hold time for custom statuses * fix: set total hold time only if hold configurations are enabled Co-authored-by: Himanshu --- erpnext/support/doctype/issue/issue.py | 67 ++++++++++++++------------ 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 883e603fd3..87168e151e 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -79,47 +79,52 @@ class Issue(Document): def handle_hold_time(self, status): if self.service_level_agreement: - # set response and resolution variance as None as the issue is on Hold for status as Replied + # set response and resolution variance as None as the issue is on Hold pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"], filters={"parent": self.service_level_agreement}) hold_statuses = [entry.status for entry in pause_sla_on] update_values = {} - if self.status in hold_statuses and status not in hold_statuses: - update_values['on_hold_since'] = frappe.flags.current_time or now_datetime() - if not self.first_responded_on: - update_values['response_by'] = None - update_values['response_by_variance'] = 0 - update_values['resolution_by'] = None - update_values['resolution_by_variance'] = 0 + if hold_statuses: + if self.status in hold_statuses and status not in hold_statuses: + update_values['on_hold_since'] = frappe.flags.current_time or now_datetime() + if not self.first_responded_on: + update_values['response_by'] = None + update_values['response_by_variance'] = 0 + update_values['resolution_by'] = None + update_values['resolution_by_variance'] = 0 - # calculate hold time when status is changed from Replied to any other status - if self.status not in hold_statuses and status in hold_statuses: - hold_time = self.total_hold_time if self.total_hold_time else 0 - now_time = frappe.flags.current_time or now_datetime() - update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since) + # calculate hold time when status is changed from any hold status to any non-hold status + if self.status not in hold_statuses and status in hold_statuses: + hold_time = self.total_hold_time if self.total_hold_time else 0 + now_time = frappe.flags.current_time or now_datetime() + last_hold_time = 0 + if self.on_hold_since: + # last_hold_time will be added to the sla variables + last_hold_time = time_diff_in_seconds(now_time, self.on_hold_since) + update_values['total_hold_time'] = hold_time + last_hold_time - # re-calculate SLA variables after issue changes from Replied to Open - # add hold time to SLA variables - if self.status == "Open" and status in hold_statuses: - start_date_time = get_datetime(self.service_level_agreement_creation) - priority = get_priority(self) - now_time = frappe.flags.current_time or now_datetime() - hold_time = time_diff_in_seconds(now_time, self.on_hold_since) + # re-calculate SLA variables after issue changes from any hold status to any non-hold status + # add hold time to SLA variables + start_date_time = get_datetime(self.service_level_agreement_creation) + priority = get_priority(self) + now_time = frappe.flags.current_time or now_datetime() - if not self.first_responded_on: - response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time) - update_values['response_by'] = add_to_date(response_by, seconds=round(hold_time)) - response_by_variance = round(time_diff_in_hours(self.response_by, now_time)) - update_values['response_by_variance'] = response_by_variance + (hold_time // 3600) + if not self.first_responded_on: + response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time) + response_by = add_to_date(response_by, seconds=round(last_hold_time)) + response_by_variance = round(time_diff_in_hours(response_by, now_time)) + update_values['response_by'] = response_by + update_values['response_by_variance'] = response_by_variance + (last_hold_time // 3600) - resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time) - update_values['resolution_by'] = add_to_date(resolution_by, seconds=round(hold_time)) - resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_time)) - update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600) - update_values['on_hold_since'] = None + resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time) + resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time)) + resolution_by_variance = round(time_diff_in_hours(resolution_by, now_time)) + update_values['resolution_by'] = resolution_by + update_values['resolution_by_variance'] = resolution_by_variance + (last_hold_time // 3600) + update_values['on_hold_since'] = None - self.db_set(update_values) + self.db_set(update_values) def update_agreement_status(self): if self.service_level_agreement and self.agreement_fulfilled == "Ongoing": From e7c91917aa458e00d2ebac7fee1d5902e3326948 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 25 Jun 2020 13:32:58 +0530 Subject: [PATCH 15/38] fix: setup status indicators for Job Offer and Job Applicant --- .../doctype/job_applicant/job_applicant_list.js | 15 +++++++++++++++ erpnext/hr/doctype/job_offer/job_offer.json | 3 +-- erpnext/hr/doctype/job_offer/job_offer_list.js | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 erpnext/hr/doctype/job_applicant/job_applicant_list.js create mode 100644 erpnext/hr/doctype/job_offer/job_offer_list.js diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_list.js b/erpnext/hr/doctype/job_applicant/job_applicant_list.js new file mode 100644 index 0000000000..3b9141ba79 --- /dev/null +++ b/erpnext/hr/doctype/job_applicant/job_applicant_list.js @@ -0,0 +1,15 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.listview_settings['Job Applicant'] = { + add_fields: ["company", "designation", "job_applicant", "status"], + get_indicator: function (doc) { + if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (["Open", "Replied"].includes(doc.status)) { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (["Hold", "Rejected"].includes(doc.status)) { + return [__(doc.status), "red", "status,=," + doc.status]; + } + } +}; diff --git a/erpnext/hr/doctype/job_offer/job_offer.json b/erpnext/hr/doctype/job_offer/job_offer.json index ccbfdc5383..c0b7f69e1b 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.json +++ b/erpnext/hr/doctype/job_offer/job_offer.json @@ -30,7 +30,6 @@ { "fieldname": "job_applicant", "fieldtype": "Link", - "in_list_view": 1, "label": "Job Applicant", "options": "Job Applicant", "print_hide": 1, @@ -161,7 +160,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2019-12-31 02:40:33.650728", + "modified": "2020-06-25 00:56:24.756395", "modified_by": "Administrator", "module": "HR", "name": "Job Offer", diff --git a/erpnext/hr/doctype/job_offer/job_offer_list.js b/erpnext/hr/doctype/job_offer/job_offer_list.js new file mode 100644 index 0000000000..4fa5be7cc8 --- /dev/null +++ b/erpnext/hr/doctype/job_offer/job_offer_list.js @@ -0,0 +1,15 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.listview_settings['Job Offer'] = { + add_fields: ["company", "designation", "job_applicant", "status"], + get_indicator: function (doc) { + if (doc.status == "Accepted") { + return [__(doc.status), "green", "status,=," + doc.status]; + } else if (doc.status == "Awaiting Response") { + return [__(doc.status), "orange", "status,=," + doc.status]; + } else if (doc.status == "Rejected") { + return [__(doc.status), "red", "status,=," + doc.status]; + } + } +}; From ecff8f958226c0746f0a72ec689e4c895a3afc4e Mon Sep 17 00:00:00 2001 From: Sun Howwrongbum Date: Thu, 25 Jun 2020 14:25:53 +0530 Subject: [PATCH 16/38] fix: UnboundLocalError when setting product_info (#22436) Co-authored-by: Marica --- erpnext/portal/product_configurator/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 6b6b8c579b..f8af30a1c3 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -239,13 +239,12 @@ def get_next_attribute_and_values(item_code, selected_attributes): if exact_match: data = get_product_info_for_website(exact_match[0]) product_info = data.product_info + product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock) if not data.cart_settings.show_price: product_info = None else: product_info = None - product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock) - return { 'next_attribute': next_attribute, 'valid_options_for_attributes': valid_options_for_attributes, From 8a55ec1580f514847311505445b59b21b8d3d8d4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:34:01 +0530 Subject: [PATCH 17/38] fix: Add view ledger button for cancelled docs (#22432) * fix: Add view ledger button for cancelled docs * fix: Add moddified to date in Stock Ledger --- .../invoice_discounting/invoice_discounting.js | 7 ++++--- .../accounts/doctype/journal_entry/journal_entry.js | 7 ++++--- .../accounts/doctype/payment_entry/payment_entry.js | 11 ++++++----- .../period_closing_voucher/period_closing_voucher.js | 5 +++-- erpnext/education/doctype/fees/fees.js | 7 ++++--- erpnext/hr/doctype/expense_claim/expense_claim.js | 7 +++++-- erpnext/loan_management/loan_common.js | 7 +++++-- erpnext/public/js/controllers/stock_controller.js | 10 ++++++---- 8 files changed, 37 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js index 0fab8b70f4..db4f7c423f 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js @@ -188,14 +188,15 @@ frappe.ui.form.on('Invoice Discounting', { }, show_general_ledger: (frm) => { - if(frm.doc.docstatus===1) { + if(frm.doc.docstatus > 0) { cur_frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: frm.doc.name, from_date: frm.doc.posting_date, - to_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), company: frm.doc.company, - group_by: "Group by Voucher (Consolidated)" + group_by: "Group by Voucher (Consolidated)", + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 5685f839fe..a09face791 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -13,15 +13,16 @@ frappe.ui.form.on("Journal Entry", { refresh: function(frm) { erpnext.toggle_naming_series(); - if(frm.doc.docstatus==1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, + "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'), "company": frm.doc.company, "finance_book": frm.doc.finance_book, - "group_by_voucher": 0 + "group_by": '', + "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __('View')); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index a378a51cdf..42c9fdeba4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -172,8 +172,8 @@ frappe.ui.form.on('Payment Entry', { frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); frm.toggle_display("base_received_amount", ( - frm.doc.paid_to_account_currency != company_currency - && frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency + frm.doc.paid_to_account_currency != company_currency + && frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency && frm.doc.base_paid_amount != frm.doc.base_received_amount )); @@ -234,14 +234,15 @@ frappe.ui.form.on('Payment Entry', { }, show_general_ledger: function(frm) { - if(frm.doc.docstatus==1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Ledger'), function() { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, + "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'), "company": frm.doc.company, - group_by: "" + "group_by": "", + "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, "fa fa-table"); diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js index 87e02fef1b..e923d4ed5e 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js @@ -25,9 +25,10 @@ frappe.ui.form.on('Period Closing Voucher', { frappe.route_options = { "voucher_no": frm.doc.name, "from_date": frm.doc.posting_date, - "to_date": frm.doc.posting_date, + "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'), "company": frm.doc.company, - group_by_voucher: 0 + "group_by": "", + "show_cancelled_entries": frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, "fa fa-table"); diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js index 17ef44954b..867866fbf1 100644 --- a/erpnext/education/doctype/fees/fees.js +++ b/erpnext/education/doctype/fees/fees.js @@ -55,14 +55,15 @@ frappe.ui.form.on("Fees", { frm.set_df_property('posting_date', 'read_only', 1); frm.set_df_property('posting_time', 'read_only', 1); } - if(frm.doc.docstatus===1) { + if(frm.doc.docstatus > 0) { frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: frm.doc.name, from_date: frm.doc.posting_date, - to_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), company: frm.doc.company, - group_by_voucher: false + group_by: '', + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 6bb9af9826..fa63ec2834 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -213,12 +213,15 @@ frappe.ui.form.on("Expense Claim", { refresh: function(frm) { frm.trigger("toggle_fields"); - if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") { + if(frm.doc.docstatus > 0 && frm.doc.approval_status !== "Rejected") { frm.add_custom_button(__('Accounting Ledger'), function() { frappe.route_options = { voucher_no: frm.doc.name, company: frm.doc.company, - group_by_voucher: false + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + group_by: '', + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); diff --git a/erpnext/loan_management/loan_common.js b/erpnext/loan_management/loan_common.js index 3a47a88cbe..d9dd415296 100644 --- a/erpnext/loan_management/loan_common.js +++ b/erpnext/loan_management/loan_common.js @@ -9,12 +9,15 @@ frappe.ui.form.on(cur_frm.doctype, { } if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype) - && frm.doc.docstatus == 1) { + && frm.doc.docstatus > 0) { frm.add_custom_button(__("Accounting Ledger"), function() { frappe.route_options = { voucher_no: frm.doc.name, - company: frm.doc.company + company: frm.doc.company, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + show_cancelled_entries: frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); diff --git a/erpnext/public/js/controllers/stock_controller.js b/erpnext/public/js/controllers/stock_controller.js index 2ce49e766b..87b21b78de 100644 --- a/erpnext/public/js/controllers/stock_controller.js +++ b/erpnext/public/js/controllers/stock_controller.js @@ -55,8 +55,9 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({ frappe.route_options = { voucher_no: me.frm.doc.name, from_date: me.frm.doc.posting_date, - to_date: me.frm.doc.posting_date, - company: me.frm.doc.company + to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'), + company: me.frm.doc.company, + show_cancelled_entries: me.frm.doc.docstatus === 2 }; frappe.set_route("query-report", "Stock Ledger"); }, __("View")); @@ -71,9 +72,10 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({ frappe.route_options = { voucher_no: me.frm.doc.name, from_date: me.frm.doc.posting_date, - to_date: me.frm.doc.posting_date, + to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'), company: me.frm.doc.company, - group_by: "Group by Voucher (Consolidated)" + group_by: "Group by Voucher (Consolidated)", + show_cancelled_entries: me.frm.doc.docstatus === 2 }; frappe.set_route("query-report", "General Ledger"); }, __("View")); From 40847aeac3c5a138dc422d4996979f8a04c0e74a Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 25 Jun 2020 15:43:31 +0530 Subject: [PATCH 18/38] fix: cannot cancel assets with repair pending (#22440) * fix: cannot cancel assets with repair pending * fix: message * Update asset.py Co-authored-by: Nabin Hait --- erpnext/assets/doctype/asset/asset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 2ecabe60f0..9ae5a87e6b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -408,6 +408,8 @@ class Asset(AccountsController): row.expected_value_after_useful_life = asset_value_after_full_schedule def validate_cancellation(self): + if self.status in ("In Maintenance", "Out of Order"): + frappe.throw(_("There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset.")) if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) From f311f52f6ecd61a34e640a246abd4a33711c988a Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 25 Jun 2020 16:02:18 +0530 Subject: [PATCH 19/38] fix: Disable Renaming in Serial No --- erpnext/stock/doctype/serial_no/serial_no.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index d9f8b62754..2be14c8006 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -1,7 +1,6 @@ { "actions": [], "allow_import": 1, - "allow_rename": 1, "autoname": "field:serial_no", "creation": "2013-05-16 10:59:15", "description": "Distinct unit of an Item", @@ -427,7 +426,7 @@ "icon": "fa fa-barcode", "idx": 1, "links": [], - "modified": "2020-05-21 19:29:58.517772", + "modified": "2020-06-25 15:53:50.900855", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", From 7f2a3e8c7dd1e91fa9b5cea0ee86e75401827d56 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Thu, 25 Jun 2020 13:56:32 +0200 Subject: [PATCH 20/38] Update customs_tariff_number.json Main information of tariff numbers are the number. A description is more or less double information as this is mostly the number. Thus a description should not be mandetory. --- .../doctype/customs_tariff_number/customs_tariff_number.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json b/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json index 07992b8231..90385a245d 100644 --- a/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json +++ b/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json @@ -71,7 +71,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -143,4 +143,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} From 5374e7ae1ffe9c32ae50bb227e61b6c073c0e789 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 25 Jun 2020 19:41:42 +0530 Subject: [PATCH 21/38] fix: add company filter in email digest --- .../setup/doctype/email_digest/email_digest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index 7c0be3bff6..b30bd7814b 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -405,8 +405,8 @@ class EmailDigest(Document): value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0), count(*) from `tabSales Order` - where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed" - and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] + where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed" and company = %(company)s + and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0] label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_bill"), report_type="Report Builder", @@ -430,8 +430,8 @@ class EmailDigest(Document): value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_delivered/100)),0), count(*) from `tabSales Order` - where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered" - and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] + where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered" and company = %(company)s + and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0] label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_deliver"), report_type="Report Builder", @@ -455,8 +455,8 @@ class EmailDigest(Document): value, count = frappe.db.sql("""select ifnull((sum(grand_total))-(sum(grand_total*per_received/100)),0), count(*) from `tabPurchase Order` - where (transaction_date <= %(to_date)s) and per_received < 100 - and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] + where (transaction_date <= %(to_date)s) and per_received < 100 and company = %(company)s + and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0] label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_receive"), report_type="Report Builder", @@ -480,8 +480,8 @@ class EmailDigest(Document): value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0), count(*) from `tabPurchase Order` - where (transaction_date <= %(to_date)s) and per_billed < 100 - and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] + where (transaction_date <= %(to_date)s) and per_billed < 100 and company = %(company)s + and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0] label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_bill"), report_type="Report Builder", From c8f590bc8f4b589b7f0991ccc0bccc37c357375b Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 25 Jun 2020 21:30:45 +0530 Subject: [PATCH 22/38] fix: Handling Empty tables in Production Plan --- .../production_plan/production_plan.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 560286e68a..016ab14d5f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -98,11 +98,17 @@ class ProductionPlan(Document): elif self.get_items_from == "Material Request": self.get_mr_items() + def get_so_mr_list(self, field, table): + """Returns a list of Sales Orders or Material Requests from the respective tables""" + so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)] + return so_mr_list + def get_so_items(self): - so_list = [d.sales_order for d in self.sales_orders if d.sales_order] - if not so_list: - msgprint(_("Please enter Sales Orders in the above table")) - return [] + # Check for empty table or empty rows + if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"): + frappe.throw(_("Please fill the Sales Orders table"), title=_("Sales Orders Required")) + + so_list = self.get_so_mr_list("sales_order", "sales_orders") item_condition = "" if self.item_code: @@ -134,10 +140,11 @@ class ProductionPlan(Document): self.calculate_total_planned_qty() def get_mr_items(self): - mr_list = [d.material_request for d in self.material_requests if d.material_request] - if not mr_list: - msgprint(_("Please enter Material Requests in the above table")) - return [] + # Check for empty table or empty rows + if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"): + frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required")) + + mr_list = self.get_so_mr_list("material_request", "material_requests") item_condition = "" if self.item_code: @@ -628,16 +635,18 @@ def get_items_for_material_requests(doc, warehouses=None): if warehouse_list: warehouses = list(set(warehouse_list)) - + if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses: warehouses.remove(doc.get("for_warehouse")) warehouse_list = None doc['mr_items'] = [] - po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') - if not po_items: - frappe.throw(_("Items are required to pull the raw materials which is associated with it.")) + + po_items = doc.get('po_items') + if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]: + frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."), + title=_("Items Required")) company = doc.get('company') ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') From 978722621fb804abc31d791c4176a90dc024cdac Mon Sep 17 00:00:00 2001 From: Michelle Alva <50285544+michellealva@users.noreply.github.com> Date: Thu, 25 Jun 2020 22:35:33 +0530 Subject: [PATCH 23/38] fix: grammatical fix on error message Before: Fiscal Year: 2020 does not exists After: Fiscal Year 2020 Does Not Exist --- erpnext/controllers/trends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py index 092baa4018..9b4b0eb917 100644 --- a/erpnext/controllers/trends.py +++ b/erpnext/controllers/trends.py @@ -33,7 +33,7 @@ def validate_filters(filters): frappe.throw(_("{0} is mandatory").format(f)) if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")): - frappe.throw(_("Fiscal Year: {0} does not exists").format(filters.get("fiscal_year"))) + frappe.throw(_("Fiscal Year {0} Does Not Exist").format(filters.get("fiscal_year"))) if filters.get("based_on") == filters.get("group_by"): frappe.throw(_("'Based On' and 'Group By' can not be same")) From 27d27c924dde64d846f2200b7f4570e819058d7c Mon Sep 17 00:00:00 2001 From: Anupam K Date: Fri, 26 Jun 2020 12:46:40 +0530 Subject: [PATCH 24/38] enable show_configure_button when shopping cart is enabled --- .../shopping_cart_settings/shopping_cart_settings.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js index e1510f5335..45889872dc 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js @@ -13,6 +13,12 @@ $.extend(cur_frm.cscript, { }, enable_checkout: function(){ toggle_mandatory(cur_frm) + }, + enabled: function() { + if(cur_frm.doc.enabled == 1) { + cur_frm.doc.show_configure_button = 1; + cur_frm.refresh_field('show_configure_button'); + } } }); From 0ff5af222a5842e93f4b9e1bdbc31011abbe105d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 26 Jun 2020 12:46:42 +0530 Subject: [PATCH 25/38] fix: Send email to credit controllers to increase credit limit --- erpnext/selling/doctype/customer/customer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 682dfede72..d70c64fce4 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -388,8 +388,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager") # form a list of emails and names to show to the user - credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})] - credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list] + credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users] if not credit_controller_users: frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer))) @@ -409,7 +408,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, 'customer': customer, 'customer_outstanding': customer_outstanding, 'credit_limit': credit_limit, - 'credit_controller_users_list': credit_controller_users_list + 'credit_controller_users_list': credit_controller_users } } ) From a97715e4642a1145750262400fba1e5a78bd5c53 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Fri, 26 Jun 2020 10:24:48 +0200 Subject: [PATCH 26/38] Update customs_tariff_number.json --- .../doctype/customs_tariff_number/customs_tariff_number.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json b/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json index 90385a245d..7eeb14760f 100644 --- a/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json +++ b/erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json @@ -88,7 +88,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-09-12 09:30:03.951743", + "modified": "2020-06-26 09:30:03.951743", "modified_by": "Administrator", "module": "Stock", "name": "Customs Tariff Number", From 0f056ec51d73aa6f2e6914921bd8a7aa8548c81a Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 26 Jun 2020 14:12:11 +0530 Subject: [PATCH 27/38] fix: Sales Order to MR test failing --- .../manufacturing/doctype/production_plan/production_plan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 016ab14d5f..c8892376b7 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -643,7 +643,8 @@ def get_items_for_material_requests(doc, warehouses=None): doc['mr_items'] = [] - po_items = doc.get('po_items') + po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') + # Check for empty table or empty rows if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]: frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."), title=_("Items Required")) From 13e8c3217247dca8d17e1330b512d35a9a94d956 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 26 Jun 2020 15:07:32 +0530 Subject: [PATCH 28/38] Update shopping_cart_settings.js --- .../doctype/shopping_cart_settings/shopping_cart_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js index 45889872dc..ffc5daba62 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js @@ -15,7 +15,7 @@ $.extend(cur_frm.cscript, { toggle_mandatory(cur_frm) }, enabled: function() { - if(cur_frm.doc.enabled == 1) { + if (cur_frm.doc.enabled === 1) { cur_frm.doc.show_configure_button = 1; cur_frm.refresh_field('show_configure_button'); } From 0086a3727fbc6351ffa109d04eb6a6ee6f46d662 Mon Sep 17 00:00:00 2001 From: britlog Date: Tue, 9 Jun 2020 22:50:49 +0200 Subject: [PATCH 29/38] fix: product_info --- erpnext/shopping_cart/product_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py index 7c08f5b5b2..29617a8748 100644 --- a/erpnext/shopping_cart/product_info.py +++ b/erpnext/shopping_cart/product_info.py @@ -55,7 +55,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False): def set_product_info_for_website(item): """set product price uom for website""" - product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True) + product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info") if product_info: item.update(product_info) From e9cec2ff760beb986f532a5b3b384a7fa597233d Mon Sep 17 00:00:00 2001 From: michellealva Date: Fri, 26 Jun 2020 19:29:25 +0530 Subject: [PATCH 30/38] fix: remove redundant label in salary slip --- erpnext/payroll/doctype/salary_slip/salary_slip.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 663a3ef9ea..88931c2a4b 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -381,7 +381,6 @@ { "fieldname": "earning", "fieldtype": "Column Break", - "label": "Earning", "oldfieldtype": "Column Break", "show_days": 1, "show_seconds": 1, @@ -400,7 +399,6 @@ { "fieldname": "deduction", "fieldtype": "Column Break", - "label": "Deduction", "oldfieldtype": "Column Break", "show_days": 1, "show_seconds": 1, @@ -616,7 +614,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2020-06-22 14:42:43.921828", + "modified": "2020-06-25 14:42:43.921828", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", From 08f842d12ee2472fc7a33472b4f888f73d331bb8 Mon Sep 17 00:00:00 2001 From: michellealva Date: Fri, 26 Jun 2020 19:56:00 +0530 Subject: [PATCH 31/38] fix: apply filters in all transactions --- erpnext/public/js/controllers/transaction.js | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index ca897dd4b1..421d44b5e7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -159,6 +159,27 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }; }); } + if (this.frm.fields_dict["items"].grid.get_field("cost_center")) { + this.frm.set_query("cost_center", "items", function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0 + } + }; + }); + } + + if (this.frm.fields_dict["items"].grid.get_field("expense_account")) { + this.frm.set_query("expense_account", "items", function(doc) { + return { + filters: { + "company": doc.company + } + }; + }); + } + if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) { this.frm.set_indicator_formatter('pricing_rule', function(doc) { From 2f37c447dd15d7488ec6e905968750655244717b Mon Sep 17 00:00:00 2001 From: Michelle Alva <50285544+michellealva@users.noreply.github.com> Date: Sat, 27 Jun 2020 09:27:45 +0530 Subject: [PATCH 32/38] fix: change button label in issue Changed the label of the button from "Make" to "Create" so that it is consistent with other DocTypes. Before: --- erpnext/support/doctype/issue/issue.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index e7e5bd312b..9e15757ce0 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -79,7 +79,7 @@ frappe.ui.form.on("Issue", { method: "erpnext.support.doctype.issue.issue.make_task", frm: frm }); - }, __("Make")); + }, __("Create")); } else { if (frm.doc.service_level_agreement) { @@ -232,4 +232,4 @@ function get_status(variance) { } else { return {"diff_display": "Failed", "indicator": "red"}; } -} \ No newline at end of file +} From 589364856c23aa97f7b235c01d96ab7d6e4b208e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 27 Jun 2020 18:44:36 +0530 Subject: [PATCH 33/38] fix: Syntax Error --- .../doctype/loan_security_shortfall/loan_security_shortfall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 99cb0b3d62..ffd96737a5 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -20,7 +20,7 @@ def update_shortfall_status(loan, security_value): if security_value >= loan_security_shortfall.shortfall_amount: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, { - "status", "Completed", + "status": "Completed", "shortfall_value": loan_security_shortfall.shortfall_amount}) else: frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, From d5736711ce04131364b079a29b10f6ee585d3dd7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sun, 28 Jun 2020 11:12:20 +0530 Subject: [PATCH 34/38] fix: indentation --- erpnext/public/js/controllers/transaction.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 421d44b5e7..905f6fb80f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -161,7 +161,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } if (this.frm.fields_dict["items"].grid.get_field("cost_center")) { this.frm.set_query("cost_center", "items", function(doc) { - return { + return { filters: { "company": doc.company, "is_group": 0 @@ -170,9 +170,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }); } - if (this.frm.fields_dict["items"].grid.get_field("expense_account")) { + if (this.frm.fields_dict["items"].grid.get_field("expense_account")) { this.frm.set_query("expense_account", "items", function(doc) { - return { + return { filters: { "company": doc.company } @@ -180,7 +180,6 @@ 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"; From 04cf2e029f98874b4eb8043ccbdce08f649f36d6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 28 Jun 2020 17:08:12 +0530 Subject: [PATCH 35/38] fix: letter head not found in opening invoice creation tool --- .../opening_invoice_creation_tool.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 54464e71c4..a53417eedf 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -68,6 +68,9 @@ class OpeningInvoiceCreationTool(Document): if not self.company: frappe.throw(_("Please select the Company")) + company_details = frappe.get_cached_value('Company', self.company, + ["default_currency", "default_letter_head"], as_dict=1) or {} + for row in self.invoices: if not row.qty: row.qty = 1.0 @@ -99,6 +102,12 @@ class OpeningInvoiceCreationTool(Document): if not args: continue + if company_details: + args.update({ + "currency": company_details.get("default_currency"), + "letter_head": company_details.get("default_letter_head") + }) + doc = frappe.get_doc(args).insert() doc.submit() names.append(doc.name) @@ -172,8 +181,7 @@ class OpeningInvoiceCreationTool(Document): "due_date": row.due_date, "posting_date": row.posting_date, frappe.scrub(party_type): row.party, - "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", - "currency": frappe.get_cached_value('Company', self.company, "default_currency") + "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice" }) accounting_dimension = get_accounting_dimensions() From eca2c1cd936a982fd842df927e67aa31e881076b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 28 Jun 2020 18:53:55 +0530 Subject: [PATCH 36/38] fix: patch --- erpnext/patches/v12_0/create_irs_1099_field_united_states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py index 82c8f5c414..4fb66b07a4 100644 --- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py +++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py @@ -5,6 +5,7 @@ from erpnext.regional.united_states.setup import make_custom_fields def execute(): frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) + frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True) company = frappe.get_all('Company', filters = {'country': 'United States'}) if not company: From 50e6e47e66238c3c10be53c0c633fa357130ebf0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 28 Jun 2020 21:32:08 +0530 Subject: [PATCH 37/38] fix: Patch --- erpnext/patches/v12_0/create_irs_1099_field_united_states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py index 4fb66b07a4..43bd0ccdd7 100644 --- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py +++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py @@ -6,6 +6,7 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True) + frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True) company = frappe.get_all('Company', filters = {'country': 'United States'}) if not company: From 73edba0e10d0a33fdd4186a83673e9d2295d578f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 29 Jun 2020 08:54:53 +0530 Subject: [PATCH 38/38] fix: handle nonetype issue for packed items (#22493) --- .../pending_so_items_for_purchase_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py index 14d8031582..e89c45182f 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py @@ -158,7 +158,7 @@ def get_data(): } pending_so.append(so_record) else: - for item in bundled_item_map.get((so.name, so.item_code)): + for item in bundled_item_map.get((so.name, so.item_code), []): material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {} if flt(item.qty) > flt(material_requests_against_so.get('qty')): so_record = {