Merge branch 'version-13-hotfix' into email-digest

This commit is contained in:
Anupam Kumar
2021-07-28 18:24:29 +05:30
committed by GitHub
1203 changed files with 31372 additions and 27391 deletions

View File

@@ -90,12 +90,6 @@ frappe.ui.form.on("Company", {
frm.toggle_enable("default_currency", (frm.doc.__onload &&
!frm.doc.__onload.transactions_exist));
if (frm.has_perm('write')) {
frm.add_custom_button(__('Create Tax Template'), function() {
frm.trigger("make_default_tax_template");
});
}
if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});
@@ -121,17 +115,21 @@ frappe.ui.form.on("Company", {
}
if (frm.has_perm('write')) {
frm.add_custom_button(__('Default Tax Template'), function() {
frm.add_custom_button(__('Create Tax Template'), function() {
frm.trigger("make_default_tax_template");
}, __('Create'));
}, __('Manage'));
}
if (frappe.user.has_role('System Manager')) {
if (frm.has_perm('write')) {
frm.add_custom_button(__('Delete Transactions'), function() {
frm.trigger("delete_company_transactions");
}, __('Manage'));
}
}
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
if (!frappe.user.has_role('System Manager')) {
frm.get_field("delete_company_transactions").hide();
}
},
make_default_tax_template: function(frm) {
@@ -145,11 +143,6 @@ frappe.ui.form.on("Company", {
})
},
onload_post_render: function(frm) {
if(frm.get_field("delete_company_transactions").$input)
frm.get_field("delete_company_transactions").$input.addClass("btn-danger");
},
country: function(frm) {
erpnext.company.set_chart_of_accounts_options(frm.doc);
},
@@ -169,9 +162,9 @@ frappe.ui.form.on("Company", {
return;
}
frappe.call({
method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions",
method: "erpnext.setup.doctype.company.company.create_transaction_deletion_request",
args: {
company_name: data.company_name
company: data.company_name
},
freeze: true,
callback: function(r, rt) {
@@ -276,7 +269,7 @@ erpnext.company.setup_queries = function(frm) {
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
["unrealized_profit_loss_account", {"root_type": "Liability"},]
["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}]
], function(i, v) {
erpnext.company.set_custom_query(frm, v);
});

View File

@@ -99,7 +99,6 @@
"company_description",
"registration_info",
"registration_details",
"delete_company_transactions",
"lft",
"rgt",
"old_parent"
@@ -666,11 +665,6 @@
"oldfieldname": "registration_details",
"oldfieldtype": "Code"
},
{
"fieldname": "delete_company_transactions",
"fieldtype": "Button",
"label": "Delete Company Transactions"
},
{
"fieldname": "lft",
"fieldtype": "Int",
@@ -747,7 +741,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2021-02-16 15:53:37.167589",
"modified": "2021-05-07 03:11:28.189740",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -17,6 +17,7 @@ from frappe.utils.nestedset import NestedSet
from past.builtins import cmp
import functools
from erpnext.accounts.doctype.account.account import get_account_currency
from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges
class Company(NestedSet):
nsm_parent_field = 'parent_company'
@@ -53,7 +54,7 @@ class Company(NestedSet):
def validate_abbr(self):
if not self.abbr:
self.abbr = ''.join([c[0] for c in self.company_name.split()]).upper()
self.abbr = ''.join(c[0] for c in self.company_name.split()).upper()
self.abbr = self.abbr.strip()
@@ -68,11 +69,7 @@ class Company(NestedSet):
@frappe.whitelist()
def create_default_tax_template(self):
from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
create_sales_tax({
'country': self.country,
'company_name': self.name
})
setup_taxes_and_charges(self.name, self.country)
def validate_default_accounts(self):
accounts = [
@@ -113,7 +110,7 @@ class Company(NestedSet):
self.create_default_warehouses()
if frappe.flags.country_change:
install_country_fixtures(self.name)
install_country_fixtures(self.name, self.country)
self.create_default_tax_template()
if not frappe.db.get_value("Department", {"company": self.name}):
@@ -294,7 +291,7 @@ class Company(NestedSet):
cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name')
if cash and self.default_cash_account \
and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}):
mode_of_payment = frappe.get_doc('Mode of Payment', cash)
mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True)
mode_of_payment.append('accounts', {
'company': self.name,
'default_account': self.default_cash_account
@@ -338,7 +335,7 @@ class Company(NestedSet):
clear_defaults_cache()
def abbreviate(self):
self.abbr = ''.join([c[0].upper() for c in self.company_name.split()])
self.abbr = ''.join(c[0].upper() for c in self.company_name.split())
def on_trash(self):
"""
@@ -398,7 +395,7 @@ class Company(NestedSet):
@frappe.whitelist()
def enqueue_replace_abbr(company, old, new):
kwargs = dict(company=company, old=old, new=new)
kwargs = dict(queue="long", company=company, old=old, new=new)
frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
@@ -410,8 +407,6 @@ def replace_abbr(company, old, new):
frappe.only_for("System Manager")
frappe.db.set_value("Company", company, "abbr", new)
def _rename_record(doc):
parts = doc[0].rsplit(" - ", 1)
if len(parts) == 1 or parts[1].lower() == old.lower():
@@ -422,11 +417,18 @@ def replace_abbr(company, old, new):
doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company))
for d in doc:
_rename_record(d)
try:
frappe.db.auto_commit_on_many_writes = 1
frappe.db.set_value("Company", company, "abbr", new)
for dt in ["Warehouse", "Account", "Cost Center", "Department",
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
_rename_records(dt)
frappe.db.commit()
for dt in ["Warehouse", "Account", "Cost Center", "Department",
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
_rename_records(dt)
frappe.db.commit()
except Exception:
frappe.log_error(title=_('Abbreviation Rename Error'))
finally:
frappe.db.auto_commit_on_many_writes = 0
def get_name_with_abbr(name, company):
@@ -438,16 +440,15 @@ def get_name_with_abbr(name, company):
return " - ".join(parts)
def install_country_fixtures(company):
company_doc = frappe.get_doc("Company", company)
path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(company_doc.country))
def install_country_fixtures(company, country):
path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))
if os.path.exists(path.encode("utf-8")):
try:
module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(company_doc.country))
frappe.get_attr(module_name)(company_doc, False)
module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(country))
frappe.get_attr(module_name)(company, False)
except Exception as e:
frappe.log_error()
frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country)))
frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(country)))
def update_company_current_month_sales(company):
@@ -616,4 +617,13 @@ def get_default_company_address(name, sort_key='is_primary_address', existing_ad
if out:
return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0]
else:
return None
return None
@frappe.whitelist()
def create_transaction_deletion_request(company):
tdr = frappe.get_doc({
'doctype': 'Transaction Deletion Record',
'company': company
})
tdr.insert()
tdr.submit()

View File

@@ -1,117 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import cint
from frappe import _
from frappe.desk.notifications import clear_notifications
import functools
@frappe.whitelist()
def delete_company_transactions(company_name):
frappe.only_for("System Manager")
doc = frappe.get_doc("Company", company_name)
if frappe.session.user != doc.owner:
frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
frappe.PermissionError)
delete_bins(company_name)
delete_lead_addresses(company_name)
for doctype in frappe.db.sql_list("""select parent from
tabDocField where fieldtype='Link' and options='Company'"""):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
"Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile", "BOM",
"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
"Item Default", "Customer", "Supplier", "GST Account"):
delete_for_doctype(doctype, company_name)
# reset company values
doc.total_monthly_sales = 0
doc.sales_monthly_history = None
doc.save()
# Clear notification counts
clear_notifications()
def delete_for_doctype(doctype, company_name):
meta = frappe.get_meta(doctype)
company_fieldname = meta.get("fields", {"fieldtype": "Link",
"options": "Company"})[0].fieldname
if not meta.issingle:
if not meta.istable:
# delete communication
delete_communications(doctype, company_name, company_fieldname)
# delete children
for df in meta.get_table_fields():
frappe.db.sql("""delete from `tab{0}` where parent in
(select name from `tab{1}` where `{2}`=%s)""".format(df.options,
doctype, company_fieldname), company_name)
#delete version log
frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
(select name from `tab{0}` where `{1}`=%s)""".format(doctype,
company_fieldname), (doctype, company_name))
# delete parent
frappe.db.sql("""delete from `tab{0}`
where {1}= %s """.format(doctype, company_fieldname), company_name)
# reset series
naming_series = meta.get_field("naming_series")
if naming_series and naming_series.options:
prefixes = sorted(naming_series.options.split("\n"),
key=functools.cmp_to_key(lambda a, b: len(b) - len(a)))
for prefix in prefixes:
if prefix:
last = frappe.db.sql("""select max(name) from `tab{0}`
where name like %s""".format(doctype), prefix + "%")
if last and last[0][0]:
last = cint(last[0][0].replace(prefix, ""))
else:
last = 0
frappe.db.sql("""update tabSeries set current = %s
where name=%s""", (last, prefix))
def delete_bins(company_name):
frappe.db.sql("""delete from tabBin where warehouse in
(select name from tabWarehouse where company=%s)""", company_name)
def delete_lead_addresses(company_name):
"""Delete addresses to which leads are linked"""
leads = frappe.get_all("Lead", filters={"company": company_name})
leads = [ "'%s'"%row.get("name") for row in leads ]
addresses = []
if leads:
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
in ({leads})""".format(leads=",".join(leads)))
if addresses:
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
def delete_communications(doctype, company_name, company_fieldname):
reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
reference_doc_names = [r.name for r in reference_docs]
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]})
communication_names = [c.name for c in communications]
frappe.delete_doc("Communication", communication_names, ignore_permissions=True)

View File

@@ -86,15 +86,6 @@ class TestCompany(unittest.TestCase):
self.delete_mode_of_payment(template)
frappe.delete_doc("Company", template)
def test_delete_communication(self):
from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
company = create_child_company()
lead = create_test_lead_in_company(company)
communication = create_company_communication("Lead", lead)
delete_communications("Lead", "Test Company", "company")
self.assertFalse(frappe.db.exists("Communcation", communication))
self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
def delete_mode_of_payment(self, company):
frappe.db.sql(""" delete from `tabMode of Payment Account`
where company =%s """, (company))

View File

@@ -60,6 +60,9 @@ class EmailDigest(Document):
reference_name = self.name,
unsubscribe_message = _("Unsubscribe from this Email Digest"))
frappe.set_user(original_user)
frappe.set_user_lang(original_user)
def get_msg_html(self):
"""Build email digest content"""
frappe.flags.ignore_account_permission = True
@@ -240,7 +243,7 @@ class EmailDigest(Document):
card = cache.get(cache_key)
if card:
card = eval(card)
card = frappe.safe_eval(card)
else:
card = frappe._dict(getattr(self, "get_" + key)())
@@ -799,7 +802,6 @@ def get_incomes_expenses_for_period(account, from_date, to_date):
val = balance_on_to_date - balance_before_from_date
else:
last_year_closing_balance = get_balance_on(account, date=fy_start_date - timedelta(days=1))
print(fy_start_date - timedelta(days=1), last_year_closing_balance)
val = balance_on_to_date + (last_year_closing_balance - balance_before_from_date)
return val
@@ -828,4 +830,4 @@ def get_future_date_for_calendaer_event(frequency):
elif frequency == "Monthly":
to_date = add_to_date(from_date, months=1)
return from_date, to_date
return from_date, to_date

View File

@@ -59,12 +59,14 @@ class GlobalDefaults(Document):
# Make property setters to hide rounded total fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
"Supplier Quotation", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check")
make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check")
"Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"):
make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check")
make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check")
make_property_setter(doctype, "rounded_total", "hidden", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "rounded_total", "print_hide", self.disable_rounded_total, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "disable_rounded_total", "default", cint(self.disable_rounded_total), "Text", validate_fields_for_doctype=False)
def toggle_in_words(self):
self.disable_in_words = cint(self.disable_in_words)
@@ -72,5 +74,5 @@ class GlobalDefaults(Document):
# Make property setters to hide in words fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
"Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"):
make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check")
make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check")
make_property_setter(doctype, "in_words", "hidden", self.disable_in_words, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "in_words", "print_hide", self.disable_in_words, "Check", validate_fields_for_doctype=False)

View File

@@ -61,7 +61,7 @@ frappe.ui.form.on("Item Group", {
frappe.set_route("List", "Item", {"item_group": frm.doc.name});
});
}
frappe.model.with_doctype('Item', () => {
const item_meta = frappe.get_meta('Item');
@@ -69,10 +69,12 @@ frappe.ui.form.on("Item Group", {
df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
).map(df => ({ label: df.label, value: df.fieldname }));
const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname);
field.fieldtype = 'Select';
field.options = valid_fields;
frm.fields_dict.filter_fields.grid.refresh();
frm.fields_dict.filter_fields.grid.update_docfield_property(
'fieldname', 'fieldtype', 'Select'
);
frm.fields_dict.filter_fields.grid.update_docfield_property(
'fieldname', 'options', valid_fields
);
});
},

View File

@@ -87,11 +87,11 @@ class ItemGroup(NestedSet, WebsiteGenerator):
if not field_filters:
field_filters = {}
# Ensure the query remains within current item group
field_filters['item_group'] = self.name
# Ensure the query remains within current item group & sub group
field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)]
engine = ProductQuery()
context.items = engine.query(attribute_filters, field_filters, search, start)
context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
filter_engine = ProductFiltersBuilder(self.name)
@@ -139,7 +139,7 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non
# return child item groups if the type is of "Is Group"
return get_child_groups_for_list_in_html(item_group, start, limit, search)
child_groups = ", ".join([frappe.db.escape(i[0]) for i in get_child_groups(product_group)])
child_groups = ", ".join(frappe.db.escape(i[0]) for i in get_child_groups(product_group))
# base query
query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
@@ -239,7 +239,7 @@ def get_item_for_list_in_html(context):
return frappe.get_template(products_template).render(context)
def get_group_item_count(item_group):
child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(item_group)])
child_groups = ", ".join('"' + i[0] + '"' for i in get_child_groups(item_group))
return frappe.db.sql("""select count(*) from `tabItem`
where docstatus = 0 and show_in_website = 1
and (item_group in (%s)

View File

@@ -159,6 +159,7 @@ class NamingSeries(Document):
if frappe.db.get_value('Series', series, 'name', order_by="name") == None:
frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series))
@frappe.whitelist()
def update_series_start(self):
if self.prefix:
prefix = self.parse_naming_series()
@@ -182,8 +183,8 @@ class NamingSeries(Document):
def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True):
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
if naming_series:
make_property_setter(doctype, "naming_series", "hidden", 0, "Check")
make_property_setter(doctype, "naming_series", "reqd", 1, "Check")
make_property_setter(doctype, "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "naming_series", "reqd", 1, "Check", validate_fields_for_doctype=False)
# set values for mandatory
try:
@@ -194,15 +195,15 @@ def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True
pass
if hide_name_field:
make_property_setter(doctype, fieldname, "reqd", 0, "Check")
make_property_setter(doctype, fieldname, "hidden", 1, "Check")
make_property_setter(doctype, fieldname, "reqd", 0, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, fieldname, "hidden", 1, "Check", validate_fields_for_doctype=False)
else:
make_property_setter(doctype, "naming_series", "reqd", 0, "Check")
make_property_setter(doctype, "naming_series", "hidden", 1, "Check")
make_property_setter(doctype, "naming_series", "reqd", 0, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False)
if hide_name_field:
make_property_setter(doctype, fieldname, "hidden", 0, "Check")
make_property_setter(doctype, fieldname, "reqd", 1, "Check")
make_property_setter(doctype, fieldname, "hidden", 0, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, fieldname, "reqd", 1, "Check", validate_fields_for_doctype=False)
# set values for mandatory
frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=`name` where

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestTransactionDeletionRecord(unittest.TestCase):
def setUp(self):
create_company('Dunder Mifflin Paper Co')
def tearDown(self):
frappe.db.rollback()
def test_doctypes_contain_company_field(self):
tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
for doctype in tdr.doctypes:
contains_company = False
doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()['fields']
for doctype_field in doctype_fields:
if doctype_field['fieldtype'] == 'Link' and doctype_field['options'] == 'Company':
contains_company = True
break
self.assertTrue(contains_company)
def test_no_of_docs_is_correct(self):
for i in range(5):
create_task('Dunder Mifflin Paper Co')
tdr = create_transaction_deletion_request('Dunder Mifflin Paper Co')
for doctype in tdr.doctypes:
if doctype.doctype_name == 'Task':
self.assertEqual(doctype.no_of_docs, 5)
def test_deletion_is_successful(self):
create_task('Dunder Mifflin Paper Co')
create_transaction_deletion_request('Dunder Mifflin Paper Co')
tasks_containing_company = frappe.get_all('Task',
filters = {
'company' : 'Dunder Mifflin Paper Co'
})
self.assertEqual(tasks_containing_company, [])
def create_company(company_name):
company = frappe.get_doc({
'doctype': 'Company',
'company_name': company_name,
'default_currency': 'INR'
})
company.insert(ignore_if_duplicate = True)
def create_transaction_deletion_request(company):
tdr = frappe.get_doc({
'doctype': 'Transaction Deletion Record',
'company': company
})
tdr.insert()
tdr.submit()
return tdr
def create_task(company):
task = frappe.get_doc({
'doctype': 'Task',
'company': company,
'subject': 'Delete'
})
task.insert()

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Transaction Deletion Record', {
onload: function(frm) {
if (frm.doc.docstatus == 0) {
let doctypes_to_be_ignored_array;
frappe.call({
method: 'erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored',
callback: function(r) {
doctypes_to_be_ignored_array = r.message;
populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
frm.refresh_field('doctypes_to_be_ignored');
}
});
}
frm.get_field('doctypes_to_be_ignored').grid.cannot_add_rows = true;
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
frm.refresh_field('doctypes_to_be_ignored');
},
refresh: function(frm) {
frm.fields_dict['doctypes_to_be_ignored'].grid.set_column_disp('no_of_docs', false);
frm.refresh_field('doctypes_to_be_ignored');
}
});
function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) {
if (!(frm.doc.doctypes_to_be_ignored)) {
var i;
for (i = 0; i < doctypes_to_be_ignored_array.length; i++) {
frm.add_child('doctypes_to_be_ignored', {
doctype_name: doctypes_to_be_ignored_array[i]
});
}
}
}

