From 9cc451fc7dbffd487316a3407d2eef1eee105d5d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 26 Aug 2021 07:35:59 +0500 Subject: [PATCH 01/41] feat(Regional): KSA VAT Report --- .../workspace/accounting/accounting.json | 23 ++- .../ksa_vat_purchase_account/__init__.py | 0 .../ksa_vat_purchase_account.json | 49 +++++ .../ksa_vat_purchase_account.py | 8 + .../doctype/ksa_vat_sales_account/__init__.py | 0 .../ksa_vat_sales_account.js | 8 + .../ksa_vat_sales_account.json | 49 +++++ .../ksa_vat_sales_account.py | 8 + .../test_ksa_vat_sales_account.py | 8 + .../doctype/ksa_vat_setting/__init__.py | 0 .../ksa_vat_setting/ksa_vat_setting.js | 8 + .../ksa_vat_setting/ksa_vat_setting.json | 49 +++++ .../ksa_vat_setting/ksa_vat_setting.py | 8 + .../ksa_vat_setting/ksa_vat_setting_list.js | 5 + .../ksa_vat_setting/test_ksa_vat_setting.py | 8 + erpnext/regional/report/ksa_vat/__init__.py | 0 erpnext/regional/report/ksa_vat/ksa_vat.js | 60 ++++++ erpnext/regional/report/ksa_vat/ksa_vat.json | 32 ++++ erpnext/regional/report/ksa_vat/ksa_vat.py | 177 ++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++ .../regional/saudi_arabia/wizard/__init__.py | 0 .../saudi_arabia/wizard/data/__init__.py | 0 .../wizard/data/ksa_vat_settings.json | 47 +++++ .../wizard/operations/__init__.py | 0 .../operations/setup_ksa_vat_setting.py | 45 +++++ 25 files changed, 608 insertions(+), 1 deletion(-) create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py create mode 100644 erpnext/regional/report/ksa_vat/__init__.py create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.js create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.json create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.py create mode 100644 erpnext/regional/saudi_arabia/wizard/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index b5bd14d1370..1ee5914d774 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -515,6 +515,17 @@ "only_for": "United Arab Emirates", "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "KSA VAT Report", + "link_to": "KSA VAT", + "link_type": "Report", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1135,6 +1146,16 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "KSA VAT Setting", + "link_to": "KSA VAT Setting", + "link_type": "DocType", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1188,7 +1209,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:52.872470", + "modified": "2021-08-26 13:15:52.872470", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json new file mode 100644 index 00000000000..89ba3e977af --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 09:17:09.862163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:38.205597", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Purchase Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py new file mode 100644 index 00000000000..1302e314c3c --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATPurchaseAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js new file mode 100644 index 00000000000..72613f4064f --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Sales Account', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json new file mode 100644 index 00000000000..df2747891dc --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 08:46:33.820968", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:00.081407", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Sales Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py new file mode 100644 index 00000000000..8e437f832b7 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSalesAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py new file mode 100644 index 00000000000..d7f911343b6 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSalesAccount(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js new file mode 100644 index 00000000000..0238c7b3068 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Setting', { + onload: function(frm) { + frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); + } +}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json new file mode 100644 index 00000000000..33619467ed0 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-13 08:49:01.100356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "ksa_vat_sales_accounts", + "ksa_vat_purchase_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ksa_vat_sales_accounts", + "fieldtype": "Table", + "label": "KSA VAT Sales Accounts", + "options": "KSA VAT Sales Account", + "reqd": 1 + }, + { + "fieldname": "ksa_vat_purchase_accounts", + "fieldtype": "Table", + "label": "KSA VAT Purchase Accounts", + "options": "KSA VAT Purchase Account", + "reqd": 1 + } + ], + "links": [], + "modified": "2021-08-26 04:29:06.499378", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Setting", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "company", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py new file mode 100644 index 00000000000..6db6f9cc59a --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSetting(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js new file mode 100644 index 00000000000..23d28b9e68b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -0,0 +1,5 @@ +frappe.listview_settings['KSA VAT Setting'] = { + onload(list) { + frappe.breadcrumbs.add('Accounts'); + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py new file mode 100644 index 00000000000..ae2defc61d1 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js new file mode 100644 index 00000000000..5dd9c92bf7a --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -0,0 +1,60 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["KSA VAT"] = { + onload() { + frappe.breadcrumbs.add('Accounts'); + }, + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') + && data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else if (data.title=='Grand Total'){ + if (data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else{ + value = default_formatter(value, row, column, data); + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + console.log($value) + return value + } + }else{ + value = default_formatter(value, row, column, data); + return value; + } + }, +}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json new file mode 100644 index 00000000000..036e2603103 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-13 08:54:38.000949", + "disable_prepared_report": 1, + "disabled": 1, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-26 04:14:37.202594", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "GL Entry", + "report_name": "KSA VAT", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py new file mode 100644 index 00000000000..6828007203a --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -0,0 +1,177 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import get_url_to_list +from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data, get_rounded_tax_amount +import json + +def execute(filters=None): + columns = columns = get_columns() + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "fieldname": "title", + "label": _("Title"), + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": _("Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "adjustment_amount", + "label": _("Adjustment (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "vat_amount", + "label": _("VAT Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + } + ] + +def get_data(filters): + data = [] + + # Validate if vat settings exist + company = filters.get('company') + if frappe.db.exists('KSA VAT Setting', company) is None: + url = get_url_to_list('KSA VAT Setting') + frappe.msgprint(f'Create KSA VAT Setting for this company') + return data + + ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) + + # Sales Heading + append_data(data, 'VAT on Sales', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Sales Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + # Blank Line + append_data(data, '', '', '', '') + + # Purchase Heading + append_data(data, 'VAT on Purchases', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Purchase Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + return data + +def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): + ''' + (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n + calculates and returns \n + total_taxable_amount, total_taxable_adjustment_amount, total_tax''' + from_date = filters.get('from_date') + to_date = filters.get('to_date') + + # Initiate variables + total_taxable_amount = 0 + total_taxable_adjustment_amount = 0 + total_tax = 0 + # Fetch All Invoices + invoices = frappe.get_list(doctype, + filters ={ + 'docstatus': 1, + 'posting_date': ['between', [from_date, to_date]] + }, + fields =['name', 'is_return']) + + for invoice in invoices: + invoice_items = frappe.get_list(f'{doctype} Item', + filters ={ + 'docstatus': 1, + 'parent': invoice.name, + 'item_tax_template': vat_setting.item_tax_template + }, + fields =['item_code', 'net_amount']) + + + for item in invoice_items: + # Summing up total taxable amount + if invoice.is_return == 0: + total_taxable_amount += item.net_amount + + if invoice.is_return == 1: + total_taxable_adjustment_amount += item.net_amount + + # Summing up total tax + total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) + + return total_taxable_amount, total_taxable_adjustment_amount, total_tax + + + +def append_data(data, title, amount, adjustment_amount, vat_amount): + """Returns data with appended value.""" + data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + +def get_tax_amount(item_code, account_head, doctype, parent): + if doctype == 'Sales Invoice': + tax_doctype = 'Sales Taxes and Charges' + + elif doctype == 'Purchase Invoice': + tax_doctype = 'Purchase Taxes and Charges' + + item_wise_tax_detail = frappe.get_value(tax_doctype, { + 'docstatus': 1, + 'parent': parent, + 'account_head': account_head + }, 'item_wise_tax_detail') + + tax_amount = 0 + if item_wise_tax_detail and len(item_wise_tax_detail) > 0: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + for key, value in item_wise_tax_detail.items(): + if key == item_code: + tax_amount = value[1] + break + + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 9b3677d2c64..053496729f7 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -3,9 +3,26 @@ from __future__ import unicode_literals +import frappe +from frappe.permissions import add_permission, update_permission_property from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting def setup(company=None, patch=True): make_custom_fields() add_print_formats() + add_permissions() + create_ksa_vat_setting(company) + + +def add_permissions(): + """Add Permissions for KSA VAT Setting.""" + add_permission('KSA VAT Setting', 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission('KSA VAT Setting', role, 0) + update_permission_property('KSA VAT Setting', role, 0, 'write', 1) + update_permission_property('KSA VAT Setting', role, 0, 'create', 1) + + """Enable KSA VAT Report""" + frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json new file mode 100644 index 00000000000..709d65be041 --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -0,0 +1,47 @@ +[ + { + "type": "Sales Account", + "accounts": [ + { + "title": "Standard rated Sales", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Zero rated domestic sales", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted sales", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + }, + { + "type": "Purchase Account", + "accounts": [ + { + "title": "Standard rated domestic purchases", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Imports subject to VAT paid at customs", + "item_tax_template": "KSA Excise 50%", + "account": "Excise 50%" + }, + { + "title": "Zero rated purchases", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted purchases", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + } +] \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py new file mode 100644 index 00000000000..4bccdc35bfe --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -0,0 +1,45 @@ +import frappe +import os +import json +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + +def create_ksa_vat_setting(company): + """ + On creation of first company. Creates KSA VAT Setting""" + + company = frappe.get_doc('Company', company) + setup_taxes_and_charges(company.name, company.country) + + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') + with open(file_path, 'r') as json_file: + account_data = json.load(json_file) + + + # Creating KSA VAT Setting + ksa_vat_setting = frappe.get_doc({ + 'doctype': 'KSA VAT Setting', + 'company': company.name + }) + + for data in account_data: + if data['type'] == 'Sales Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_sales_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + elif data['type'] == 'Purchase Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_purchase_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + ksa_vat_setting.save() \ No newline at end of file From cb0c2d1477795566ca8bf5b03ed355791511b654 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 26 Aug 2021 07:35:59 +0500 Subject: [PATCH 02/41] feat(Regional): KSA VAT Report --- .../workspace/accounting/accounting.json | 21 +++ .../ksa_vat_purchase_account/__init__.py | 0 .../ksa_vat_purchase_account.json | 49 +++++ .../ksa_vat_purchase_account.py | 8 + .../doctype/ksa_vat_sales_account/__init__.py | 0 .../ksa_vat_sales_account.js | 8 + .../ksa_vat_sales_account.json | 49 +++++ .../ksa_vat_sales_account.py | 8 + .../test_ksa_vat_sales_account.py | 8 + .../doctype/ksa_vat_setting/__init__.py | 0 .../ksa_vat_setting/ksa_vat_setting.js | 8 + .../ksa_vat_setting/ksa_vat_setting.json | 49 +++++ .../ksa_vat_setting/ksa_vat_setting.py | 8 + .../ksa_vat_setting/ksa_vat_setting_list.js | 5 + .../ksa_vat_setting/test_ksa_vat_setting.py | 8 + erpnext/regional/report/ksa_vat/__init__.py | 0 erpnext/regional/report/ksa_vat/ksa_vat.js | 60 ++++++ erpnext/regional/report/ksa_vat/ksa_vat.json | 32 ++++ erpnext/regional/report/ksa_vat/ksa_vat.py | 177 ++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++ .../regional/saudi_arabia/wizard/__init__.py | 0 .../saudi_arabia/wizard/data/__init__.py | 0 .../wizard/data/ksa_vat_settings.json | 47 +++++ .../wizard/operations/__init__.py | 0 .../operations/setup_ksa_vat_setting.py | 45 +++++ 25 files changed, 607 insertions(+) create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py create mode 100644 erpnext/regional/report/ksa_vat/__init__.py create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.js create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.json create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.py create mode 100644 erpnext/regional/saudi_arabia/wizard/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 2b26ac50900..e7210f19ffb 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -533,6 +533,17 @@ "only_for": "United Arab Emirates", "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "KSA VAT Report", + "link_to": "KSA VAT", + "link_type": "Report", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1153,6 +1164,16 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "KSA VAT Setting", + "link_to": "KSA VAT Setting", + "link_type": "DocType", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json new file mode 100644 index 00000000000..89ba3e977af --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 09:17:09.862163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:38.205597", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Purchase Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py new file mode 100644 index 00000000000..1302e314c3c --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATPurchaseAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js new file mode 100644 index 00000000000..72613f4064f --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Sales Account', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json new file mode 100644 index 00000000000..df2747891dc --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 08:46:33.820968", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:00.081407", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Sales Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py new file mode 100644 index 00000000000..8e437f832b7 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSalesAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py new file mode 100644 index 00000000000..d7f911343b6 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSalesAccount(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js new file mode 100644 index 00000000000..0238c7b3068 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Setting', { + onload: function(frm) { + frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); + } +}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json new file mode 100644 index 00000000000..33619467ed0 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-13 08:49:01.100356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "ksa_vat_sales_accounts", + "ksa_vat_purchase_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ksa_vat_sales_accounts", + "fieldtype": "Table", + "label": "KSA VAT Sales Accounts", + "options": "KSA VAT Sales Account", + "reqd": 1 + }, + { + "fieldname": "ksa_vat_purchase_accounts", + "fieldtype": "Table", + "label": "KSA VAT Purchase Accounts", + "options": "KSA VAT Purchase Account", + "reqd": 1 + } + ], + "links": [], + "modified": "2021-08-26 04:29:06.499378", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Setting", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "company", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py new file mode 100644 index 00000000000..6db6f9cc59a --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSetting(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js new file mode 100644 index 00000000000..23d28b9e68b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -0,0 +1,5 @@ +frappe.listview_settings['KSA VAT Setting'] = { + onload(list) { + frappe.breadcrumbs.add('Accounts'); + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py new file mode 100644 index 00000000000..ae2defc61d1 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js new file mode 100644 index 00000000000..5dd9c92bf7a --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -0,0 +1,60 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["KSA VAT"] = { + onload() { + frappe.breadcrumbs.add('Accounts'); + }, + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') + && data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else if (data.title=='Grand Total'){ + if (data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else{ + value = default_formatter(value, row, column, data); + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + console.log($value) + return value + } + }else{ + value = default_formatter(value, row, column, data); + return value; + } + }, +}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json new file mode 100644 index 00000000000..036e2603103 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-13 08:54:38.000949", + "disable_prepared_report": 1, + "disabled": 1, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-26 04:14:37.202594", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "GL Entry", + "report_name": "KSA VAT", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py new file mode 100644 index 00000000000..6828007203a --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -0,0 +1,177 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import get_url_to_list +from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data, get_rounded_tax_amount +import json + +def execute(filters=None): + columns = columns = get_columns() + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "fieldname": "title", + "label": _("Title"), + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": _("Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "adjustment_amount", + "label": _("Adjustment (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "vat_amount", + "label": _("VAT Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + } + ] + +def get_data(filters): + data = [] + + # Validate if vat settings exist + company = filters.get('company') + if frappe.db.exists('KSA VAT Setting', company) is None: + url = get_url_to_list('KSA VAT Setting') + frappe.msgprint(f'Create KSA VAT Setting for this company') + return data + + ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) + + # Sales Heading + append_data(data, 'VAT on Sales', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Sales Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + # Blank Line + append_data(data, '', '', '', '') + + # Purchase Heading + append_data(data, 'VAT on Purchases', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Purchase Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + return data + +def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): + ''' + (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n + calculates and returns \n + total_taxable_amount, total_taxable_adjustment_amount, total_tax''' + from_date = filters.get('from_date') + to_date = filters.get('to_date') + + # Initiate variables + total_taxable_amount = 0 + total_taxable_adjustment_amount = 0 + total_tax = 0 + # Fetch All Invoices + invoices = frappe.get_list(doctype, + filters ={ + 'docstatus': 1, + 'posting_date': ['between', [from_date, to_date]] + }, + fields =['name', 'is_return']) + + for invoice in invoices: + invoice_items = frappe.get_list(f'{doctype} Item', + filters ={ + 'docstatus': 1, + 'parent': invoice.name, + 'item_tax_template': vat_setting.item_tax_template + }, + fields =['item_code', 'net_amount']) + + + for item in invoice_items: + # Summing up total taxable amount + if invoice.is_return == 0: + total_taxable_amount += item.net_amount + + if invoice.is_return == 1: + total_taxable_adjustment_amount += item.net_amount + + # Summing up total tax + total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) + + return total_taxable_amount, total_taxable_adjustment_amount, total_tax + + + +def append_data(data, title, amount, adjustment_amount, vat_amount): + """Returns data with appended value.""" + data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + +def get_tax_amount(item_code, account_head, doctype, parent): + if doctype == 'Sales Invoice': + tax_doctype = 'Sales Taxes and Charges' + + elif doctype == 'Purchase Invoice': + tax_doctype = 'Purchase Taxes and Charges' + + item_wise_tax_detail = frappe.get_value(tax_doctype, { + 'docstatus': 1, + 'parent': parent, + 'account_head': account_head + }, 'item_wise_tax_detail') + + tax_amount = 0 + if item_wise_tax_detail and len(item_wise_tax_detail) > 0: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + for key, value in item_wise_tax_detail.items(): + if key == item_code: + tax_amount = value[1] + break + + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 9b3677d2c64..053496729f7 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -3,9 +3,26 @@ from __future__ import unicode_literals +import frappe +from frappe.permissions import add_permission, update_permission_property from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting def setup(company=None, patch=True): make_custom_fields() add_print_formats() + add_permissions() + create_ksa_vat_setting(company) + + +def add_permissions(): + """Add Permissions for KSA VAT Setting.""" + add_permission('KSA VAT Setting', 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission('KSA VAT Setting', role, 0) + update_permission_property('KSA VAT Setting', role, 0, 'write', 1) + update_permission_property('KSA VAT Setting', role, 0, 'create', 1) + + """Enable KSA VAT Report""" + frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json new file mode 100644 index 00000000000..709d65be041 --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -0,0 +1,47 @@ +[ + { + "type": "Sales Account", + "accounts": [ + { + "title": "Standard rated Sales", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Zero rated domestic sales", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted sales", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + }, + { + "type": "Purchase Account", + "accounts": [ + { + "title": "Standard rated domestic purchases", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Imports subject to VAT paid at customs", + "item_tax_template": "KSA Excise 50%", + "account": "Excise 50%" + }, + { + "title": "Zero rated purchases", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted purchases", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + } +] \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py new file mode 100644 index 00000000000..4bccdc35bfe --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -0,0 +1,45 @@ +import frappe +import os +import json +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + +def create_ksa_vat_setting(company): + """ + On creation of first company. Creates KSA VAT Setting""" + + company = frappe.get_doc('Company', company) + setup_taxes_and_charges(company.name, company.country) + + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') + with open(file_path, 'r') as json_file: + account_data = json.load(json_file) + + + # Creating KSA VAT Setting + ksa_vat_setting = frappe.get_doc({ + 'doctype': 'KSA VAT Setting', + 'company': company.name + }) + + for data in account_data: + if data['type'] == 'Sales Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_sales_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + elif data['type'] == 'Purchase Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_purchase_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + ksa_vat_setting.save() \ No newline at end of file From 87380d021d589a9717a26ee74db19e3902948c36 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 6 Sep 2021 23:36:55 +0500 Subject: [PATCH 03/41] feat(regional): QR Code generation for Saudi Arabia Sales Invoices --- erpnext/hooks.py | 6 ++- erpnext/regional/__init__.py | 72 ++++++++++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++++-- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5b6e1eeafcd..050ce2e11d1 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,6 +262,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { + "after_insert": "erpnext.regional.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", @@ -271,7 +272,10 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], - "on_trash": "erpnext.regional.check_deletion_permission", + "on_trash": [ + "erpnext.regional.check_deletion_permission", + "erpnext.regional.delete_qr_code_file" + ], "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 45a689efa8b..3591be07b89 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -7,6 +7,9 @@ import frappe from frappe import _ from erpnext import get_region +from pyqrcode import create as qr_create +import io +import os def check_deletion_permission(doc, method): @@ -31,3 +34,72 @@ def create_transaction_log(doc, method): "document_name": doc.name, "data": data }).insert(ignore_permissions=True) + + +def create_qr_code(doc, method): + """Create QR Code after inserting Sales Inv + """ + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, do nothing + if not hasattr(doc, 'qr_code'): + return + + # Don't create QR Code if it already exists + qr_code = doc.get("qr_code") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + fields = frappe.get_meta('Sales Invoice').fields + + for field in fields: + if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + # Creating public url to print format + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + + # System Language + language = frappe.get_system_settings('language') + + # creating qr code for the url + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + qr_image = io.BytesIO() + url = qr_create(url, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + # making file + filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue() + }) + + _file.save() + + # assigning to document + doc.db_set('qr_code', _file.file_url) + doc.notify_update() + + break + + else: + pass + +def delete_qr_code_file(doc, method): + """Delete QR Code on deleted sales invoice""" + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'qr_code'): + if doc.get('qr_code'): + file_doc = frappe.get_list('File', { + 'file_url': doc.qr_code + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 053496729f7..f55900023d4 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -2,18 +2,18 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import frappe from frappe.permissions import add_permission, update_permission_property -from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields, add_print_formats from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting - +from frappe.custom.doctype.custom_field.custom_field import create_custom_field def setup(company=None, patch=True): - make_custom_fields() + uae_custom_fields() add_print_formats() add_permissions() create_ksa_vat_setting(company) + make_custom_fields() def add_permissions(): @@ -26,3 +26,12 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) + +def make_custom_fields(): + qr_code_field = dict( + fieldname='qr_code', + label='QR Code', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1) + + create_custom_field('Sales Invoice', qr_code_field) From b5b61b3a3d8d46ef9d15af3d631ad407e18e9270 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:26:44 +0500 Subject: [PATCH 04/41] Update erpnext/regional/report/ksa_vat/ksa_vat.py String made translatable Co-authored-by: Saqib --- erpnext/regional/report/ksa_vat/ksa_vat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 6828007203a..1175fbf11f5 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -48,7 +48,7 @@ def get_data(filters): company = filters.get('company') if frappe.db.exists('KSA VAT Setting', company) is None: url = get_url_to_list('KSA VAT Setting') - frappe.msgprint(f'Create KSA VAT Setting for this company') + frappe.msgprint(_('Create KSA VAT Setting for this company').format(url)) return data ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) From 6985ca210e6170c55f15d3b3322fa7117a32c2e1 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:28:57 +0500 Subject: [PATCH 05/41] Refactor erpnext/regional/__init__.py Co-authored-by: Saqib --- erpnext/regional/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 3591be07b89..6b68bc032b9 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -86,8 +86,6 @@ def create_qr_code(doc, method): break - else: - pass def delete_qr_code_file(doc, method): """Delete QR Code on deleted sales invoice""" From 0950713e4c01a63ae442abb443f9b0441834648b Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:29:33 +0500 Subject: [PATCH 06/41] Update erpnext/regional/saudi_arabia/setup.py Method name updated Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index f55900023d4..22a050650c5 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -13,7 +13,7 @@ def setup(company=None, patch=True): add_print_formats() add_permissions() create_ksa_vat_setting(company) - make_custom_fields() + make_qrcode_field() def add_permissions(): From de01066ef5258e351c61106518dc8c05aa86aa08 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:29:45 +0500 Subject: [PATCH 07/41] Update erpnext/regional/saudi_arabia/setup.py Method name updated Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 22a050650c5..82f2f4fab00 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -27,7 +27,7 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) -def make_custom_fields(): +def make_qrcode_field(): qr_code_field = dict( fieldname='qr_code', label='QR Code', From 8a42570c962af645a49c8631704fb183bb08b79a Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 14 Sep 2021 14:45:23 +0500 Subject: [PATCH 08/41] refactor(regional): moved methos to region specific utils for KSA --- erpnext/hooks.py | 4 +- erpnext/regional/__init__.py | 69 ------------------------ erpnext/regional/saudi_arabia/utils.py | 73 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 erpnext/regional/saudi_arabia/utils.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 050ce2e11d1..c41a1081de6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,7 +262,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { - "after_insert": "erpnext.regional.create_qr_code", + "after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", @@ -274,7 +274,7 @@ doc_events = { ], "on_trash": [ "erpnext.regional.check_deletion_permission", - "erpnext.regional.delete_qr_code_file" + "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" ], "validate": [ "erpnext.regional.india.utils.validate_document_name", diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 6b68bc032b9..d7dcbf4fe18 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -7,9 +7,6 @@ import frappe from frappe import _ from erpnext import get_region -from pyqrcode import create as qr_create -import io -import os def check_deletion_permission(doc, method): @@ -35,69 +32,3 @@ def create_transaction_log(doc, method): "data": data }).insert(ignore_permissions=True) - -def create_qr_code(doc, method): - """Create QR Code after inserting Sales Inv - """ - - region = get_region(doc.company) - if region not in ['Saudi Arabia']: - return - - # if QR Code field not present, do nothing - if not hasattr(doc, 'qr_code'): - return - - # Don't create QR Code if it already exists - qr_code = doc.get("qr_code") - if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): - return - - fields = frappe.get_meta('Sales Invoice').fields - - for field in fields: - if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': - # Creating public url to print format - default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") - - # System Language - language = frappe.get_system_settings('language') - - # creating qr code for the url - url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" - qr_image = io.BytesIO() - url = qr_create(url, error='L') - url.png(qr_image, scale=2, quiet_zone=1) - - # making file - filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") - _file = frappe.get_doc({ - "doctype": "File", - "file_name": filename, - "is_private": 0, - "content": qr_image.getvalue() - }) - - _file.save() - - # assigning to document - doc.db_set('qr_code', _file.file_url) - doc.notify_update() - - break - - -def delete_qr_code_file(doc, method): - """Delete QR Code on deleted sales invoice""" - - region = get_region(doc.company) - if region not in ['Saudi Arabia']: - return - - if hasattr(doc, 'qr_code'): - if doc.get('qr_code'): - file_doc = frappe.get_list('File', { - 'file_url': doc.qr_code - }) - if len(file_doc): - frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py new file mode 100644 index 00000000000..4431ba065ef --- /dev/null +++ b/erpnext/regional/saudi_arabia/utils.py @@ -0,0 +1,73 @@ +import frappe +from frappe import _ +from erpnext import get_region +from pyqrcode import create as qr_create +import io +import os + + +def create_qr_code(doc, method): + """Create QR Code after inserting Sales Inv + """ + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, do nothing + if not hasattr(doc, 'qr_code'): + return + + # Don't create QR Code if it already exists + qr_code = doc.get("qr_code") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + fields = frappe.get_meta('Sales Invoice').fields + + for field in fields: + if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + # Creating public url to print format + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + + # System Language + language = frappe.get_system_settings('language') + + # creating qr code for the url + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + qr_image = io.BytesIO() + url = qr_create(url, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + # making file + filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue() + }) + + _file.save() + + # assigning to document + doc.db_set('qr_code', _file.file_url) + doc.notify_update() + + break + + +def delete_qr_code_file(doc, method): + """Delete QR Code on deleted sales invoice""" + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'qr_code'): + if doc.get('qr_code'): + file_doc = frappe.get_list('File', { + 'file_url': doc.qr_code + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file From 940db71a8292fc9f4f89a00014138fbe6809a474 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:14:41 +0500 Subject: [PATCH 09/41] fix: indentations --- erpnext/hooks.py | 2 +- .../ksa_vat_setting/ksa_vat_setting.js | 2 +- .../ksa_vat_setting/ksa_vat_setting_list.js | 6 +-- erpnext/regional/report/ksa_vat/ksa_vat.py | 51 +++++++++---------- erpnext/regional/saudi_arabia/utils.py | 19 +++---- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c41a1081de6..1e3ad8e2e92 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -275,7 +275,7 @@ doc_events = { "on_trash": [ "erpnext.regional.check_deletion_permission", "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" - ], + ], "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js index 0238c7b3068..00b62b9adfb 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('KSA VAT Setting', { - onload: function(frm) { + onload: function () { frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); } }); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js index 23d28b9e68b..269cbec5fb4 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['KSA VAT Setting'] = { - onload(list) { - frappe.breadcrumbs.add('Accounts'); - } + onload () { + frappe.breadcrumbs.add('Accounts'); + } } \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 1175fbf11f5..1697bbbd4d6 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -2,11 +2,13 @@ # For license information, please see license.txt from __future__ import unicode_literals + +import json + import frappe from frappe import _ from frappe.utils import get_url_to_list -from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data, get_rounded_tax_amount -import json + def execute(filters=None): columns = columns = get_columns() @@ -52,7 +54,7 @@ def get_data(filters): return data ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) - + # Sales Heading append_data(data, 'VAT on Sales', '', '', '') @@ -63,19 +65,19 @@ def get_data(filters): for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: total_taxable_amount, total_taxable_adjustment_amount, \ total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') - + # Adding results to data - append_data(data, vat_setting.title, total_taxable_amount, + append_data(data, vat_setting.title, total_taxable_amount, total_taxable_adjustment_amount, total_tax) - + grand_total_taxable_amount += total_taxable_amount grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount grand_total_tax += total_tax # Sales Grand Total - append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax ) - + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax) + # Blank Line append_data(data, '', '', '', '') @@ -89,9 +91,9 @@ def get_data(filters): for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: total_taxable_amount, total_taxable_adjustment_amount, \ total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') - + # Adding results to data - append_data(data, vat_setting.title, total_taxable_amount, + append_data(data, vat_setting.title, total_taxable_amount, total_taxable_adjustment_amount, total_tax) grand_total_taxable_amount += total_taxable_amount @@ -99,8 +101,8 @@ def get_data(filters): grand_total_tax += total_tax # Purchase Grand Total - append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax ) + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax) return data @@ -117,36 +119,33 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): total_taxable_adjustment_amount = 0 total_tax = 0 # Fetch All Invoices - invoices = frappe.get_list(doctype, + invoices = frappe.get_list(doctype, filters ={ 'docstatus': 1, 'posting_date': ['between', [from_date, to_date]] - }, - fields =['name', 'is_return']) + }, fields =['name', 'is_return']) for invoice in invoices: - invoice_items = frappe.get_list(f'{doctype} Item', + invoice_items = frappe.get_list(f'{doctype} Item', filters ={ 'docstatus': 1, 'parent': invoice.name, 'item_tax_template': vat_setting.item_tax_template - }, - fields =['item_code', 'net_amount']) + }, fields =['item_code', 'net_amount']) - for item in invoice_items: # Summing up total taxable amount if invoice.is_return == 0: total_taxable_amount += item.net_amount - + if invoice.is_return == 1: total_taxable_adjustment_amount += item.net_amount # Summing up total tax total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) - + return total_taxable_amount, total_taxable_adjustment_amount, total_tax - + def append_data(data, title, amount, adjustment_amount, vat_amount): @@ -156,10 +155,10 @@ def append_data(data, title, amount, adjustment_amount, vat_amount): def get_tax_amount(item_code, account_head, doctype, parent): if doctype == 'Sales Invoice': tax_doctype = 'Sales Taxes and Charges' - + elif doctype == 'Purchase Invoice': tax_doctype = 'Purchase Taxes and Charges' - + item_wise_tax_detail = frappe.get_value(tax_doctype, { 'docstatus': 1, 'parent': parent, @@ -173,5 +172,5 @@ def get_tax_amount(item_code, account_head, doctype, parent): if key == item_code: tax_amount = value[1] break - + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 4431ba065ef..e83d8c72ad6 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -1,10 +1,11 @@ -import frappe -from frappe import _ -from erpnext import get_region -from pyqrcode import create as qr_create import io import os +import frappe +from pyqrcode import create as qr_create + +from erpnext import get_region + def create_qr_code(doc, method): """Create QR Code after inserting Sales Inv @@ -24,21 +25,21 @@ def create_qr_code(doc, method): return fields = frappe.get_meta('Sales Invoice').fields - + for field in fields: if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") - + # System Language language = frappe.get_system_settings('language') - + # creating qr code for the url url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" qr_image = io.BytesIO() url = qr_create(url, error='L') url.png(qr_image, scale=2, quiet_zone=1) - + # making file filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") _file = frappe.get_doc({ @@ -59,7 +60,7 @@ def create_qr_code(doc, method): def delete_qr_code_file(doc, method): """Delete QR Code on deleted sales invoice""" - + region = get_region(doc.company) if region not in ['Saudi Arabia']: return From 95b3b9c8f5f3d2c53d82b6f7a9b9a85bae722d15 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:27:37 +0500 Subject: [PATCH 10/41] Update erpnext/regional/saudi_arabia/utils.py Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index e83d8c72ad6..5b8b7c13836 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -68,7 +68,7 @@ def delete_qr_code_file(doc, method): if hasattr(doc, 'qr_code'): if doc.get('qr_code'): file_doc = frappe.get_list('File', { - 'file_url': doc.qr_code + 'file_url': doc.get('qr_code') }) if len(file_doc): frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file From 05321d7f4e1b72f59a93039ba6b2774bda7dc0fb Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:28:52 +0500 Subject: [PATCH 11/41] Update erpnext/regional/saudi_arabia/utils.py Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 5b8b7c13836..79a29de9ac7 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -46,7 +46,10 @@ def create_qr_code(doc, method): "doctype": "File", "file_name": filename, "is_private": 0, - "content": qr_image.getvalue() + "content": qr_image.getvalue(), + "attached_to_doctype": doc.get("doctype"), + "attached_to_name": doc.get("name"), + "attached_to_field": "qr_code" }) _file.save() From f1e5a64c61b8bea35e9fff813e290e8f05a32b7a Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:33:32 +0500 Subject: [PATCH 12/41] refactor (regional): KSA utils --- erpnext/regional/saudi_arabia/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 79a29de9ac7..cc6c0af7a56 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -24,10 +24,10 @@ def create_qr_code(doc, method): if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): return - fields = frappe.get_meta('Sales Invoice').fields + meta = frappe.get_meta('Sales Invoice') - for field in fields: - if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + for field in meta.get_image_fields(): + if field.fieldname == 'qr_code': # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") From 4ec733fcc0d2f1bae8c016edac5d6dbc5598e764 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:46:45 +0500 Subject: [PATCH 13/41] fix: pre-commit hooks --- .../wizard/operations/setup_ksa_vat_setting.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index 4bccdc35bfe..c30fcffc37c 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -1,11 +1,13 @@ -import frappe -import os import json +import os + +import frappe + from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + def create_ksa_vat_setting(company): - """ - On creation of first company. Creates KSA VAT Setting""" + """On creation of first company. Creates KSA VAT Setting""" company = frappe.get_doc('Company', company) setup_taxes_and_charges(company.name, company.country) @@ -13,14 +15,14 @@ def create_ksa_vat_setting(company): file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') with open(file_path, 'r') as json_file: account_data = json.load(json_file) - + # Creating KSA VAT Setting ksa_vat_setting = frappe.get_doc({ 'doctype': 'KSA VAT Setting', 'company': company.name }) - + for data in account_data: if data['type'] == 'Sales Account': for row in data['accounts']: @@ -31,7 +33,7 @@ def create_ksa_vat_setting(company): 'item_tax_template': f'{item_tax_template} - {company.abbr}', 'account': f'{account} - {company.abbr}' }) - + elif data['type'] == 'Purchase Account': for row in data['accounts']: item_tax_template = row['item_tax_template'] From 9dae36b81d88359da72efcc1d1020af83188799b Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:51:32 +0500 Subject: [PATCH 14/41] fix: pre-commit hooks --- erpnext/regional/saudi_arabia/setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 82f2f4fab00..6113f48d3f1 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -14,7 +14,6 @@ def setup(company=None, patch=True): add_permissions() create_ksa_vat_setting(company) make_qrcode_field() - def add_permissions(): """Add Permissions for KSA VAT Setting.""" @@ -28,10 +27,11 @@ def add_permissions(): frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) def make_qrcode_field(): + """Created QR code Image file""" qr_code_field = dict( - fieldname='qr_code', - label='QR Code', - fieldtype='Attach Image', + fieldname='qr_code', + label='QR Code', + fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1) - + create_custom_field('Sales Invoice', qr_code_field) From 0ab57d49c1e2da11b3a2f12d394515e5e2991bca Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:54:59 +0500 Subject: [PATCH 15/41] fix: pre-commit hooks --- .../ksa_vat_purchase_account/ksa_vat_purchase_account.py | 1 + .../doctype/ksa_vat_sales_account/ksa_vat_sales_account.py | 1 + .../doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py | 1 + erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py | 1 + .../regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py | 1 + erpnext/regional/report/ksa_vat/ksa_vat.js | 2 +- 6 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py index 1302e314c3c..3920bc546c1 100644 --- a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class KSAVATPurchaseAccount(Document): pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py index 8e437f832b7..7c2689f530e 100644 --- a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class KSAVATSalesAccount(Document): pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py index d7f911343b6..1d6a6a793dc 100644 --- a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestKSAVATSalesAccount(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py index 6db6f9cc59a..bdae1161fd7 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class KSAVATSetting(Document): pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py index ae2defc61d1..7207901fd43 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestKSAVATSetting(unittest.TestCase): pass diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js index 5dd9c92bf7a..d46d260ac1e 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.js +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -4,7 +4,7 @@ frappe.query_reports["KSA VAT"] = { onload() { - frappe.breadcrumbs.add('Accounts'); + frappe.breadcrumbs.add('Accounts'); }, "filters": [ { From f5e0cad6a1c3036bff7a07a4dcc77766ffa36c79 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 19 Aug 2021 14:18:50 +0530 Subject: [PATCH 16/41] refactor: updated onboarding cards and tours --- .../buying_settings/buying_settings.js | 4 +- .../buying_settings/buying_settings.json | 77 +++++++++++++++ .../purchase_order/purchase_order.json | 82 ++++++++++++++++ .../module_onboarding/buying/buying.json | 14 +-- .../create_a_material_request.json | 10 +- .../create_your_first_purchase_order.json | 10 +- .../introduction_to_buying.json | 15 +-- .../material_request/material_request.json | 97 +++++++++++++++++++ 8 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 erpnext/buying/form_tour/buying_settings/buying_settings.json create mode 100644 erpnext/buying/form_tour/purchase_order/purchase_order.json create mode 100644 erpnext/stock/form_tour/material_request/material_request.json diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js index e496e9628d1..32431fc3910 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.js +++ b/erpnext/buying/doctype/buying_settings/buying_settings.js @@ -11,7 +11,7 @@ frappe.tour['Buying Settings'] = [ { fieldname: "supp_master_name", title: "Supplier Naming By", - description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."), + description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a Naming Series choose the 'Naming Series' option."), }, { fieldname: "buying_price_list", @@ -28,4 +28,4 @@ frappe.tour['Buying Settings'] = [ title: "Purchase Receipt Required for Purchase Invoice Creation", description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in the Supplier master.") } -]; \ No newline at end of file +]; diff --git a/erpnext/buying/form_tour/buying_settings/buying_settings.json b/erpnext/buying/form_tour/buying_settings/buying_settings.json new file mode 100644 index 00000000000..fa8c80d6cdf --- /dev/null +++ b/erpnext/buying/form_tour/buying_settings/buying_settings.json @@ -0,0 +1,77 @@ +{ + "creation": "2021-07-28 11:51:42.319984", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-10-05 13:06:56.414584", + "modified_by": "Administrator", + "module": "Buying", + "name": "Buying Settings", + "owner": "Administrator", + "reference_doctype": "Buying Settings", + "save_on_complete": 0, + "steps": [ + { + "description": "When a Supplier is saved, system generates a unique identity or name for that Supplier which can be used to refer the Supplier in various Buying transactions.", + "field": "", + "fieldname": "supp_master_name", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Supplier Naming By", + "parent_field": "", + "position": "Bottom", + "title": "Supplier Naming By" + }, + { + "description": "Configure what should be the default value of Supplier Group when creating a new Supplier.", + "field": "", + "fieldname": "supplier_group", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Supplier Group", + "parent_field": "", + "position": "Right", + "title": "Default Supplier Group" + }, + { + "description": "Item prices will be fetched from this Price List.", + "field": "", + "fieldname": "buying_price_list", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Buying Price List", + "parent_field": "", + "position": "Bottom", + "title": "Default Buying Price List" + }, + { + "description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice or a Purchase Receipt directly without creating a Purchase Order first.", + "field": "", + "fieldname": "po_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?", + "parent_field": "", + "position": "Bottom", + "title": "Purchase Order Required" + }, + { + "description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first.", + "field": "", + "fieldname": "pr_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Purchase Receipt Required for Purchase Invoice Creation?", + "parent_field": "", + "position": "Bottom", + "title": "Purchase Receipt Required" + } + ], + "title": "Buying Settings" +} \ No newline at end of file diff --git a/erpnext/buying/form_tour/purchase_order/purchase_order.json b/erpnext/buying/form_tour/purchase_order/purchase_order.json new file mode 100644 index 00000000000..3cc88fbf4fe --- /dev/null +++ b/erpnext/buying/form_tour/purchase_order/purchase_order.json @@ -0,0 +1,82 @@ +{ + "creation": "2021-07-29 14:11:58.271113", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-10-05 13:11:31.436135", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order", + "owner": "Administrator", + "reference_doctype": "Purchase Order", + "save_on_complete": 1, + "steps": [ + { + "description": "Select a Supplier", + "field": "", + "fieldname": "supplier", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Supplier", + "parent_field": "", + "position": "Right", + "title": "Supplier" + }, + { + "description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.", + "field": "", + "fieldname": "schedule_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Required By", + "parent_field": "", + "position": "Left", + "title": "Required By" + }, + { + "description": "Items to be purchased can be added here.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "Items Table" + }, + { + "child_doctype": "Purchase Order Item", + "description": "Enter the Item Code.", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 1, + "label": "Item Code", + "next_step_condition": "eval: doc.item_code", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Item Code" + }, + { + "child_doctype": "Purchase Order Item", + "description": "Enter the required quantity for the material.", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Quantity", + "parent_field": "", + "parent_fieldname": "items", + "position": "Bottom", + "title": "Quantity" + } + ], + "title": "Purchase Order" +} \ No newline at end of file diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json index 887f85b82d1..84e97a2d4d2 100644 --- a/erpnext/buying/module_onboarding/buying/buying.json +++ b/erpnext/buying/module_onboarding/buying/buying.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:28.273641", + "modified": "2021-08-24 18:13:42.463776", "modified_by": "Administrator", "module": "Buying", "name": "Buying", @@ -28,23 +28,11 @@ { "step": "Introduction to Buying" }, - { - "step": "Create a Supplier" - }, - { - "step": "Setup your Warehouse" - }, - { - "step": "Create a Product" - }, { "step": "Create a Material Request" }, { "step": "Create your first Purchase Order" - }, - { - "step": "Buying Settings" } ], "subtitle": "Products, Purchases, Analysis, and more.", diff --git a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json index 9dc493dd499..28e86ab0641 100644 --- a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json +++ b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json @@ -1,19 +1,21 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Let\u2019s create your first Material Request", "creation": "2020-05-15 14:39:09.818764", + "description": "# Track Material Request\n\n\nAlso known as Purchase Request or an Indent, is a document identifying a requirement of a set of items (products or services) for various purposes like procurement, transfer, issue, or manufacturing. Once the Material Request is validated, a purchase manager can take the next actions for purchasing items like requesting RFQ from a supplier or directly placing an order with an identified Supplier.\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-15 14:39:09.818764", + "modified": "2021-08-24 18:08:08.347501", "modified_by": "Administrator", "name": "Create a Material Request", "owner": "Administrator", "reference_document": "Material Request", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a Material Request", + "title": "Track Material Request", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json index 9dbed239789..18a39315861 100644 --- a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json +++ b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json @@ -1,19 +1,21 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Let\u2019s create your first Purchase Order", "creation": "2020-05-12 18:17:49.976035", + "description": "# Create first Purchase Order\n\nPurchase Order is at the heart of your buying transactions. In ERPNext, Purchase Order can can be created against a Purchase Material Request (indent) and Supplier Quotation as well. Purchase Orders is also linked to Purchase Receipt and Purchase Invoices, allowing you to keep a birds-eye view on your purchase deals.\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 18:31:56.856112", + "modified": "2021-08-24 18:08:08.936484", "modified_by": "Administrator", "name": "Create your first Purchase Order", "owner": "Administrator", "reference_document": "Purchase Order", + "show_form_tour": 0, "show_full_form": 0, - "title": "Create your first Purchase Order", + "title": "Create first Purchase Order", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json index fd98fddafae..01ac8b81760 100644 --- a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json +++ b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json @@ -1,19 +1,22 @@ { - "action": "Watch Video", + "action": "Show Form Tour", + "action_label": "Let\u2019s walk-through few Buying Settings", "creation": "2020-05-06 15:37:09.477765", + "description": "# Buying Settings\n\n\nBuying module\u2019s features are highly configurable as per your business needs. Buying Settings is the place where you can set your preferences for:\n\n- Supplier naming and default values\n- Billing and shipping preference in buying transactions\n\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, - "is_single": 0, + "is_single": 1, "is_skipped": 0, - "modified": "2020-05-12 18:25:08.509900", + "modified": "2021-08-24 18:08:08.345735", "modified_by": "Administrator", "name": "Introduction to Buying", "owner": "Administrator", - "show_full_form": 0, - "title": "Introduction to Buying", + "reference_document": "Buying Settings", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Buying Settings", "validate_action": 1, "video_url": "https://youtu.be/efFajTTQBa8" } \ No newline at end of file diff --git a/erpnext/stock/form_tour/material_request/material_request.json b/erpnext/stock/form_tour/material_request/material_request.json new file mode 100644 index 00000000000..145b4a06c2b --- /dev/null +++ b/erpnext/stock/form_tour/material_request/material_request.json @@ -0,0 +1,97 @@ +{ + "creation": "2021-07-29 12:32:08.929900", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-10-05 13:11:13.119453", + "modified_by": "Administrator", + "module": "Stock", + "name": "Material Request", + "owner": "Administrator", + "reference_doctype": "Material Request", + "save_on_complete": 1, + "steps": [ + { + "description": "The purpose of the material request can be selected here. For now select \"Purchase\" as the purpose.", + "field": "", + "fieldname": "material_request_type", + "fieldtype": "Select", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Purpose", + "next_step_condition": "eval: doc.material_request_type == \"Purchase\"", + "parent_field": "", + "position": "Bottom", + "title": "Purpose" + }, + { + "description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.", + "field": "", + "fieldname": "schedule_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Required By", + "next_step_condition": "", + "parent_field": "", + "position": "Left", + "title": "Required By" + }, + { + "description": "Setting the target warehouse sets it for all the items.", + "field": "", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Set Target Warehouse", + "next_step_condition": "", + "parent_field": "", + "position": "Left", + "title": "Target Warehouse" + }, + { + "description": "Items table", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "Items" + }, + { + "child_doctype": "Material Request Item", + "description": "Select an Item code. Item details will be fetched automatically.", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 1, + "label": "Item Code", + "next_step_condition": "eval: doc.item_code", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Item Code" + }, + { + "child_doctype": "Material Request Item", + "description": "Enter the required quantity for the material.", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Quantity", + "parent_field": "", + "parent_fieldname": "items", + "position": "Bottom", + "title": "Quantity" + } + ], + "title": "Material Request" +} \ No newline at end of file From dc4206428d86304d7b441532e0674b725b55d48d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 Oct 2021 10:41:18 +0530 Subject: [PATCH 17/41] fix: consolidated report not consider company currency --- erpnext/accounts/doctype/account/account.py | 6 +- .../consolidated_financial_statement.js | 5 +- .../consolidated_financial_statement.py | 72 ++++++++++++------- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index f6198eb23ba..6cfbc6d1521 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +import erpnext from frappe import _, throw from frappe.utils import cint, cstr from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of @@ -196,7 +197,7 @@ class Account(NestedSet): "company": company, # parent account's currency should be passed down to child account's curreny # if it is None, it picks it up from default company currency, which might be unintended - "account_currency": self.account_currency, + "account_currency": erpnext.get_company_currency(company), "parent_account": parent_acc_name_map[company] }) @@ -207,8 +208,7 @@ class Account(NestedSet): # update the parent company's value in child companies doc = frappe.get_doc("Account", child_account) parent_value_changed = False - for field in ['account_type', 'account_currency', - 'freeze_account', 'balance_must_be']: + for field in ['account_type', 'freeze_account', 'balance_must_be']: if doc.get(field) != self.get(field): parent_value_changed = True doc.set(field, self.get(field)) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 6a8301a6f91..e24a5f99184 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -103,8 +103,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { column.is_tree = true; } - value = default_formatter(value, row, column, data); + if (data && data.account && column.apply_currency_formatter) { + data.currency = erpnext.get_currency(column.company_name); + } + value = default_formatter(value, row, column, data); if (!data.parent_account) { value = $(`${value}`); diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index b0cfbac9cb1..bca13d50792 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.utils import cint, flt, getdate +import erpnext from erpnext.accounts.report.balance_sheet.balance_sheet import ( check_opening_balance, get_chart_data, @@ -31,7 +32,7 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import ( get_report_summary as get_pl_summary, ) -from erpnext.accounts.report.utils import convert_to_presentation_currency +from erpnext.accounts.report.utils import convert, convert_to_presentation_currency def execute(filters=None): @@ -42,7 +43,7 @@ def execute(filters=None): fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year')) companies_column, companies = get_companies(filters) - columns = get_columns(companies_column) + columns = get_columns(companies_column, filters) if filters.get('report') == "Balance Sheet": data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters) @@ -193,30 +194,37 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters): data["total"] = total return data -def get_columns(companies): - columns = [{ - "fieldname": "account", - "label": _("Account"), - "fieldtype": "Link", - "options": "Account", - "width": 300 - }] - - columns.append({ - "fieldname": "currency", - "label": _("Currency"), - "fieldtype": "Link", - "options": "Currency", - "hidden": 1 - }) +def get_columns(companies, filters): + columns = [ + { + "fieldname": "account", + "label": _("Account"), + "fieldtype": "Link", + "options": "Account", + "width": 300 + }, { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1 + } + ] for company in companies: + apply_currency_formatter = 1 if not filters.presentation_currency else 0 + currency = filters.presentation_currency + if not currency: + currency = erpnext.get_company_currency(company) + columns.append({ "fieldname": company, - "label": company, + "label": f'{company} ({currency})', "fieldtype": "Currency", "options": "currency", - "width": 150 + "width": 150, + "apply_currency_formatter": apply_currency_formatter, + "company_name": company }) return columns @@ -236,6 +244,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None end_date = filters.period_end_date + filters.end_date = end_date + gl_entries_by_account = {} for root in frappe.db.sql("""select lft, rgt from tabAccount where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1): @@ -246,7 +256,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters) accumulate_values_into_parents(accounts, accounts_by_name, companies) - out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency) + out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters) if out: add_total_row(out, root_type, balance_must_be, companies, company_currency) @@ -271,10 +281,20 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d # check if posting date is within the period if (entry.company == company or (filters.get('accumulated_in_group_company')) and entry.company in companies.get(company)): - d[company] = d.get(company, 0.0) + flt(entry.debit) - flt(entry.credit) + parent_company_currency = erpnext.get_company_currency(d.company) + child_company_currency = erpnext.get_company_currency(entry.company) + + debit, credit = flt(entry.debit), flt(entry.credit) + + if (entry.company != company and parent_company_currency != child_company_currency + and filters.get('accumulated_in_group_company')): + debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date) + credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date) + + d[company] = d.get(company, 0.0) + flt(debit) - flt(credit) if entry.posting_date < getdate(start_date): - d["opening_balance"] = d.get("opening_balance", 0.0) + flt(entry.debit) - flt(entry.credit) + d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit) def accumulate_values_into_parents(accounts, accounts_by_name, companies): """accumulate children's values in parent accounts""" @@ -353,7 +373,7 @@ def get_accounts(root_type, filters): `tabAccount` where company = %s and root_type = %s """ , (filters.get('company'), root_type), as_dict=1) -def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency): +def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters): data = [] for d in accounts: @@ -368,9 +388,10 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com "indent": flt(d.indent), "year_start_date": start_date, "year_end_date": end_date, - "currency": company_currency, + "currency": filters.presentation_currency, "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1) }) + for company in companies: if d.get(company) and balance_must_be == "Credit": # change sign based on Debit or Credit, since calculation is done using (debit - credit) @@ -385,6 +406,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com row["has_value"] = has_value row["total"] = total + data.append(row) return data From e85c6e3fc5d32d3eaef3e4c4032a916d42c4bb28 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:19:56 +0530 Subject: [PATCH 18/41] fix: SO delivery Date not getting set via data import (#27862) (#27864) * fix: SO delivery Date not getting set via data import * fix: logic to add delivery dates * fix: linting issue Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> Co-authored-by: Afshan (cherry picked from commit d9a219850a02539f82929b5d5490554835fcde8b) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/selling/doctype/sales_order/sales_order.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 93676094218..dcf478bda6e 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -110,7 +110,7 @@ class SalesOrder(SellingController): if self.order_type == 'Sales' and not self.skip_delivery_note: delivery_date_list = [d.delivery_date for d in self.get("items") if d.delivery_date] max_delivery_date = max(delivery_date_list) if delivery_date_list else None - if not self.delivery_date: + if (max_delivery_date and not self.delivery_date) or (max_delivery_date and getdate(self.delivery_date) != getdate(max_delivery_date)): self.delivery_date = max_delivery_date if self.delivery_date: for d in self.get("items"): @@ -119,8 +119,6 @@ class SalesOrder(SellingController): if getdate(self.transaction_date) > getdate(d.delivery_date): frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"), indicator='orange', title=_('Warning')) - if getdate(self.delivery_date) != getdate(max_delivery_date): - self.delivery_date = max_delivery_date else: frappe.throw(_("Please enter Delivery Date")) From 90a249527de1d3ccf952d2333acefff023e2884c Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:27:16 +0530 Subject: [PATCH 19/41] fix: update dead links in help_links.js (#27860) --- erpnext/public/js/help_links.js | 199 ++++++++++++-------------------- 1 file changed, 76 insertions(+), 123 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index f3dc9b74f5a..b643ccae947 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -5,7 +5,7 @@ const docsUrl = "https://erpnext.com/docs/"; frappe.help.help_links["Form/Rename Tool"] = [ { label: "Bulk Rename", - url: docsUrl + "user/manual/en/setting-up/data/bulk-rename", + url: docsUrl + "user/manual/en/using-erpnext/articles/bulk-rename", }, ]; @@ -59,10 +59,23 @@ frappe.help.help_links["Form/System Settings"] = [ }, ]; -frappe.help.help_links["data-import-tool"] = [ +frappe.help.help_links["Form/Data Import"] = [ { label: "Importing and Exporting Data", - url: docsUrl + "user/manual/en/setting-up/data/data-import-tool", + url: docsUrl + "user/manual/en/setting-up/data/data-import", + }, + { + label: "Overwriting Data from Data Import Tool", + url: + docsUrl + + "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool", + }, +]; + +frappe.help.help_links["List/Data Import"] = [ + { + label: "Importing and Exporting Data", + url: docsUrl + "user/manual/en/setting-up/data/data-import", }, { label: "Overwriting Data from Data Import Tool", @@ -101,14 +114,14 @@ frappe.help.help_links["Form/Global Defaults"] = [ }, ]; -frappe.help.help_links["Form/Email Digest"] = [ +frappe.help.help_links["List/Print Heading"] = [ { - label: "Email Digest", - url: docsUrl + "user/manual/en/setting-up/email/email-digest", + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", }, ]; -frappe.help.help_links["List/Print Heading"] = [ +frappe.help.help_links["Form/Print Heading"] = [ { label: "Print Heading", url: docsUrl + "user/manual/en/setting-up/print/print-headings", @@ -153,18 +166,25 @@ frappe.help.help_links["List/Email Account"] = [ frappe.help.help_links["List/Notification"] = [ { label: "Notification", - url: docsUrl + "user/manual/en/setting-up/email/notifications", + url: docsUrl + "user/manual/en/setting-up/notifications", }, ]; frappe.help.help_links["Form/Notification"] = [ { label: "Notification", - url: docsUrl + "user/manual/en/setting-up/email/notifications", + url: docsUrl + "user/manual/en/setting-up/notifications", }, ]; -frappe.help.help_links["List/Email Digest"] = [ +frappe.help.help_links["Form/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; + +frappe.help.help_links["Form/Email Digest"] = [ { label: "Email Digest", url: docsUrl + "user/manual/en/setting-up/email/email-digest", @@ -174,7 +194,7 @@ frappe.help.help_links["List/Email Digest"] = [ frappe.help.help_links["List/Auto Email Report"] = [ { label: "Auto Email Reports", - url: docsUrl + "user/manual/en/setting-up/email/email-reports", + url: docsUrl + "user/manual/en/setting-up/email/auto-email-reports", }, ]; @@ -188,14 +208,7 @@ frappe.help.help_links["Form/Print Settings"] = [ frappe.help.help_links["print-format-builder"] = [ { label: "Print Format Builder", - url: docsUrl + "user/manual/en/setting-up/print/print-settings", - }, -]; - -frappe.help.help_links["List/Print Heading"] = [ - { - label: "Print Heading", - url: docsUrl + "user/manual/en/setting-up/print/print-headings", + url: docsUrl + "user/manual/en/setting-up/print/print-format-builder", }, ]; @@ -315,7 +328,7 @@ frappe.help.help_links["Form/Sales Order"] = [ }, { label: "Recurring Sales Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Applying Discount", @@ -344,14 +357,14 @@ frappe.help.help_links["Form/Sales Order"] = [ frappe.help.help_links["Form/Product Bundle"] = [ { label: "Product Bundle", - url: docsUrl + "user/manual/en/selling/setup/product-bundle", + url: docsUrl + "user/manual/en/selling/product-bundle", }, ]; frappe.help.help_links["Form/Selling Settings"] = [ { label: "Selling Settings", - url: docsUrl + "user/manual/en/selling/setup/selling-settings", + url: docsUrl + "user/manual/en/selling/selling-settings", }, ]; @@ -435,24 +448,17 @@ frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [ }, ]; -frappe.help.help_links["List/POS Profile"] = [ - { - label: "POS Profile", - url: docsUrl + "user/manual/en/setting-up/pos-setting", - }, -]; - frappe.help.help_links["List/Price List"] = [ { label: "Price List", - url: docsUrl + "user/manual/en/setting-up/price-lists", + url: docsUrl + "user/manual/en/stock/price-lists", }, ]; frappe.help.help_links["List/Authorization Rule"] = [ { label: "Authorization Rule", - url: docsUrl + "user/manual/en/setting-up/authorization-rule", + url: docsUrl + "user/manual/en/customize-erpnext/authorization-rule", }, ]; @@ -468,27 +474,14 @@ frappe.help.help_links["List/Stock Reconciliation"] = [ label: "Stock Reconciliation", url: docsUrl + - "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item", + "user/manual/en/stock/stock-reconciliation", }, ]; frappe.help.help_links["Tree/Territory"] = [ { label: "Territory", - url: docsUrl + "user/manual/en/setting-up/territory", - }, -]; - -frappe.help.help_links["Form/Dropbox Backup"] = [ - { - label: "Dropbox Backup", - url: docsUrl + "user/manual/en/setting-up/third-party-backups", - }, - { - label: "Setting Up Dropbox Backup", - url: - docsUrl + - "user/manual/en/setting-up/articles/setting-up-dropbox-backups", + url: docsUrl + "user/manual/en/selling/territory", }, ]; @@ -501,12 +494,6 @@ frappe.help.help_links["List/Company"] = [ label: "Company", url: docsUrl + "user/manual/en/setting-up/company-setup", }, - { - label: "Managing Multiple Companies", - url: - docsUrl + - "user/manual/en/setting-up/articles/managing-multiple-companies", - }, { label: "Delete All Related Transactions for a Company", url: @@ -517,21 +504,6 @@ frappe.help.help_links["List/Company"] = [ //Accounts -frappe.help.help_links["modules/Accounts"] = [ - { - label: "Introduction to Accounts", - url: docsUrl + "user/manual/en/accounts/", - }, - { - label: "Chart of Accounts", - url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html", - }, - { - label: "Multi Currency Accounting", - url: docsUrl + "user/manual/en/accounts/multi-currency-accounting", - }, -]; - frappe.help.help_links["Tree/Account"] = [ { label: "Chart of Accounts", @@ -560,7 +532,7 @@ frappe.help.help_links["Form/Sales Invoice"] = [ }, { label: "Recurring Sales Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -571,7 +543,7 @@ frappe.help.help_links["List/Sales Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balances", }, { label: "Sales Return", @@ -579,21 +551,28 @@ frappe.help.help_links["List/Sales Invoice"] = [ }, { label: "Recurring Sales Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; -frappe.help.help_links["pos"] = [ +frappe.help.help_links["point-of-sale"] = [ { label: "Point of Sale Invoice", - url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice", + url: docsUrl + "user/manual/en/accounts/point-of-sales", }, ]; frappe.help.help_links["List/POS Profile"] = [ { label: "Point of Sale Profile", - url: docsUrl + "user/manual/en/setting-up/pos-setting", + url: docsUrl + "user/manual/en/accounts/pos-profile", + }, +]; + +frappe.help.help_links["Form/POS Profile"] = [ + { + label: "POS Profile", + url: docsUrl + "user/manual/en/accounts/pos-profile", }, ]; @@ -644,7 +623,7 @@ frappe.help.help_links["List/Payment Request"] = [ frappe.help.help_links["List/Asset"] = [ { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, ]; @@ -659,6 +638,8 @@ frappe.help.help_links["Tree/Cost Center"] = [ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, ]; +//Stock + frappe.help.help_links["List/Item"] = [ { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, { @@ -676,7 +657,7 @@ frappe.help.help_links["List/Item"] = [ }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, { label: "Item Codification", @@ -711,7 +692,7 @@ frappe.help.help_links["Form/Item"] = [ }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, { label: "Item Codification", @@ -771,10 +752,6 @@ frappe.help.help_links["Form/Delivery Note"] = [ url: docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", }, - { - label: "Subcontracting", - url: docsUrl + "user/manual/en/manufacturing/subcontracting", - }, ]; frappe.help.help_links["List/Installation Note"] = [ @@ -784,21 +761,10 @@ frappe.help.help_links["List/Installation Note"] = [ }, ]; -frappe.help.help_links["Tree"] = [ - { - label: "Managing Tree Structure Masters", - url: - docsUrl + - "user/manual/en/setting-up/articles/managing-tree-structure-masters", - }, -]; - frappe.help.help_links["List/Budget"] = [ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, ]; -//Stock - frappe.help.help_links["List/Material Request"] = [ { label: "Material Request", @@ -861,6 +827,10 @@ frappe.help.help_links["Form/Serial No"] = [ { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, ]; +frappe.help.help_links["List/Batch"] = [ + { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, +]; + frappe.help.help_links["Form/Batch"] = [ { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, ]; @@ -868,35 +838,35 @@ frappe.help.help_links["Form/Batch"] = [ frappe.help.help_links["Form/Packing Slip"] = [ { label: "Packing Slip", - url: docsUrl + "user/manual/en/stock/tools/packing-slip", + url: docsUrl + "user/manual/en/stock/packing-slip", }, ]; frappe.help.help_links["Form/Quality Inspection"] = [ { label: "Quality Inspection", - url: docsUrl + "user/manual/en/stock/tools/quality-inspection", + url: docsUrl + "user/manual/en/stock/quality-inspection", }, ]; frappe.help.help_links["Form/Landed Cost Voucher"] = [ { label: "Landed Cost Voucher", - url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher", + url: docsUrl + "user/manual/en/stock/landed-cost-voucher", }, ]; frappe.help.help_links["Tree/Item Group"] = [ { label: "Item Group", - url: docsUrl + "user/manual/en/stock/setup/item-group", + url: docsUrl + "user/manual/en/stock/item-group", }, ]; frappe.help.help_links["Form/Item Attribute"] = [ { label: "Item Attribute", - url: docsUrl + "user/manual/en/stock/setup/item-attribute", + url: docsUrl + "user/manual/en/stock/item-attribute", }, ]; @@ -911,7 +881,7 @@ frappe.help.help_links["Form/UOM"] = [ frappe.help.help_links["Form/Stock Reconciliation"] = [ { label: "Opening Stock Entry", - url: docsUrl + "user/manual/en/stock/opening-stock", + url: docsUrl + "user/manual/en/stock/stock-reconciliation", }, ]; @@ -938,13 +908,13 @@ frappe.help.help_links["Form/Newsletter"] = [ ]; frappe.help.help_links["Form/Campaign"] = [ - { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" }, + { label: "Campaign", url: docsUrl + "user/manual/en/CRM/campaign" }, ]; frappe.help.help_links["Tree/Sales Person"] = [ { label: "Sales Person", - url: docsUrl + "user/manual/en/CRM/setup/sales-person", + url: docsUrl + "user/manual/en/CRM/sales-person", }, ]; @@ -953,30 +923,13 @@ frappe.help.help_links["Form/Sales Person"] = [ label: "Sales Person Target", url: docsUrl + - "user/manual/en/selling/setup/sales-person-target-allocation", + "user/manual/en/selling/sales-person-target-allocation", }, -]; - -//Support - -frappe.help.help_links["List/Feedback Trigger"] = [ { - label: "Feedback Trigger", - url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback", - }, -]; - -frappe.help.help_links["List/Feedback Request"] = [ - { - label: "Feedback Request", - url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", - }, -]; - -frappe.help.help_links["List/Feedback Request"] = [ - { - label: "Feedback Request", - url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + label: "Sales Person in Transactions", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", }, ]; @@ -1019,7 +972,7 @@ frappe.help.help_links["Form/Operation"] = [ frappe.help.help_links["Form/BOM Update Tool"] = [ { label: "BOM Update Tool", - url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool", + url: docsUrl + "user/manual/en/manufacturing/bom-update-tool", }, ]; @@ -1036,7 +989,7 @@ frappe.help.help_links["Form/Customize Form"] = [ }, ]; -frappe.help.help_links["Form/Custom Field"] = [ +frappe.help.help_links["List/Custom Field"] = [ { label: "Custom Field", url: docsUrl + "user/manual/en/customize-erpnext/custom-field", From d3d4a3da620fcbc7cd0f608305a504dfa65befc0 Mon Sep 17 00:00:00 2001 From: yadavyk <32797974+yadavyk@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:39:33 +0530 Subject: [PATCH 20/41] fix: Salary Slip Label fixes (#27865) Co-authored-by: Rucha Mahabal --- erpnext/payroll/doctype/salary_slip/salary_slip.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 19744037a54..7a80e69374f 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -329,7 +329,7 @@ { "fieldname": "earning_deduction", "fieldtype": "Section Break", - "label": "Earning & Deduction", + "label": "Earnings & Deductions", "oldfieldtype": "Section Break" }, { @@ -380,7 +380,7 @@ "depends_on": "total_loan_repayment", "fieldname": "loan_repayment", "fieldtype": "Section Break", - "label": "Loan repayment" + "label": "Loan Repayment" }, { "fieldname": "loans", @@ -425,7 +425,7 @@ { "fieldname": "net_pay_info", "fieldtype": "Section Break", - "label": "net pay info" + "label": "Net Pay Info" }, { "fieldname": "net_pay", @@ -647,7 +647,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-09-01 10:35:52.374549", + "modified": "2021-10-08 11:47:47.098248", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", From 41fefa356f3cbade7a65527bb14a70534475aab1 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 8 Oct 2021 14:31:13 +0530 Subject: [PATCH 21/41] fix: remove readonly from billing address --- erpnext/stock/doctype/delivery_note/delivery_note.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 9bf142c4b44..ad1b3b43aee 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -395,8 +395,7 @@ "fieldtype": "Link", "label": "Billing Address Name", "options": "Address", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "tax_id", @@ -1309,7 +1308,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-09-28 13:10:09.761714", + "modified": "2021-10-08 14:29:13.428984", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 6019f60d0aa5e35ce8a218c328476cb0c9b3f6bf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 8 Oct 2021 16:12:15 +0530 Subject: [PATCH 22/41] fix(perf): index creation on voucher_detail_no (#27866) voucher_detail_no is supposed to have an index, it was added on on_doctype_update function of table, however this function is only called if DocType itself is updated and `on_update` is called on DocType. Stock ledger Entry doctype hasn't changed since addition of this index in function. Before: Lack of this index was causing full table scan in get_future_sle_to_fix function. (~50 seconds in a reposting job) After: Single row is fetched (~0.5 second in full reposting job) Learnings: 1. Add simple indexes via doctype only 2. For complex indexes always change doctype.json file for it to take effect. --- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.json | 5 +++-- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 2463a21ed61..40ae340bfea 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -142,6 +142,7 @@ "oldfieldtype": "Data", "print_width": "150px", "read_only": 1, + "search_index": 1, "width": "150px" }, { @@ -316,7 +317,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-07 11:10:35.318872", + "modified": "2021-10-08 12:42:51.857631", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", @@ -338,4 +339,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index caa1d42b662..382fdfa4bff 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -181,4 +181,3 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) - frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"]) From b0aa4a6e1cbf9d108fa51213db8888b67733537f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 10 Oct 2021 11:42:39 +0530 Subject: [PATCH 23/41] fix: patch fails if accounts are frozen --- erpnext/patches.txt | 2 +- .../patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 22a63139942..5ea666f00fb 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -299,7 +299,7 @@ erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.create_custom_field_for_finance_book -erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2 erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates \ No newline at end of file diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index fa8a86437d0..c2902ce9ef0 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -17,7 +17,7 @@ def execute(): where ref_exchange_rate = 1 and docstatus = 1 - and ifnull(exchange_gain_loss, '') != '' + and ifnull(exchange_gain_loss, 0) != 0 group by parent """, as_dict=1) @@ -30,7 +30,7 @@ def execute(): where ref_exchange_rate = 1 and docstatus = 1 - and ifnull(exchange_gain_loss, '') != '' + and ifnull(exchange_gain_loss, 0) != 0 group by parent """, as_dict=1) @@ -38,6 +38,8 @@ def execute(): if purchase_invoices + sales_invoices: frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) doc.docstatus = 2 @@ -46,4 +48,5 @@ def execute(): if advance.ref_exchange_rate == 1: advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 - doc.make_gl_entries() \ No newline at end of file + doc.make_gl_entries() + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file From 353ad5f6ffa7e4aaa472f3882d6ef19faa9d3431 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 10 Oct 2021 11:47:48 +0530 Subject: [PATCH 24/41] feat: handle exceptions --- .../modify_invalid_gain_loss_gl_entries.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index c2902ce9ef0..ddf70aa814c 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -39,14 +39,21 @@ def execute(): frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + for invoice in purchase_invoices + sales_invoices: - doc = frappe.get_doc(invoice.type, invoice.name) - doc.docstatus = 2 - doc.make_gl_entries() - for advance in doc.advances: - if advance.ref_exchange_rate == 1: - advance.db_set('exchange_gain_loss', 0, False) - doc.docstatus = 1 - doc.make_gl_entries() - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file + try: + doc = frappe.get_doc(invoice.type, invoice.name) + doc.docstatus = 2 + doc.make_gl_entries() + for advance in doc.advances: + if advance.ref_exchange_rate == 1: + advance.db_set('exchange_gain_loss', 0, False) + doc.docstatus = 1 + doc.make_gl_entries() + except Exception: + print(f'Failed to correct gl entries of {invoice.name}') + + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file From 19d14da0d485c82223a58dd57d3c22512ef337c0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 10 Oct 2021 14:00:25 +0530 Subject: [PATCH 25/41] fix: opening balance to calculate 'Unclosed Fiscal Years Profit / Loss (Credit)' --- erpnext/accounts/doctype/account/account.py | 3 +- .../consolidated_financial_statement.py | 96 +++++++++++++++---- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 6cfbc6d1521..605262f7b3e 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import frappe -import erpnext from frappe import _, throw from frappe.utils import cint, cstr from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of +import erpnext + class RootNotEditable(frappe.ValidationError): pass class BalanceMismatchError(frappe.ValidationError): pass diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index bca13d50792..e093f9618b4 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -8,8 +8,8 @@ from frappe import _ from frappe.utils import cint, flt, getdate import erpnext +from collections import defaultdict from erpnext.accounts.report.balance_sheet.balance_sheet import ( - check_opening_balance, get_chart_data, get_provisional_profit_loss, ) @@ -74,21 +74,24 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity, companies, filters.get('company'), company_currency, True) - message, opening_balance = check_opening_balance(asset, liability, equity) + message, opening_balance = prepare_companywise_opening_balance(asset, liability, equity, companies) - if opening_balance and round(opening_balance,2) !=0: - unclosed ={ + if opening_balance: + unclosed = { "account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "warn_if_negative": True, "currency": company_currency } - for company in companies: - unclosed[company] = opening_balance - if provisional_profit_loss: - provisional_profit_loss[company] = provisional_profit_loss[company] - opening_balance - unclosed["total"]=opening_balance + for company in companies: + unclosed[company] = opening_balance.get(company) + if provisional_profit_loss and provisional_profit_loss.get(company): + provisional_profit_loss[company] = ( + flt(provisional_profit_loss[company]) - flt(opening_balance.get(company)) + ) + + unclosed["total"] = opening_balance.get(company) data.append(unclosed) if provisional_profit_loss: @@ -103,6 +106,37 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): return data, message, chart, report_summary +def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, companies): + opening_balance = {} + for company in companies: + opening_value = 0 + + # opening_value = Aseet - liability - equity + for data in [asset_data, liability_data, equity_data]: + account_name = get_root_account_name(data[0].root_type, company) + opening_value += get_opening_balance(account_name, data, company) + + opening_balance[company] = opening_value + + if opening_balance: + return _("Previous Financial Year is not closed"), opening_balance + + return '', {} + +def get_opening_balance(account_name, data, company): + for row in data: + if row.get('account_name') == account_name: + return row.get('company_wise_opening_bal', {}).get(company, 0.0) + +def get_root_account_name(root_type, company): + return frappe.get_all( + 'Account', + fields=['account_name'], + filters = {'root_type': root_type, 'is_group': 1, + 'company': company, 'parent_account': ('is', 'not set')}, + as_list=1 + )[0][0] + def get_profit_loss_data(fiscal_year, companies, columns, filters): income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters) company_currency = get_company_currency(filters) @@ -254,8 +288,9 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i end_date, root.lft, root.rgt, filters, gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False) - calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters) + calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year) accumulate_values_into_parents(accounts, accounts_by_name, companies) + out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters) if out: @@ -267,7 +302,10 @@ def get_company_currency(filters=None): return (filters.get('presentation_currency') or frappe.get_cached_value('Company', filters.company, "default_currency")) -def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters): +def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year): + start_date = (fiscal_year.year_start_date + if filters.filter_based_on == 'Fiscal Year' else filters.period_start_date) + for entries in gl_entries_by_account.values(): for entry in entries: if entry.account_number: @@ -276,7 +314,9 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d account_name = entry.account_name d = accounts_by_name.get(account_name) + if d: + debit, credit = 0, 0 for company in companies: # check if posting date is within the period if (entry.company == company or (filters.get('accumulated_in_group_company')) @@ -286,13 +326,18 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d debit, credit = flt(entry.debit), flt(entry.credit) - if (entry.company != company and parent_company_currency != child_company_currency + if (not filters.get('presentation_currency') + and entry.company != company + and parent_company_currency != child_company_currency and filters.get('accumulated_in_group_company')): debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date) credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date) d[company] = d.get(company, 0.0) + flt(debit) - flt(credit) + if entry.posting_date < getdate(start_date): + d['company_wise_opening_bal'][company] += (flt(debit) - flt(credit)) + if entry.posting_date < getdate(start_date): d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit) @@ -302,17 +347,18 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies): if d.parent_account: account = d.parent_account_name - if not accounts_by_name.get(account): - continue + # if not accounts_by_name.get(account): + # continue for company in companies: accounts_by_name[account][company] = \ accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0) + accounts_by_name[account]['company_wise_opening_bal'][company] += d.get('company_wise_opening_bal', {}).get(company, 0.0) + accounts_by_name[account]["opening_balance"] = \ accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0) - def get_account_heads(root_type, companies, filters): accounts = get_accounts(root_type, filters) @@ -387,8 +433,10 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com "parent_account": _(d.parent_account), "indent": flt(d.indent), "year_start_date": start_date, + "root_type": d.root_type, "year_end_date": end_date, "currency": filters.presentation_currency, + "company_wise_opening_bal": d.company_wise_opening_bal, "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1) }) @@ -469,6 +517,7 @@ def get_account_details(account): 'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1) def validate_entries(key, entry, accounts_by_name, accounts): + # If an account present in the child company and not in the parent company if key not in accounts_by_name: args = get_account_details(entry.account) @@ -478,12 +527,23 @@ def validate_entries(key, entry, accounts_by_name, accounts): args.update({ 'lft': parent_args.lft + 1, 'rgt': parent_args.rgt - 1, + 'indent': 3, 'root_type': parent_args.root_type, - 'report_type': parent_args.report_type + 'report_type': parent_args.report_type, + 'parent_account_name': parent_args.account_name, + 'company_wise_opening_bal': defaultdict(float) }) accounts_by_name.setdefault(key, args) - accounts.append(args) + + idx = len(accounts) + # To identify parent account index + for index, row in enumerate(accounts): + if row.parent_account_name == args.parent_account_name: + idx = index + break + + accounts.insert(idx+1, args) def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions = [] @@ -513,7 +573,6 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency): for company in companies: total_row.setdefault(company, 0.0) total_row[company] += row.get(company, 0.0) - row[company] = 0.0 total_row.setdefault("total", 0.0) total_row["total"] += flt(row["total"]) @@ -533,6 +592,7 @@ def filter_accounts(accounts, depth=10): account_name = d.account_number + ' - ' + d.account_name else: account_name = d.account_name + d['company_wise_opening_bal'] = defaultdict(float) accounts_by_name[account_name] = d parent_children_map.setdefault(d.parent_account or None, []).append(d) From cb2213ef0cc066eb2ba40d6cb1d8ecf3d377d842 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sun, 10 Oct 2021 21:17:59 +0200 Subject: [PATCH 26/41] refactor: make strings translate --- erpnext/regional/report/ksa_vat/ksa_vat.py | 4 ++-- .../saudi_arabia/wizard/operations/setup_ksa_vat_setting.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 1697bbbd4d6..a42ebc9f7e5 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -150,7 +150,7 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): def append_data(data, title, amount, adjustment_amount, vat_amount): """Returns data with appended value.""" - data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) def get_tax_amount(item_code, account_head, doctype, parent): if doctype == 'Sales Invoice': @@ -173,4 +173,4 @@ def get_tax_amount(item_code, account_head, doctype, parent): tax_amount = value[1] break - return tax_amount \ No newline at end of file + return tax_amount diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index c30fcffc37c..3c89edd37ed 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -16,7 +16,6 @@ def create_ksa_vat_setting(company): with open(file_path, 'r') as json_file: account_data = json.load(json_file) - # Creating KSA VAT Setting ksa_vat_setting = frappe.get_doc({ 'doctype': 'KSA VAT Setting', @@ -44,4 +43,4 @@ def create_ksa_vat_setting(company): 'account': f'{account} - {company.abbr}' }) - ksa_vat_setting.save() \ No newline at end of file + ksa_vat_setting.save() From 4dc17a856ef8a4ed5950e6e94406ef5d0daeddb9 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Mon, 11 Oct 2021 10:37:59 +0530 Subject: [PATCH 27/41] fix(CI): Use bugbear instead of flake8-mutable --- .github/helper/.flake8_strict | 7 ++----- .pre-commit-config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict index c8337a9c125..a79137d7c32 100644 --- a/.github/helper/.flake8_strict +++ b/.github/helper/.flake8_strict @@ -1,6 +1,8 @@ [flake8] ignore = B007, + B009, + B010, B950, E101, E111, @@ -65,11 +67,6 @@ ignore = E713, E712, -enable-extensions = - M90 - -select = - M511 max-line-length = 200 exclude=.github/helper/semgrep_rules,test_*.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e411f11301e..b74d9a640da 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,9 +21,9 @@ repos: hooks: - id: flake8 additional_dependencies: [ - 'flake8-mutable', + 'flake8-bugbear', ] - args: ['--select=M511', '--config', '.github/helper/.flake8_strict'] + args: ['--config', '.github/helper/.flake8_strict'] exclude: ".*setup.py$" - repo: https://github.com/timothycrosley/isort From f0c4ea14a989192def4e06b018f5de03f39f2207 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Mon, 11 Oct 2021 11:59:26 +0530 Subject: [PATCH 28/41] fix(hr): Update expense account after company is updated (#27843) --- .../hr/doctype/expense_claim/expense_claim.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 3c4c672816c..218e97d7fc2 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -10,6 +10,26 @@ frappe.ui.form.on('Expense Claim', { }, company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + var expenses = frm.doc.expenses; + for (var i = 0; i < expenses.length; i++) { + var expense = expenses[i]; + if (!expense.expense_type) { + continue; + } + frappe.call({ + method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center", + args: { + "expense_claim_type": expense.expense_type, + "company": frm.doc.company + }, + callback: function(r) { + if (r.message) { + expense.default_account = r.message.account; + expense.cost_center = r.message.cost_center; + } + } + }); + } }, }); From 0a3dd3e954b3dbe19d01e0c77a33a8475567d494 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 11 Oct 2021 12:10:33 +0530 Subject: [PATCH 29/41] fix: bom item query #27890 fix: bom item query --- erpnext/manufacturing/doctype/bom/bom.py | 3 +-- erpnext/manufacturing/doctype/bom/test_bom.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 7cfec974fc8..232e3a0b0ff 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1133,8 +1133,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): query_filters["has_variants"] = 0 if filters and filters.get("is_stock_item"): - or_cond_filters["is_stock_item"] = 1 - or_cond_filters["has_variants"] = 1 + query_filters["is_stock_item"] = 1 return frappe.get_list("Item", fields = fields, filters=query_filters, diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 706ea268c6e..4c032307d80 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -4,13 +4,14 @@ import unittest from collections import deque +from functools import partial import frappe from frappe.test_runner import make_test_records from frappe.utils import cstr, flt from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order -from erpnext.manufacturing.doctype.bom.bom import make_variant_bom +from erpnext.manufacturing.doctype.bom.bom import item_query, make_variant_bom from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( @@ -375,6 +376,16 @@ class TestBOM(unittest.TestCase): # FG Items in Scrap/Loss Table should have Is Process Loss set self.assertRaises(frappe.ValidationError, bom_doc.submit) + def test_bom_item_query(self): + query = partial(item_query, doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters={"is_stock_item": 1}) + + test_items = query(txt="_Test") + filtered = query(txt="_Test Item 2") + + self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results") + self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") + + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) From 3337ae120cdf722c157b2048106399e2f4ca0b14 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Oct 2021 13:12:02 +0530 Subject: [PATCH 30/41] fix: Status check for closed loans --- .../doctype/loan_disbursement/loan_disbursement.py | 5 ++++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index 6d9d4f490d3..99f0d259246 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -198,7 +198,7 @@ def get_disbursal_amount(loan, on_current_security_price=0): security_value = get_total_pledged_security_value(loan) if loan_details.is_secured_loan and not on_current_security_price: - security_value = flt(loan_details.maximum_loan_amount) + security_value = get_maximum_amount_as_per_pledged_security(loan) if not security_value and not loan_details.is_secured_loan: security_value = flt(loan_details.loan_amount) @@ -209,3 +209,6 @@ def get_disbursal_amount(loan, on_current_security_price=0): disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount return disbursal_amount + +def get_maximum_amount_as_per_pledged_security(loan): + return flt(frappe.db.get_value('Loan Security Pledge', {'loan': loan}, 'sum(maximum_loan_value)')) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 13b73573274..40bb581165b 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -411,7 +411,7 @@ def get_amounts(amounts, against_loan, posting_date): if due_date and not final_due_date: final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) - if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'): + if against_loan_doc.status in ('Disbursed', 'Closed') or against_loan_doc.disbursed_amount >= against_loan_doc.loan_amount: pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid \ - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount else: From 7acdcc70ad4741fb0fd14750ab76630b15cb4440 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 11 Oct 2021 15:05:55 +0530 Subject: [PATCH 31/41] =?UTF-8?q?fix:=20v12=20migrate=20error=20-=20unknow?= =?UTF-8?q?n=20column=20=E2=80=98mandatory=5Fdepends=5Fon=E2=80=99=20(#278?= =?UTF-8?q?97)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: v12 doesn't have mandatory_depends_on field * fix: move update_vehicle_no_reqd_condition to v13 * fix: move update_vehicle_no_reqd_condition to v13 * fix: file name missing .py * refactor!: add back empty line * fix: linters issue --- .../consolidated_financial_statement.py | 3 ++- erpnext/patches.txt | 4 ++-- .../{v12_0 => v13_0}/update_vehicle_no_reqd_condition.py | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename erpnext/patches/{v12_0 => v13_0}/update_vehicle_no_reqd_condition.py (100%) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e093f9618b4..a600ead9e54 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -3,12 +3,13 @@ from __future__ import unicode_literals +from collections import defaultdict + import frappe from frappe import _ from frappe.utils import cint, flt, getdate import erpnext -from collections import defaultdict from erpnext.accounts.report.balance_sheet.balance_sheet import ( get_chart_data, get_provisional_profit_loss, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 22a63139942..c55fb0abd1b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -246,7 +246,7 @@ erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes -erpnext.patches.v12_0.update_vehicle_no_reqd_condition +erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae @@ -302,4 +302,4 @@ erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table -erpnext.patches.v13_0.add_default_interview_notification_templates \ No newline at end of file +erpnext.patches.v13_0.add_default_interview_notification_templates diff --git a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py similarity index 100% rename from erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py rename to erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py From ad444153cc899589b78e14168088dd6ff16d8f83 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 11 Oct 2021 22:14:28 +0530 Subject: [PATCH 32/41] fix: force reload custom field doctype (#27909) custom_field.json has the same modified key in both versions but not the same content. This can happen again if something is backported, safe solution is to force reload. --- erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py index 69bfaaa2cb1..902707b4b66 100644 --- a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py +++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py @@ -2,7 +2,7 @@ import frappe def execute(): - frappe.reload_doc('custom', 'doctype', 'custom_field') + frappe.reload_doc('custom', 'doctype', 'custom_field', force=True) company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: return From e1967870a9673cf8552c6de4a6a366df131672b3 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 12 Oct 2021 10:27:23 +0530 Subject: [PATCH 33/41] Merge pull request #27906 from Anuja-pawar/accounts-settings fix(Accounts Settings): Update label --- .../accounts/doctype/accounts_settings/accounts_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 7d0ecfbafd9..55ea571ebf8 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -174,7 +174,7 @@ "default": "0", "fieldname": "automatically_fetch_payment_terms", "fieldtype": "Check", - "label": "Automatically Fetch Payment Terms" + "label": "Automatically Fetch Payment Terms from Order" }, { "description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ", @@ -282,7 +282,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-08-19 11:17:38.788054", + "modified": "2021-10-11 17:42:36.427699", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 401e22fb8d8a0bcf07e8318e9a97a3e8891976e9 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Tue, 12 Oct 2021 10:28:36 +0530 Subject: [PATCH 34/41] fix(accounts): Fix issue with fetching loyalty point entries (#27892) --- .../accounts/doctype/loyalty_point_entry/loyalty_point_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py index 0813926f5f2..003389e0b51 100644 --- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py +++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py @@ -16,7 +16,7 @@ class LoyaltyPointEntry(Document): def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None): if not expiry_date: - date = today() + expiry_date = today() return frappe.db.sql(''' select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice From 8355af6dcf6a6e770c354133e1f08012ad9bdfc9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Oct 2021 12:44:40 +0530 Subject: [PATCH 35/41] fix: Incorrect maximum loan amount update --- .../loan_management/doctype/loan/loan.json | 3 +-- erpnext/loan_management/doctype/loan/loan.py | 25 ++++++++++++------- .../loan_application/loan_application.py | 7 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index c9f23ca4df3..5979992bbe8 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -334,7 +334,6 @@ }, { "depends_on": "eval:doc.is_secured_loan", - "fetch_from": "loan_application.maximum_loan_amount", "fieldname": "maximum_loan_amount", "fieldtype": "Currency", "label": "Maximum Loan Amount", @@ -360,7 +359,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:10:32.360818", + "modified": "2021-10-12 18:10:32.360818", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 7dbd42297e1..0f2c3cfdfc0 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -137,16 +137,23 @@ class Loan(AccountsController): frappe.throw(_("Loan amount is mandatory")) def link_loan_security_pledge(self): - if self.is_secured_loan: - loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application}, - 'name') + if self.is_secured_loan and self.loan_application: + maximum_loan_value = frappe.db.get_value('Loan Security Pledge', + { + 'loan_application': self.loan_application, + 'status': 'Requested' + }, + 'sum(maximum_loan_value)' + ) - if loan_security_pledge: - frappe.db.set_value('Loan Security Pledge', loan_security_pledge, { - 'loan': self.name, - 'status': 'Pledged', - 'pledge_time': now_datetime() - }) + if maximum_loan_value: + frappe.db.sql(""" + UPDATE `tabLoan Security Pledge` + SET loan = %s, pledge_time = %s, status = 'Pledged' + WHERE status = 'Requested' and loan_application = %s + """, (self.name, now_datetime(), self.loan_application)) + + self.db_set('maximum_loan_amount', maximum_loan_value) def unlink_loan_security_pledge(self): pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name}) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index e492920abb3..66a3be35130 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -130,10 +130,11 @@ class LoanApplication(Document): def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", - fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type} - )[0] + fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], + filters = {'name': source_doc.loan_type})[0] + if source_doc.is_secured_loan: + target_doc.maximum_loan_amount = 0 target_doc.mode_of_payment = account_details.mode_of_payment target_doc.payment_account = account_details.payment_account From af14ba43de755104bfab67b9e72ba838f30e355a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Oct 2021 12:58:42 +0530 Subject: [PATCH 36/41] fix: Linting issues --- .../doctype/loan_application/loan_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index 66a3be35130..ede0467b0e7 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -131,7 +131,7 @@ def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type})[0] + filters = {'name': source_doc.loan_type})[0] if source_doc.is_secured_loan: target_doc.maximum_loan_amount = 0 From c103f72faddf9b7afd9d58d44703e98c6e687605 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:13:48 +0530 Subject: [PATCH 37/41] fix: rollback on exception --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index ddf70aa814c..3af7dac3422 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -52,7 +52,9 @@ def execute(): advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 doc.make_gl_entries() + frappe.db.commit() except Exception: + frappe.db.rollback() print(f'Failed to correct gl entries of {invoice.name}') if acc_frozen_upto: From 569dc5f6b17da4c7d59f040bea97fa684ec1b156 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 12 Oct 2021 13:23:20 +0530 Subject: [PATCH 38/41] fix: add cost center in gl entry for advance payment entry (#27840) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 8037ca16aa0..0740ccd130b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -712,10 +712,14 @@ class PaymentEntry(AccountsController): dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" for d in self.get("references"): + cost_center = self.cost_center + if d.reference_doctype == "Sales Invoice" and not cost_center: + cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") gle = party_gl_dict.copy() gle.update({ "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name + "against_voucher": d.reference_name, + "cost_center": cost_center }) allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate), From 06fa35a9c132c387bdb99d7c6340d0dbe16ad5fe Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Sep 2021 20:05:16 +0530 Subject: [PATCH 39/41] test: add custom TestCase class and use in stock --- erpnext/stock/doctype/batch/test_batch.py | 6 ++---- .../doctype/delivery_note/test_delivery_note.py | 4 ++-- .../doctype/delivery_trip/test_delivery_trip.py | 6 ++++-- erpnext/stock/doctype/item/test_item.py | 6 +++--- .../item_alternative/test_item_alternative.py | 5 +++-- .../doctype/item_attribute/test_item_attribute.py | 6 +++--- .../stock/doctype/item_price/test_item_price.py | 6 +++--- .../test_landed_cost_voucher.py | 5 ++--- .../material_request/test_material_request.py | 5 ++--- .../doctype/packing_slip/test_packing_slip.py | 2 ++ erpnext/stock/doctype/pick_list/test_pick_list.py | 5 ++--- .../purchase_receipt/test_purchase_receipt.py | 3 ++- .../doctype/putaway_rule/test_putaway_rule.py | 5 ++--- .../quality_inspection/test_quality_inspection.py | 6 +++--- erpnext/stock/doctype/serial_no/test_serial_no.py | 5 ++--- erpnext/stock/doctype/shipment/test_shipment.py | 4 ++-- .../stock_ledger_entry/test_stock_ledger_entry.py | 5 ++--- .../test_stock_reconciliation.py | 6 ++---- .../doctype/stock_settings/test_stock_settings.py | 5 ++++- erpnext/stock/doctype/warehouse/test_warehouse.py | 6 +++--- .../stock_analytics/test_stock_analytics.py | 3 ++- erpnext/tests/utils.py | 15 +++++++++++++++ 22 files changed, 67 insertions(+), 52 deletions(-) diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 79989307efc..0a663c2a188 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -import unittest import frappe from frappe.exceptions import ValidationError @@ -11,9 +8,10 @@ from frappe.utils import cint, flt from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty from erpnext.stock.get_item_details import get_item_details +from erpnext.tests.utils import ERPNextTestCase -class TestBatch(unittest.TestCase): +class TestBatch(ERPNextTestCase): def test_item_has_batch_enabled(self): self.assertRaises(ValidationError, frappe.get_doc({ "doctype": "Batch", diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 7fda94b269d..f58b586ab20 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.utils import cstr, flt, nowdate, nowtime @@ -37,9 +36,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ) from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.tests.utils import ERPNextTestCase -class TestDeliveryNote(unittest.TestCase): +class TestDeliveryNote(ERPNextTestCase): def test_over_billing_against_dn(self): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index c9081c908f7..c6ff73e633b 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -14,11 +14,12 @@ from erpnext.stock.doctype.delivery_trip.delivery_trip import ( make_expense_claim, notify_customers, ) -from erpnext.tests.utils import create_test_contact_and_address +from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address -class TestDeliveryTrip(unittest.TestCase): +class TestDeliveryTrip(ERPNextTestCase): def setUp(self): + super().setUp() driver = create_driver() create_vehicle() create_delivery_notification() @@ -32,6 +33,7 @@ class TestDeliveryTrip(unittest.TestCase): frappe.db.sql("delete from `tabVehicle`") frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabDelivery Trip`") + return super().tearDown() def test_expense_claim_fields_are_fetched_properly(self): expense_claim = make_expense_claim(self.delivery_trip.name) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e911d35db38..9198272513b 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.test_runner import make_test_objects @@ -25,7 +24,7 @@ from erpnext.stock.doctype.item.item import ( ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details -from erpnext.tests.utils import change_settings +from erpnext.tests.utils import ERPNextTestCase, change_settings test_ignore = ["BOM"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] @@ -53,8 +52,9 @@ def make_item(item_code, properties=None): return item -class TestItem(unittest.TestCase): +class TestItem(ERPNextTestCase): def setUp(self): + super().setUp() frappe.flags.attribute_values = None def get_item(self, idx): diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index 2be8ef740a4..af6cc472e34 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.utils import flt @@ -21,10 +20,12 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.tests.utils import ERPNextTestCase -class TestItemAlternative(unittest.TestCase): +class TestItemAlternative(ERPNextTestCase): def setUp(self): + super().setUp() make_items() def test_alternative_item_for_subcontract_rm(self): diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py index fc809f443e6..2cd711bbb19 100644 --- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py @@ -3,17 +3,17 @@ from __future__ import unicode_literals -import unittest - import frappe test_records = frappe.get_test_records('Item Attribute') from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError +from erpnext.tests.utils import ERPNextTestCase -class TestItemAttribute(unittest.TestCase): +class TestItemAttribute(ERPNextTestCase): def setUp(self): + super().setUp() if frappe.db.exists("Item Attribute", "_Test_Length"): frappe.delete_doc("Item Attribute", "_Test_Length") diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 5ed80921660..3a51fbbe17e 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -3,17 +3,17 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.test_runner import make_test_records_for_doctype from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem from erpnext.stock.get_item_details import get_price_list_rate_for, process_args +from erpnext.tests.utils import ERPNextTestCase -class TestItemPrice(unittest.TestCase): +class TestItemPrice(ERPNextTestCase): def setUp(self): + super().setUp() frappe.db.sql("delete from `tabItem Price`") make_test_records_for_doctype("Item Price", force=True) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 58a72f72dd1..339eaaaf7a5 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -4,8 +4,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import flt @@ -16,9 +14,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import ( get_gl_entries, make_purchase_receipt, ) +from erpnext.tests.utils import ERPNextTestCase -class TestLandedCostVoucher(unittest.TestCase): +class TestLandedCostVoucher(ERPNextTestCase): def test_landed_cost_voucher(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 5c2ac2584f7..f66a228e35e 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import flt, today @@ -18,9 +16,10 @@ from erpnext.stock.doctype.material_request.material_request import ( make_supplier_quotation, raise_work_orders, ) +from erpnext.tests.utils import ERPNextTestCase -class TestMaterialRequest(unittest.TestCase): +class TestMaterialRequest(ERPNextTestCase): def test_make_purchase_order(self): mr = frappe.copy_doc(test_records[0]).insert() diff --git a/erpnext/stock/doctype/packing_slip/test_packing_slip.py b/erpnext/stock/doctype/packing_slip/test_packing_slip.py index 193adfcf1cb..c70cba67f2b 100644 --- a/erpnext/stock/doctype/packing_slip/test_packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/test_packing_slip.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import unittest # test_records = frappe.get_test_records('Packing Slip') +from erpnext.tests.utils import ERPNextTestCase + class TestPackingSlip(unittest.TestCase): pass diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index aa710ad0e97..fd0b3680df2 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] @@ -15,9 +13,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( EmptyStockReconciliationItemsError, ) +from erpnext.tests.utils import ERPNextTestCase -class TestPickList(unittest.TestCase): +class TestPickList(ERPNextTestCase): def test_pick_list_picks_warehouse_for_each_item(self): try: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 044856cca95..de177444285 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -17,9 +17,10 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction +from erpnext.tests.utils import ERPNextTestCase -class TestPurchaseReceipt(unittest.TestCase): +class TestPurchaseReceipt(ERPNextTestCase): def setUp(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py index 0aa7610575e..c25bca94dbb 100644 --- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe from erpnext.stock.doctype.batch.test_batch import make_new_batch @@ -13,9 +11,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.tests.utils import ERPNextTestCase -class TestPutawayRule(unittest.TestCase): +class TestPutawayRule(ERPNextTestCase): def setUp(self): if not frappe.db.exists("Item", "_Rice"): make_item("_Rice", { diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index f5d076a077a..308c62875d5 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -import unittest - import frappe from frappe.utils import nowdate @@ -15,12 +13,14 @@ from erpnext.controllers.stock_controller import ( from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase # test_records = frappe.get_test_records('Quality Inspection') -class TestQualityInspection(unittest.TestCase): +class TestQualityInspection(ERPNextTestCase): def setUp(self): + super().setUp() create_item("_Test Item with QA") frappe.db.set_value( "Item", "_Test Item with QA", "inspection_required_before_delivery", 1 diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 818c163c681..546e21bde04 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -20,9 +18,10 @@ test_dependencies = ["Item"] test_records = frappe.get_test_records('Serial No') from erpnext.stock.doctype.serial_no.serial_no import * +from erpnext.tests.utils import ERPNextTestCase -class TestSerialNo(unittest.TestCase): +class TestSerialNo(ERPNextTestCase): def test_cannot_create_direct(self): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 9914cf80158..288d874800c 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -3,15 +3,15 @@ # See license.txt from __future__ import unicode_literals -import unittest from datetime import date, timedelta import frappe from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment +from erpnext.tests.utils import ERPNextTestCase -class TestShipment(unittest.TestCase): +class TestShipment(ERPNextTestCase): def test_shipment_from_delivery_note(self): delivery_note = create_test_delivery_note() delivery_note.submit() diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 61bae49b0bd..ff33c2789b7 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe from frappe.core.page.permission_manager.permission_manager import reset from frappe.utils import add_days, today @@ -21,9 +19,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation, ) from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.tests.utils import ERPNextTestCase -class TestStockLedgerEntry(unittest.TestCase): +class TestStockLedgerEntry(ERPNextTestCase): def setUp(self): items = create_items() reset('Stock Entry') diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 8647bee40ec..b0a15379e69 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import add_days, flt, nowdate, nowtime, random_string @@ -22,10 +20,10 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method -from erpnext.tests.utils import change_settings +from erpnext.tests.utils import ERPNextTestCase, change_settings -class TestStockReconciliation(unittest.TestCase): +class TestStockReconciliation(ERPNextTestCase): @classmethod def setUpClass(self): create_batch_or_serial_no_items() diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py index 7e8090499fb..bf8ac5dc79a 100644 --- a/erpnext/stock/doctype/stock_settings/test_stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py @@ -7,9 +7,12 @@ import unittest import frappe +from erpnext.tests.utils import ERPNextTestCase -class TestStockSettings(unittest.TestCase): + +class TestStockSettings(ERPNextTestCase): def setUp(self): + super().setUp() frappe.db.set_value("Stock Settings", None, "clean_description_html", 0) def test_settings(self): diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 1ca7181f279..98317ec9c57 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -2,8 +2,6 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import unittest - import frappe from frappe.test_runner import make_test_records from frappe.utils import cint @@ -12,11 +10,13 @@ import erpnext from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('Warehouse') -class TestWarehouse(unittest.TestCase): +class TestWarehouse(ERPNextTestCase): def setUp(self): + super().setUp() if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py index 21e1205bfcc..32df5859375 100644 --- a/erpnext/stock/report/stock_analytics/test_stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py @@ -5,9 +5,10 @@ from frappe import _dict from erpnext.accounts.utils import get_fiscal_year from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges +from erpnext.tests.utils import ERPNextTestCase -class TestStockAnalyticsReport(unittest.TestCase): +class TestStockAnalyticsReport(ERPNextTestCase): def test_get_period_date_ranges(self): filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06") diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index a3cab4b59da..95e56683e12 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import copy +import unittest from contextlib import contextmanager from typing import Any, Dict, NewType, Optional @@ -12,6 +13,20 @@ ReportFilters = Dict[str, Any] ReportName = NewType("ReportName", str) +class ERPNextTestCase(unittest.TestCase): + """A sane default test class for ERPNext tests.""" + + def setUp(self) -> None: + frappe.db.commit() + return super().setUp() + + + def tearDown(self) -> None: + frappe.db.rollback() + return super().tearDown() + + + def create_test_contact_and_address(): frappe.db.sql('delete from tabContact') frappe.db.sql('delete from `tabContact Email`') From acdb26a4bb45787fad3ee56366cd7e6100c85c41 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 14:45:29 +0530 Subject: [PATCH 40/41] refactor: rollback after full test --- .../test_stock_reconciliation.py | 1 + erpnext/tests/utils.py | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index b0a15379e69..e82401d0738 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -26,6 +26,7 @@ from erpnext.tests.utils import ERPNextTestCase, change_settings class TestStockReconciliation(ERPNextTestCase): @classmethod def setUpClass(self): + super().setUpClass() create_batch_or_serial_no_items() frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 95e56683e12..91df5480e35 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -16,15 +16,16 @@ ReportName = NewType("ReportName", str) class ERPNextTestCase(unittest.TestCase): """A sane default test class for ERPNext tests.""" - def setUp(self) -> None: + + @classmethod + def setUpClass(cls) -> None: frappe.db.commit() - return super().setUp() + return super().setUpClass() - - def tearDown(self) -> None: + @classmethod + def tearDownClass(cls) -> None: frappe.db.rollback() - return super().tearDown() - + return super().tearDownClass() def create_test_contact_and_address(): From 8d69ec72a64eae0908aa0fb80a7325545db9a0cf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 15:26:37 +0530 Subject: [PATCH 41/41] fix: remove transaction commit from tests --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 -- .../report/procurement_tracker/test_procurement_tracker.py | 1 - .../hr/doctype/daily_work_summary/test_daily_work_summary.py | 1 - erpnext/selling/doctype/sales_order/test_sales_order.py | 1 - .../shopping_cart_settings/test_shopping_cart_settings.py | 1 - erpnext/stock/doctype/shipment/test_shipment.py | 2 -- .../stock_reconciliation/test_stock_reconciliation.py | 5 ----- 7 files changed, 13 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f492a03daf5..e11fe133836 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1087,8 +1087,6 @@ class TestSalesInvoice(unittest.TestCase): actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") - frappe.db.commit() - self.assertEqual(actual_qty_0 - 5, actual_qty_1) # outgoing_rate diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index a5b09473a05..fd23795287f 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -45,7 +45,6 @@ class TestProcurementTracker(unittest.TestCase): pr = make_purchase_receipt(po.name) pr.get("items")[0].cost_center = "Main - _TPC" pr.submit() - frappe.db.commit() date_obj = datetime.date(datetime.now()) po.load_from_db() diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py index bed12e31eaa..8a23682ad47 100644 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py @@ -74,7 +74,6 @@ class TestDailyWorkSummary(unittest.TestCase): from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \ where q.name = r.parent""", as_dict=1) - frappe.db.commit() def setup_groups(self, hour=None): # setup email to trigger at this hour diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index bbfe7c06d87..222e74ee6c5 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1382,7 +1382,6 @@ def make_sales_order_workflow(): frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True) frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True) - frappe.db.commit() frappe.cache().hdel('roles', frappe.session.user) workflow = frappe.get_doc({ diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py index f8a22b0e020..1164a5d3949 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -44,7 +44,6 @@ class TestShoppingCartSettings(unittest.TestCase): def test_tax_rule_validation(self): frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") - frappe.db.commit() cart_settings = self.get_cart_settings() cart_settings.enabled = 1 diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 288d874800c..dcd0b7c17c7 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -47,7 +47,6 @@ def create_test_delivery_note(): } ) delivery_note.insert() - frappe.db.commit() return delivery_note @@ -91,7 +90,6 @@ def create_test_shipment(delivery_notes = None): } ) shipment.insert() - frappe.db.commit() return shipment diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e82401d0738..415ac5eb267 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -371,7 +371,6 @@ class TestStockReconciliation(ERPNextTestCase): """ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.stock_ledger import NegativeStockError - frappe.db.commit() item_code = "Backdated-Reco-Cancellation-Item" warehouse = "_Test Warehouse - _TC" @@ -394,10 +393,6 @@ class TestStockReconciliation(ERPNextTestCase): repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") - # teardown - frappe.db.rollback() - - def test_valid_batch(self): create_batch_item_with_batch("Testing Batch Item 1", "001") create_batch_item_with_batch("Testing Batch Item 2", "002")