Merge pull request #25186 from rohitwaghchaure/fixed-incorrect-batch-selected-in-purchase-rceipt-develop
fix: incorrect batch picked in subcontracted purchase receipt
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index d2e5870..199a183 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '13.0.1'
+__version__ = '13.0.0-dev'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/change_log/v13/v13.0.2.md b/erpnext/change_log/v13/v13.0.2.md
new file mode 100644
index 0000000..2bfbdfc
--- /dev/null
+++ b/erpnext/change_log/v13/v13.0.2.md
@@ -0,0 +1,7 @@
+## Version 13.0.2 Release Notes
+
+### Fixes
+- fix: frappe.whitelist for doc methods ([#25231](https://github.com/frappe/erpnext/pull/25231))
+- fix: incorrect incoming rate for the sales return ([#25306](https://github.com/frappe/erpnext/pull/25306))
+- fix(e-invoicing): validations & tax calculation fixes ([#25314](https://github.com/frappe/erpnext/pull/25314))
+- fix: update scheduler check time ([#25295](https://github.com/frappe/erpnext/pull/25295))
\ No newline at end of file
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index de61b35..5f759b4 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -5,6 +5,7 @@
import frappe, erpnext
from frappe import _
from frappe.model.meta import get_field_precision
+from erpnext.stock.utils import get_incoming_rate
from frappe.utils import flt, get_datetime, format_datetime
class StockOverReturnError(frappe.ValidationError): pass
@@ -389,10 +390,24 @@
return doclist
-def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None, item_row=None, voucher_detail_no=None):
+def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
+ item_row=None, voucher_detail_no=None, sle=None):
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
+ if not return_against and voucher_type == 'Sales Invoice' and sle:
+ return get_incoming_rate({
+ "item_code": sle.item_code,
+ "warehouse": sle.warehouse,
+ "posting_date": sle.get('posting_date'),
+ "posting_time": sle.get('posting_time'),
+ "qty": sle.actual_qty,
+ "serial_no": sle.get('serial_no'),
+ "company": sle.company,
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no
+ }, raise_error_if_no_rate=False)
+
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index edc40c4..54156f37 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -311,14 +311,16 @@
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
- if not cint(self.get("is_return")):
+ if not self.get("return_against"):
# Get incoming rate based on original item cost based on valuation method
+ qty = flt(d.get('stock_qty') or d.get('actual_qty'))
+
d.incoming_rate = get_incoming_rate({
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.get('posting_date') or self.get('transaction_date'),
"posting_time": self.get('posting_time') or nowtime(),
- "qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
+ "qty": qty if cint(self.get("is_return")) else (-1 * qty),
"serial_no": d.get('serial_no'),
"company": self.company,
"voucher_type": self.doctype,
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 98d5966..bb6cd8b 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -307,6 +307,8 @@
"Inpatient Medication Entry"
]
+after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
+
scheduler_events = {
"cron": {
"0/30 * * * *": [
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 14f1ab8..1f80088 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -771,3 +771,4 @@
erpnext.patches.v12_0.purchase_receipt_status
erpnext.patches.v13_0.fix_non_unique_represents_company
erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
+erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
index 3b560fd..b6bd5fa 100644
--- a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
+++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
@@ -12,5 +12,5 @@
select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
""", (creds.get('gstin')))
- if company_name and len(company_name) == 1:
+ if company_name and len(company_name) > 0:
frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py
new file mode 100644
index 0000000..a9d7883
--- /dev/null
+++ b/erpnext/patches/v13_0/make_non_standard_user_type.py
@@ -0,0 +1,24 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from six import iteritems
+from erpnext.setup.install import add_non_standard_user_types
+
+def execute():
+ doctype_dict = {
+ 'projects': ['Timesheet'],
+ 'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
+ 'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
+ }
+
+ for module, doctypes in iteritems(doctype_dict):
+ for doctype in doctypes:
+ frappe.reload_doc(module, 'doctype', doctype)
+
+
+ frappe.flags.ignore_select_perm = True
+ frappe.flags.update_select_perm_after_migrate = True
+
+ add_non_standard_user_types()
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index afdf081..539f2c5 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -633,8 +633,6 @@
if additional_salary:
component_row.default_amount = 0
- component_row.additional_amount = amount
- component_row.additional_salary = additional_salary.name
component_row.deduct_full_tax_on_selected_payroll_date = \
additional_salary.deduct_full_tax_on_selected_payroll_date
else:
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 605f4e1..59c098c 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -466,21 +466,24 @@
try:
einvoice = safe_json_load(einvoice)
einvoice = santize_einvoice_fields(einvoice)
- validate_totals(einvoice)
-
except Exception:
- log_error(einvoice)
- link_to_error_list = '<a href="List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
- frappe.throw(
- _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
- invoice.name, link_to_error_list),
- title=_('E Invoice Creation Failed')
- )
+ show_link_to_error_log(invoice, einvoice)
+
+ validate_totals(einvoice)
return einvoice
+def show_link_to_error_log(invoice, einvoice):
+ err_log = log_error(einvoice)
+ link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
+ frappe.throw(
+ _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
+ invoice.name, link_to_error_log),
+ title=_('E Invoice Creation Failed')
+ )
+
def log_error(data=None):
- if not isinstance(data, dict):
+ if isinstance(data, six.string_types):
data = json.loads(data)
seperator = "--" * 50
@@ -587,7 +590,7 @@
self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
def get_seller_gstin(self):
- gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
+ gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
if not gstin:
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
return gstin
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 82f191d..c7220cb 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -8,9 +8,11 @@
from .default_success_action import get_default_success_action
from frappe import _
from frappe.utils import cint
+from frappe.installer import update_site_config
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
+from six import iteritems
default_mail_footer = """<div style="padding: 7px; text-align: right; color: #888"><small>Sent via
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
@@ -29,6 +31,7 @@
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
+ add_non_standard_user_types()
frappe.db.commit()
@@ -164,3 +167,81 @@
def add_app_name():
frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
+
+def add_non_standard_user_types():
+ user_types = get_user_types_data()
+
+ user_type_limit = {}
+ for user_type, data in iteritems(user_types):
+ user_type_limit.setdefault(frappe.scrub(user_type), 10)
+
+ update_site_config('user_type_doctype_limit', user_type_limit)
+
+ for user_type, data in iteritems(user_types):
+ create_custom_role(data)
+ create_user_type(user_type, data)
+
+def get_user_types_data():
+ return {
+ 'Employee Self Service': {
+ 'role': 'Employee Self Service',
+ 'apply_user_permission_on': 'Employee',
+ 'user_id_field': 'user_id',
+ 'doctypes': {
+ 'Salary Slip': ['read'],
+ 'Employee': ['read', 'write'],
+ 'Expense Claim': ['read', 'write', 'create', 'delete'],
+ 'Leave Application': ['read', 'write', 'create', 'delete'],
+ 'Attendance Request': ['read', 'write', 'create', 'delete'],
+ 'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
+ 'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
+ 'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
+ 'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
+ }
+ }
+ }
+
+def create_custom_role(data):
+ if data.get('role') and not frappe.db.exists('Role', data.get('role')):
+ frappe.get_doc({
+ 'doctype': 'Role',
+ 'role_name': data.get('role'),
+ 'desk_access': 1,
+ 'is_custom': 1
+ }).insert(ignore_permissions=True)
+
+def create_user_type(user_type, data):
+ if frappe.db.exists('User Type', user_type):
+ doc = frappe.get_cached_doc('User Type', user_type)
+ doc.user_doctypes = []
+ else:
+ doc = frappe.new_doc('User Type')
+ doc.update({
+ 'name': user_type,
+ 'role': data.get('role'),
+ 'user_id_field': data.get('user_id_field'),
+ 'apply_user_permission_on': data.get('apply_user_permission_on')
+ })
+
+ create_role_permissions_for_doctype(doc, data)
+ doc.save(ignore_permissions=True)
+
+def create_role_permissions_for_doctype(doc, data):
+ for doctype, perms in iteritems(data.get('doctypes')):
+ args = {'document_type': doctype}
+ for perm in perms:
+ args[perm] = 1
+
+ doc.append('user_doctypes', args)
+
+def update_select_perm_after_install():
+ if not frappe.flags.update_select_perm_after_migrate:
+ return
+
+ frappe.flags.ignore_select_perm = False
+ for row in frappe.get_all('User Type', filters= {'is_standard': 0}):
+ print('Updating user type :- ', row.name)
+ doc = frappe.get_doc('User Type', row.name)
+ doc.save()
+
+ frappe.flags.update_select_perm_after_migrate = False
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 121c51c..df5f16f 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -372,7 +372,8 @@
elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"):
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return # don't move this import to top
- rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no)
+ rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code,
+ voucher_detail_no=sle.voucher_detail_no, sle = sle)
else:
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
rate_field = "valuation_rate"