View File

@@ -0,0 +1,79 @@
{
"actions": [],
"autoname": "TDL.####",
"creation": "2021-04-06 20:17:18.404716",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"doctypes",
"doctypes_to_be_ignored",
"amended_from",
"status"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "doctypes",
"fieldtype": "Table",
"label": "Summary",
"options": "Transaction Deletion Record Item",
"read_only": 1
},
{
"fieldname": "doctypes_to_be_ignored",
"fieldtype": "Table",
"label": "Excluded DocTypes",
"options": "Transaction Deletion Record Item"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Transaction Deletion Record",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"hidden": 1,
"label": "Status",
"options": "Draft\nCompleted"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-05-08 23:13:48.049879",
"modified_by": "Administrator",
"module": "Setup",
"name": "Transaction Deletion Record",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.utils import cint
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.desk.notifications import clear_notifications
class TransactionDeletionRecord(Document):
def validate(self):
frappe.only_for('System Manager')
self.validate_doctypes_to_be_ignored()
def validate_doctypes_to_be_ignored(self):
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
for doctype in self.doctypes_to_be_ignored:
if doctype.doctype_name not in doctypes_to_be_ignored_list:
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
title=_("Not Allowed"))
def before_submit(self):
if not self.doctypes_to_be_ignored:
self.populate_doctypes_to_be_ignored_table()
self.delete_bins()
self.delete_lead_addresses()
self.reset_company_values()
clear_notifications()
self.delete_company_transactions()
def populate_doctypes_to_be_ignored_table(self):
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
for doctype in doctypes_to_be_ignored_list:
self.append('doctypes_to_be_ignored', {
'doctype_name' : doctype
})
def delete_bins(self):
frappe.db.sql("""delete from tabBin where warehouse in
(select name from tabWarehouse where company=%s)""", self.company)
def delete_lead_addresses(self):
"""Delete addresses to which leads are linked"""
leads = frappe.get_all('Lead', filters={'company': self.company})
leads = ["'%s'" % row.get("name") for row in leads]
addresses = []
if leads:
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
in ({leads})""".format(leads=",".join(leads)))
if addresses:
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
def reset_company_values(self):
company_obj = frappe.get_doc('Company', self.company)
company_obj.total_monthly_sales = 0
company_obj.sales_monthly_history = None
company_obj.save()
def delete_company_transactions(self):
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
tables = self.get_all_child_doctypes()
for docfield in docfields:
if docfield['parent'] != self.doctype:
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
if no_of_docs > 0:
self.delete_version_log(docfield['parent'], docfield['fieldname'])
self.delete_communications(docfield['parent'], docfield['fieldname'])
self.populate_doctypes_table(tables, docfield['parent'], no_of_docs)
self.delete_child_tables(docfield['parent'], docfield['fieldname'])
self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
if naming_series:
if '#' in naming_series:
self.update_naming_series(naming_series, docfield['parent'])
def get_doctypes_to_be_ignored_list(self):
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
doctypes_to_be_ignored_list = singles
for doctype in self.doctypes_to_be_ignored:
doctypes_to_be_ignored_list.append(doctype.doctype_name)
return doctypes_to_be_ignored_list
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
docfields = frappe.get_all('DocField',
filters = {
'fieldtype': 'Link',
'options': 'Company',
'parent': ['not in', doctypes_to_be_ignored_list]},
fields=['parent', 'fieldname'])
return docfields
def get_all_child_doctypes(self):
return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname):
return frappe.db.count(doctype, {company_fieldname : self.company})
def populate_doctypes_table(self, tables, doctype, no_of_docs):
if doctype not in tables:
self.append('doctypes', {
'doctype_name' : doctype,
'no_of_docs' : no_of_docs
})
def delete_child_tables(self, doctype, company_fieldname):
parent_docs_to_be_deleted = frappe.get_all(doctype, {
company_fieldname : self.company
}, pluck = 'name')
child_tables = frappe.get_all('DocField', filters = {
'fieldtype': 'Table',
'parent': doctype
}, pluck = 'options')
for table in child_tables:
frappe.db.delete(table, {
'parent': ['in', parent_docs_to_be_deleted]
})
def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
frappe.db.delete(doctype, {
company_fieldname : self.company
})
def update_naming_series(self, naming_series, doctype_name):
if '.' in naming_series:
prefix, hashes = naming_series.rsplit('.', 1)
else:
prefix, hashes = naming_series.rsplit('{', 1)
last = frappe.db.sql("""select max(name) from `tab{0}`
where name like %s""".format(doctype_name), prefix + '%')
if last and last[0][0]:
last = cint(last[0][0].replace(prefix, ''))
else:
last = 0
frappe.db.sql("""update tabSeries set current = %s where name=%s""", (last, prefix))
def delete_version_log(self, doctype, company_fieldname):
frappe.db.sql("""delete from `tabVersion` where ref_doctype=%s and docname in
(select name from `tab{0}` where `{1}`=%s)""".format(doctype,
company_fieldname), (doctype, self.company))
def delete_communications(self, doctype, company_fieldname):
reference_docs = frappe.get_all(doctype, filters={company_fieldname:self.company})
reference_doc_names = [r.name for r in reference_docs]
communications = frappe.get_all('Communication', filters={'reference_doctype':doctype,'reference_name':['in', reference_doc_names]})
communication_names = [c.name for c in communications]
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
@frappe.whitelist()
def get_doctypes_to_be_ignored():
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
'Party Account', 'Employee', 'Sales Taxes and Charges Template',
'Purchase Taxes and Charges Template', 'POS Profile', 'BOM',
'Company', 'Bank Account', 'Item Tax Template', 'Mode of Payment',
'Item Default', 'Customer', 'Supplier', 'GST Account']
return doctypes_to_be_ignored_list

View File

@@ -0,0 +1,12 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.listview_settings['Transaction Deletion Record'] = {
get_indicator: function(doc) {
if (doc.docstatus == 0) {
return [__("Draft"), "red"];
} else {
return [__("Completed"), "green"];
}
}
};

View File

@@ -0,0 +1,39 @@
{
"actions": [],
"creation": "2021-04-07 07:34:00.124124",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"doctype_name",
"no_of_docs"
],
"fields": [
{
"fieldname": "doctype_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "DocType",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "no_of_docs",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Number of Docs"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-08 23:10:46.166744",
"modified_by": "Administrator",
"module": "Setup",
"name": "Transaction Deletion Record Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class TransactionDeletionRecordItem(Document):
pass

View File

@@ -8,9 +8,11 @@ from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import D
from .default_success_action import get_default_success_action
from frappe import _
from frappe.utils import cint
from frappe.installer import update_site_config
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules
from six import iteritems
default_mail_footer = """<div style="padding: 7px; text-align: right; color: #888"><small>Sent via
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
@@ -29,6 +31,7 @@ def after_install():
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
add_non_standard_user_types()
frappe.db.commit()
@@ -36,7 +39,7 @@ def check_setup_wizard_not_completed():
if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0):
message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed.
You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall"""
frappe.throw(message)
frappe.throw(message) # nosemgrep
def set_single_defaults():
@@ -164,3 +167,81 @@ def add_standard_navbar_items():
def add_app_name():
frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
def add_non_standard_user_types():
user_types = get_user_types_data()
user_type_limit = {}
for user_type, data in iteritems(user_types):
user_type_limit.setdefault(frappe.scrub(user_type), 10)
update_site_config('user_type_doctype_limit', user_type_limit)
for user_type, data in iteritems(user_types):
create_custom_role(data)
create_user_type(user_type, data)
def get_user_types_data():
return {
'Employee Self Service': {
'role': 'Employee Self Service',
'apply_user_permission_on': 'Employee',
'user_id_field': 'user_id',
'doctypes': {
'Salary Slip': ['read'],
'Employee': ['read', 'write'],
'Expense Claim': ['read', 'write', 'create', 'delete'],
'Leave Application': ['read', 'write', 'create', 'delete'],
'Attendance Request': ['read', 'write', 'create', 'delete'],
'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
}
}
}
def create_custom_role(data):
if data.get('role') and not frappe.db.exists('Role', data.get('role')):
frappe.get_doc({
'doctype': 'Role',
'role_name': data.get('role'),
'desk_access': 1,
'is_custom': 1
}).insert(ignore_permissions=True)
def create_user_type(user_type, data):
if frappe.db.exists('User Type', user_type):
doc = frappe.get_cached_doc('User Type', user_type)
doc.user_doctypes = []
else:
doc = frappe.new_doc('User Type')
doc.update({
'name': user_type,
'role': data.get('role'),
'user_id_field': data.get('user_id_field'),
'apply_user_permission_on': data.get('apply_user_permission_on')
})
create_role_permissions_for_doctype(doc, data)
doc.save(ignore_permissions=True)
def create_role_permissions_for_doctype(doc, data):
for doctype, perms in iteritems(data.get('doctypes')):
args = {'document_type': doctype}
for perm in perms:
args[perm] = 1
doc.append('user_doctypes', args)
def update_select_perm_after_install():
if not frappe.flags.update_select_perm_after_migrate:
return
frappe.flags.ignore_select_perm = False
for row in frappe.get_all('User Type', filters= {'is_standard': 0}):
print('Updating user type :- ', row.name)
doc = frappe.get_doc('User Type', row.name)
doc.save()
frappe.flags.update_select_perm_after_migrate = False

File diff suppressed because it is too large Load Diff

View File

@@ -42,29 +42,6 @@ def enable_shopping_cart(args):
'quotation_series': "QTN-",
}).insert()
def create_bank_account(args):
if args.get("bank_account"):
company_name = args.get('company_name')
bank_account_group = frappe.db.get_value("Account",
{"account_type": "Bank", "is_group": 1, "root_type": "Asset",
"company": company_name})
if bank_account_group:
bank_account = frappe.get_doc({
"doctype": "Account",
'account_name': args.get("bank_account"),
'parent_account': bank_account_group,
'is_group':0,
'company': company_name,
"account_type": "Bank",
})
try:
return bank_account.insert()
except RootNotEditable:
frappe.throw(_("Bank account cannot be named as {0}").format(args.get("bank_account")))
except frappe.DuplicateEntryError:
# bank account same as a CoA entry
pass
def create_email_digest():
from frappe.utils.user import get_system_managers
system_managers = get_system_managers(only_name=True)

View File

@@ -12,6 +12,7 @@ from frappe.desk.doctype.global_search_settings.global_search_settings import up
from erpnext.accounts.doctype.account.account import RootNotEditable
from erpnext.regional.address_template.setup import set_up_address_templates
from frappe.utils.nestedset import rebuild_tree
default_lead_sources = ["Existing Customer", "Reference", "Advertisement",
"Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing",
@@ -280,13 +281,15 @@ def install(country=None):
set_more_defaults()
update_global_search_doctypes()
# path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))
# if os.path.exists(path.encode("utf-8")):
# frappe.get_attr("erpnext.regional.{0}.setup.setup_company_independent_fixtures".format(frappe.scrub(country)))()
def set_more_defaults():
# Do more setup stuff that can be done here with no dependencies
update_selling_defaults()
update_buying_defaults()
update_hr_defaults()
add_uom_data()
update_item_variant_settings()
def update_selling_defaults():
selling_settings = frappe.get_doc("Selling Settings")
selling_settings.set_default_customer_group_and_territory()
selling_settings.cust_master_name = "Customer Name"
@@ -296,13 +299,7 @@ def set_more_defaults():
selling_settings.sales_update_frequency = "Each Transaction"
selling_settings.save()
add_uom_data()
# set no copy fields of an item doctype to item variant settings
doc = frappe.get_doc('Item Variant Settings')
doc.set_default_fields()
doc.save()
def update_buying_defaults():
buying_settings = frappe.get_doc("Buying Settings")
buying_settings.supp_master_name = "Supplier Name"
buying_settings.po_required = "No"
@@ -311,12 +308,19 @@ def set_more_defaults():
buying_settings.allow_multiple_items = 1
buying_settings.save()
def update_hr_defaults():
hr_settings = frappe.get_doc("HR Settings")
hr_settings.emp_created_by = "Naming Series"
hr_settings.leave_approval_notification_template = _("Leave Approval Notification")
hr_settings.leave_status_notification_template = _("Leave Status Notification")
hr_settings.save()
def update_item_variant_settings():
# set no copy fields of an item doctype to item variant settings
doc = frappe.get_doc('Item Variant Settings')
doc.set_default_fields()
doc.save()
def add_uom_data():
# add UOMs
uoms = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_data.json")).read())
@@ -327,7 +331,7 @@ def add_uom_data():
"uom_name": _(d.get("uom_name")),
"name": _(d.get("uom_name")),
"must_be_whole_number": d.get("must_be_whole_number")
}).insert(ignore_permissions=True)
}).db_insert()
# bootstrap uom conversion factors
uom_conversions = json.loads(open(frappe.get_app_path("erpnext", "setup", "setup_wizard", "data", "uom_conversion_data.json")).read())
@@ -336,7 +340,7 @@ def add_uom_data():
frappe.get_doc({
"doctype": "UOM Category",
"category_name": _(d.get("category"))
}).insert(ignore_permissions=True)
}).db_insert()
if not frappe.db.exists("UOM Conversion Factor", {"from_uom": _(d.get("from_uom")), "to_uom": _(d.get("to_uom"))}):
uom_conversion = frappe.get_doc({
@@ -369,8 +373,8 @@ def add_sale_stages():
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
]
make_records(records)
for sales_stage in records:
frappe.get_doc(sales_stage).db_insert()
def install_company(args):
records = [
@@ -418,7 +422,14 @@ def install_post_company_fixtures(args=None):
{'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': args.company_name},
]
make_records(records)
# Make root department with NSM updation
make_records(records[:1])
frappe.local.flags.ignore_update_nsm = True
make_records(records[1:])
frappe.local.flags.ignore_update_nsm = False
rebuild_tree("Department", "parent_department")
def install_defaults(args=None):
@@ -432,7 +443,17 @@ def install_defaults(args=None):
# enable default currency
frappe.db.set_value("Currency", args.get("currency"), "enabled", 1)
frappe.db.set_value("Stock Settings", None, "email_footer_address", args.get("company_name"))
set_global_defaults(args)
set_active_domains(args)
update_stock_settings()
update_shopping_cart_settings(args)
args.update({"set_default": 1})
create_bank_account(args)
def set_global_defaults(args):
global_defaults = frappe.get_doc("Global Defaults", "Global Defaults")
current_fiscal_year = frappe.get_all("Fiscal Year")[0]
@@ -445,13 +466,10 @@ def install_defaults(args=None):
global_defaults.save()
system_settings = frappe.get_doc("System Settings")
system_settings.email_footer_address = args.get("company_name")
system_settings.save()
domain_settings = frappe.get_single('Domain Settings')
domain_settings.set_active_domains(args.get('domains'))
def set_active_domains(args):
frappe.get_single('Domain Settings').set_active_domains(args.get('domains'))
def update_stock_settings():
stock_settings = frappe.get_doc("Stock Settings")
stock_settings.item_naming_by = "Item Code"
stock_settings.valuation_method = "FIFO"
@@ -463,48 +481,47 @@ def install_defaults(args=None):
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
stock_settings.save()
if args.bank_account:
company_name = args.company_name
bank_account_group = frappe.db.get_value("Account",
{"account_type": "Bank", "is_group": 1, "root_type": "Asset",
"company": company_name})
if bank_account_group:
bank_account = frappe.get_doc({
"doctype": "Account",
'account_name': args.bank_account,
'parent_account': bank_account_group,
'is_group':0,
'company': company_name,
"account_type": "Bank",
})
try:
doc = bank_account.insert()
def create_bank_account(args):
if not args.get('bank_account'):
return
frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False)
company_name = args.get('company_name')
bank_account_group = frappe.db.get_value("Account",
{"account_type": "Bank", "is_group": 1, "root_type": "Asset",
"company": company_name})
if bank_account_group:
bank_account = frappe.get_doc({
"doctype": "Account",
'account_name': args.get('bank_account'),
'parent_account': bank_account_group,
'is_group':0,
'company': company_name,
"account_type": "Bank",
})
try:
doc = bank_account.insert()
except RootNotEditable:
frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account))
except frappe.DuplicateEntryError:
# bank account same as a CoA entry
pass
if args.get('set_default'):
frappe.db.set_value("Company", args.get('company_name'), "default_bank_account", bank_account.name, update_modified=False)
# Now, with fixtures out of the way, onto concrete stuff
records = [
return doc
# Shopping cart: needs price lists
{
"doctype": "Shopping Cart Settings",
"enabled": 1,
'company': args.company_name,
# uh oh
'price_list': frappe.db.get_value("Price List", {"selling": 1}),
'default_customer_group': _("Individual"),
'quotation_series': "QTN-",
},
]
make_records(records)
except RootNotEditable:
frappe.throw(_("Bank account cannot be named as {0}").format(args.get('bank_account')))
except frappe.DuplicateEntryError:
# bank account same as a CoA entry
pass
def update_shopping_cart_settings(args):
shopping_cart = frappe.get_doc("Shopping Cart Settings")
shopping_cart.update({
"enabled": 1,
'company': args.company_name,
'price_list': frappe.db.get_value("Price List", {"selling": 1}),
'default_customer_group': _("Individual"),
'quotation_series': "QTN-",
})
shopping_cart.update_single(shopping_cart.get_valid_dict())
def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year

View File

@@ -1,123 +1,295 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe, copy, os, json
from frappe.utils import flt
from erpnext.accounts.doctype.account.account import RootNotEditable
def create_sales_tax(args):
country_wise_tax = get_country_wise_tax(args.get("country"))
if country_wise_tax and len(country_wise_tax) > 0:
for sales_tax, tax_data in country_wise_tax.items():
make_tax_account_and_template(
args.get("company_name"),
tax_data.get('account_name'),
tax_data.get('tax_rate'), sales_tax)
import os
import json
def make_tax_account_and_template(company, account_name, tax_rate, template_name=None):
if not isinstance(account_name, (list, tuple)):
account_name = [account_name]
tax_rate = [tax_rate]
import frappe
from frappe import _
accounts = []
for i, name in enumerate(account_name):
tax_account = make_tax_account(company, account_name[i], tax_rate[i])
if tax_account:
accounts.append(tax_account)
try:
if accounts:
make_sales_and_purchase_tax_templates(accounts, template_name)
make_item_tax_templates(accounts, template_name)
except frappe.NameError:
if frappe.message_log: frappe.message_log.pop()
except RootNotEditable:
pass
def setup_taxes_and_charges(company_name: str, country: str):
if not frappe.db.exists('Company', company_name):
frappe.throw(_('Company {} does not exist yet. Taxes setup aborted.').format(company_name))
def make_tax_account(company, account_name, tax_rate):
tax_group = get_tax_account_group(company)
if tax_group:
file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json')
with open(file_path, 'r') as json_file:
tax_data = json.load(json_file)
country_wise_tax = tax_data.get(country)
if not country_wise_tax:
return
if 'chart_of_accounts' not in country_wise_tax:
country_wise_tax = simple_to_detailed(country_wise_tax)
from_detailed_data(company_name, country_wise_tax)
update_regional_tax_settings(country, company_name)
def simple_to_detailed(templates):
"""
Convert a simple taxes object into a more detailed data structure.
Example input:
{
"France VAT 20%": {
"account_name": "VAT 20%",
"tax_rate": 20,
"default": 1
},
"France VAT 10%": {
"account_name": "VAT 10%",
"tax_rate": 10
}
}
"""
return {
'chart_of_accounts': {
'*': {
'item_tax_templates': [{
'title': title,
'taxes': [{
'tax_type': {
'account_name': data.get('account_name'),
'tax_rate': data.get('tax_rate')
}
}]
} for title, data in templates.items()],
'*': [{
'title': title,
'is_default': data.get('default', 0),
'taxes': [{
'account_head': {
'account_name': data.get('account_name'),
'tax_rate': data.get('tax_rate')
}
}]
} for title, data in templates.items()]
}
}
}
def from_detailed_data(company_name, data):
"""Create Taxes and Charges Templates from detailed data."""
coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts')
coa_data = data.get('chart_of_accounts', {})
tax_templates = coa_data.get(coa_name) or coa_data.get('*', {})
tax_categories = data.get('tax_categories')
sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*', {})
purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*', {})
item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*', {})
if tax_categories:
for tax_category in tax_categories:
make_tax_catgory(tax_category)
if sales_tax_templates:
for template in sales_tax_templates:
make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template)
if purchase_tax_templates:
for template in purchase_tax_templates:
make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template)
if item_tax_templates:
for template in item_tax_templates:
make_item_tax_template(company_name, template)
def update_regional_tax_settings(country, company):
path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))
if os.path.exists(path.encode("utf-8")):
try:
return frappe.get_doc({
"doctype":"Account",
"company": company,
"parent_account": tax_group,
"account_name": account_name,
"is_group": 0,
"report_type": "Balance Sheet",
"root_type": "Liability",
"account_type": "Tax",
"tax_rate": flt(tax_rate) if tax_rate else None
}).insert(ignore_permissions=True, ignore_mandatory=True)
except frappe.NameError:
if frappe.message_log: frappe.message_log.pop()
abbr = frappe.get_cached_value('Company', company, 'abbr')
account = '{0} - {1}'.format(account_name, abbr)
return frappe.get_doc('Account', account)
module_name = "erpnext.regional.{0}.setup.update_regional_tax_settings".format(frappe.scrub(country))
frappe.get_attr(module_name)(country, company)
except Exception as e:
# Log error and ignore if failed to setup regional tax settings
frappe.log_error()
pass
def make_sales_and_purchase_tax_templates(accounts, template_name=None):
if not template_name:
template_name = accounts[0].name
def make_taxes_and_charges_template(company_name, doctype, template):
template['company'] = company_name
template['doctype'] = doctype
sales_tax_template = {
"doctype": "Sales Taxes and Charges Template",
"title": template_name,
"company": accounts[0].company,
'taxes': []
}
if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
return
for account in accounts:
sales_tax_template['taxes'].append({
"category": "Total",
"charge_type": "On Net Total",
"account_head": account.name,
"description": "{0} @ {1}".format(account.account_name, account.tax_rate),
"rate": account.tax_rate
})
# Sales
frappe.get_doc(copy.deepcopy(sales_tax_template)).insert(ignore_permissions=True)
for tax_row in template.get('taxes'):
account_data = tax_row.get('account_head')
tax_row_defaults = {
'category': 'Total',
'charge_type': 'On Net Total'
}
# Purchase
purchase_tax_template = copy.deepcopy(sales_tax_template)
purchase_tax_template["doctype"] = "Purchase Taxes and Charges Template"
if doctype == 'Purchase Taxes and Charges Template':
tax_row_defaults['add_deduct_tax'] = 'Add'
doc = frappe.get_doc(purchase_tax_template)
# if account_head is a dict, search or create the account and get it's name
if isinstance(account_data, dict):
tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate'))
tax_row_defaults['rate'] = account_data.get('tax_rate')
account = get_or_create_account(company_name, account_data)
tax_row['account_head'] = account.name
# use the default value if nothing other is specified
for fieldname, default_value in tax_row_defaults.items():
if fieldname not in tax_row:
tax_row[fieldname] = default_value
doc = frappe.get_doc(template)
# Data in country wise json is already pre validated, hence validations can be ignored
# Ingone validations to make doctypes faster
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.insert(ignore_permissions=True)
return doc
def make_item_tax_templates(accounts, template_name=None):
if not template_name:
template_name = accounts[0].name
def make_item_tax_template(company_name, template):
"""Create an Item Tax Template.
item_tax_template = {
"doctype": "Item Tax Template",
"title": template_name,
"company": accounts[0].company,
'taxes': []
}
This requires a separate method because Item Tax Template is structured
differently from Sales and Purchase Tax Templates.
"""
doctype = 'Item Tax Template'
template['company'] = company_name
template['doctype'] = doctype
if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
return
for account in accounts:
item_tax_template['taxes'].append({
"tax_type": account.name,
"tax_rate": account.tax_rate
for tax_row in template.get('taxes'):
account_data = tax_row.get('tax_type')
# if tax_type is a dict, search or create the account and get it's name
if isinstance(account_data, dict):
account = get_or_create_account(company_name, account_data)
tax_row['tax_type'] = account.name
if 'tax_rate' not in tax_row:
tax_row['tax_rate'] = account_data.get('tax_rate')
doc = frappe.get_doc(template)
# Data in country wise json is already pre validated, hence validations can be ignored
# Ingone validations to make doctypes faster
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.insert(ignore_permissions=True)
return doc
def make_tax_category(tax_category):
""" Make tax category based on title if not already created """
doctype = 'Tax Category'
if not frappe.db.exists(doctype, tax_category['title']):
tax_category['doctype'] = doctype
doc = frappe.get_doc(tax_category)
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.insert(ignore_permissions=True)
def get_or_create_account(company_name, account):
"""
Check if account already exists. If not, create it.
Return a tax account or None.
"""
default_root_type = 'Liability'
root_type = account.get('root_type', default_root_type)
existing_accounts = frappe.get_list('Account',
filters={
'company': company_name,
'root_type': root_type
},
or_filters={
'account_name': account.get('account_name'),
'account_number': account.get('account_number')
})
# Items
frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True)
if existing_accounts:
return frappe.get_doc('Account', existing_accounts[0].name)
def get_tax_account_group(company):
tax_group = frappe.db.get_value("Account",
{"account_name": "Duties and Taxes", "is_group": 1, "company": company})
if not tax_group:
tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability",
"account_type": "Tax", "company": company})
tax_group = get_or_create_tax_group(company_name, root_type)
return tax_group
account['doctype'] = 'Account'
account['company'] = company_name
account['parent_account'] = tax_group
account['report_type'] = 'Balance Sheet'
account['account_type'] = 'Tax'
account['root_type'] = root_type
account['is_group'] = 0
def get_country_wise_tax(country):
data = {}
with open (os.path.join(os.path.dirname(__file__), "..", "data", "country_wise_tax.json")) as countrywise_tax:
data = json.load(countrywise_tax).get(country)
doc = frappe.get_doc(account)
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.insert(ignore_permissions=True, ignore_mandatory=True)
return doc
return data
def get_or_create_tax_group(company_name, root_type):
# Look for a group account of type 'Tax'
tax_group_name = frappe.db.get_value('Account', {
'is_group': 1,
'root_type': root_type,
'account_type': 'Tax',
'company': company_name
})
if tax_group_name:
return tax_group_name
# Look for a group account named 'Duties and Taxes' or 'Tax Assets'
account_name = _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets')
tax_group_name = frappe.db.get_value('Account', {
'is_group': 1,
'root_type': root_type,
'account_name': account_name,
'company': company_name
})
if tax_group_name:
return tax_group_name
# Create a new group account named 'Duties and Taxes' or 'Tax Assets' just
# below the root account
root_account = frappe.get_list('Account', {
'is_group': 1,
'root_type': root_type,
'company': company_name,
'report_type': 'Balance Sheet',
'parent_account': ('is', 'not set')
}, limit=1)[0]
tax_group_account = frappe.get_doc({
'doctype': 'Account',
'company': company_name,
'is_group': 1,
'report_type': 'Balance Sheet',
'root_type': root_type,
'account_type': 'Tax',
'account_name': account_name,
'parent_account': root_account.name
})
tax_group_account.flags.ignore_links = True
tax_group_account.flags.ignore_validate = True
tax_group_account.insert(ignore_permissions=True)
tax_group_name = tax_group_account.name
return tax_group_name
def make_tax_catgory(tax_category):
doctype = 'Tax Category'
if isinstance(tax_category, str):
tax_category = {'title': tax_category}
tax_category['doctype'] = doctype
if not frappe.db.exists(doctype, tax_category['title']):
doc = frappe.get_doc(tax_category)
doc.insert(ignore_permissions=True)

View File

@@ -51,11 +51,6 @@ def get_setup_stages(args=None):
'status': _('Setting defaults'),
'fail_msg': 'Failed to set defaults',
'tasks': [
{
'fn': setup_post_company_fixtures,
'args': args,
'fail_msg': _("Failed to setup post company fixtures")
},
{
'fn': setup_defaults,
'args': args,
@@ -94,9 +89,6 @@ def stage_fixtures(args):
def setup_company(args):
fixtures.install_company(args)
def setup_post_company_fixtures(args):
fixtures.install_post_company_fixtures(args)
def setup_defaults(args):
fixtures.install_defaults(frappe._dict(args))
@@ -129,7 +121,6 @@ def login_as_first_user(args):
def setup_complete(args=None):
stage_fixtures(args)
setup_company(args)
setup_post_company_fixtures(args)
setup_defaults(args)
stage_four(args)
fin(args)

View File

@@ -9,5 +9,4 @@ def complete():
'data', 'test_mfg.json'), 'r') as f:
data = json.loads(f.read())
#setup_wizard.create_sales_tax(data)
setup_complete(data)

View File

@@ -11,10 +11,11 @@
"hide_custom": 0,
"icon": "settings",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"label": "ERPNext Settings",
"links": [],
"modified": "2020-12-01 13:38:37.759596",
"modified": "2021-06-12 01:58:11.399566",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@@ -109,6 +110,13 @@
"label": "Domain Settings",
"link_to": "Domain Settings",
"type": "DocType"
},
{
"doc_view": "",
"icon": "retail",
"label": "Products Settings",
"link_to": "Products Settings",
"type": "DocType"
}
]
}
}

View File

@@ -248,177 +248,9 @@
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Healthcare",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Patient",
"link_to": "Patient",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Diagnosis",
"link_to": "Diagnosis",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Education",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Student",
"link_to": "Student",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Instructor",
"link_to": "Instructor",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Course",
"link_to": "Course",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Room",
"link_to": "Room",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Non Profit",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Donor",
"link_to": "Donor",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Member",
"link_to": "Member",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Volunteer",
"link_to": "Volunteer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chapter",
"link_to": "Chapter",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Agriculture",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Location",
"link_to": "Location",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Crop",
"link_to": "Crop",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Crop Cycle",
"link_to": "Crop Cycle",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Fertilizer",
"link_to": "Fertilizer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
}
],
"modified": "2021-03-16 15:59:58.416154",
"modified": "2021-04-19 15:48:44.089927",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",