Merge branch 'version-13-hotfix' of https://github.com/frappe/erpnext into backport-asset-repair-refactor

This commit is contained in:
GangaManoj
2021-08-06 18:12:24 +05:30
156 changed files with 3002 additions and 3046 deletions

View File

@@ -291,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
@@ -395,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)

View File

@@ -1,78 +1,31 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
cur_frm.cscript.refresh = function(doc, dt, dn) {
doc = locals[dt][dn];
cur_frm.add_custom_button(__('View Now'), function() {
frappe.call({
method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg',
args: {
name: doc.name
},
callback: function(r) {
var d = new frappe.ui.Dialog({
title: __('Email Digest: ') + dn,
width: 800
frappe.ui.form.on("Email Digest", {
refresh: function(frm) {
if (!frm.is_new()) {
frm.add_custom_button(__('View Now'), function() {
frappe.call({
method: 'erpnext.setup.doctype.email_digest.email_digest.get_digest_msg',
args: {
name: frm.doc.name
},
callback: function(r) {
let d = new frappe.ui.Dialog({
title: __('Email Digest: {0}', [frm.doc.name]),
width: 800
});
$(d.body).html(r.message);
d.show();
}
});
$(d.body).html(r.message);
d.show();
}
});
}, "fa fa-eye-open", "btn-default");
if (!cur_frm.is_new()) {
cur_frm.add_custom_button(__('Send Now'), function() {
return cur_frm.call('send', null, (r) => {
frappe.show_alert(__('Message Sent'));
});
});
frm.add_custom_button(__('Send Now'), function() {
return frm.call('send', null, () => {
frappe.show_alert({ message: __("Message Sent"), indicator: 'green'});
});
});
}
}
};
cur_frm.cscript.addremove_recipients = function(doc, dt, dn) {
// Get user list
return cur_frm.call('get_users', null, function(r) {
// Open a dialog and display checkboxes against email addresses
doc = locals[dt][dn];
var d = new frappe.ui.Dialog({
title: __('Add/Remove Recipients'),
width: 400
});
$.each(r.user_list, function(i, v) {
var fullname = frappe.user.full_name(v.name);
if(fullname !== v.name) fullname = fullname + " <" + v.name + ">";
if(v.enabled==0) {
fullname = repl("<span style='color: red'> %(name)s (" + __("disabled user") + ")</span>", {name: v.name});
}
$('<div class="checkbox"><label>\
<input type="checkbox" data-id="' + v.name + '"'+
(v.checked ? 'checked' : '') +
'> '+ fullname +'</label></div>').appendTo(d.body);
});
// Display add recipients button
d.set_primary_action("Update", function() {
cur_frm.cscript.add_to_rec_list(doc, d.body, r.user_list.length);
});
cur_frm.rec_dialog = d;
d.show();
});
}
cur_frm.cscript.add_to_rec_list = function(doc, dialog, length) {
// add checked users to list of recipients
var rec_list = [];
$(dialog).find('input:checked').each(function(i, input) {
rec_list.push($(input).attr('data-id'));
});
doc.recipient_list = rec_list.join('\n');
cur_frm.rec_dialog.hide();
cur_frm.save();
cur_frm.refresh_fields();
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -47,19 +47,13 @@ class EmailDigest(Document):
# send email only to enabled users
valid_users = [p[0] for p in frappe.db.sql("""select name from `tabUser`
where enabled=1""")]
recipients = list(filter(lambda r: r in valid_users,
self.recipient_list.split("\n")))
original_user = frappe.session.user
if recipients:
for user_id in recipients:
frappe.set_user(user_id)
frappe.set_user_lang(user_id)
if self.recipients:
for row in self.recipients:
msg_for_this_recipient = self.get_msg_html()
if msg_for_this_recipient:
if msg_for_this_recipient and row.recipient in valid_users:
frappe.sendmail(
recipients=user_id,
recipients=row.recipient,
subject=_("{0} Digest").format(self.frequency),
message=msg_for_this_recipient,
reference_doctype = self.doctype,

View File

@@ -0,0 +1,33 @@
{
"actions": [],
"creation": "2020-06-08 12:19:40.428949",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"recipient"
],
"fields": [
{
"fieldname": "recipient",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Recipient",
"options": "User",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-08-24 23:10:23.217572",
"modified_by": "Administrator",
"module": "Setup",
"name": "Email Digest Recipient",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@@ -87,8 +87,8 @@ 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, item_group=self.name)

View File

@@ -54,7 +54,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-05-08 23:13:48.049879",
"modified": "2021-08-04 20:15:59.071493",
"modified_by": "Administrator",
"module": "Setup",
"name": "Transaction Deletion Record",
@@ -70,6 +70,7 @@
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
}
],

View File

@@ -12,10 +12,14 @@ 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"))
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:
@@ -23,54 +27,9 @@ class TransactionDeletionRecord(Document):
self.delete_bins()
self.delete_lead_addresses()
company_obj = frappe.get_doc('Company', self.company)
# reset company values
company_obj.total_monthly_sales = 0
company_obj.sales_monthly_history = None
company_obj.save()
# Clear notification counts
self.reset_company_values()
clear_notifications()
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
tables = frappe.get_all('DocType', filters = {'istable': 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)
docfields = frappe.get_all('DocField',
filters = {
'fieldtype': 'Link',
'options': 'Company',
'parent': ['not in', doctypes_to_be_ignored_list]},
fields=['parent', 'fieldname'])
for docfield in docfields:
if docfield['parent'] != self.doctype:
no_of_docs = frappe.db.count(docfield['parent'], {
docfield['fieldname'] : self.company
})
if no_of_docs > 0:
self.delete_version_log(docfield['parent'], docfield['fieldname'])
self.delete_communications(docfield['parent'], docfield['fieldname'])
# populate DocTypes table
if docfield['parent'] not in tables:
self.append('doctypes', {
'doctype_name' : docfield['parent'],
'no_of_docs' : no_of_docs
})
# delete the docs linked with the specified company
frappe.db.delete(docfield['parent'], {
docfield['fieldname'] : self.company
})
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'])
self.delete_company_transactions()
def populate_doctypes_to_be_ignored_table(self):
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
@@ -79,6 +38,111 @@ class TransactionDeletionRecord(Document):
'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)
@@ -107,32 +171,6 @@ class TransactionDeletionRecord(Document):
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
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)))
@frappe.whitelist()
def get_doctypes_to_be_ignored():
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',

View File

@@ -45,9 +45,16 @@ def enable_shopping_cart(args):
def create_email_digest():
from frappe.utils.user import get_system_managers
system_managers = get_system_managers(only_name=True)
if not system_managers:
return
recipients = []
for d in system_managers:
recipients.append({
'recipient': d
})
companies = frappe.db.sql_list("select name FROM `tabCompany`")
for company in companies:
if not frappe.db.exists("Email Digest", "Default Weekly Digest - " + company):
@@ -56,7 +63,7 @@ def create_email_digest():
"name": "Default Weekly Digest - " + company,
"company": company,
"frequency": "Weekly",
"recipient_list": "\n".join(system_managers)
"recipients": recipients
})
for df in edigest.meta.get("fields", {"fieldtype": "Check"}):
@@ -72,7 +79,7 @@ def create_email_digest():
"name": "Scheduler Errors",
"company": companies[0],
"frequency": "Daily",
"recipient_list": "\n".join(system_managers),
"recipients": recipients,
"scheduler_errors": 1,
"enabled": 1
})