fix: remove energy points / social module

This commit is contained in:
sokumon 2025-04-08 13:51:07 +05:30
parent 3c8a879f37
commit a1fca6ab63
29 changed files with 1 additions and 1748 deletions

View file

@ -21,10 +21,6 @@ from frappe.permissions import has_permission
from frappe.query_builder import DocType
from frappe.query_builder.functions import Count
from frappe.query_builder.terms import ParameterizedValueWrapper, SubQuery
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
from frappe.social.doctype.energy_point_settings.energy_point_settings import (
is_energy_point_enabled,
)
from frappe.utils import add_user_info, cstr, get_system_timezone
from frappe.utils.change_log import get_versions
from frappe.utils.frappecloud import on_frappecloud
@ -99,10 +95,7 @@ def get_bootinfo():
bootinfo.lang_dict = get_lang_dict()
bootinfo.success_action = get_success_action()
bootinfo.update(get_email_accounts(user=frappe.session.user))
bootinfo.energy_points_enabled = is_energy_point_enabled()
bootinfo.website_tracking_enabled = is_tracking_enabled()
bootinfo.sms_gateway_enabled = bool(frappe.db.get_single_value("SMS Settings", "sms_gateway_url"))
bootinfo.points = get_energy_points(frappe.session.user)
bootinfo.frequently_visited_links = frequently_visited_links()
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
bootinfo.additional_filters_config = get_additional_filters_from_hooks()

View file

@ -6,7 +6,6 @@ import frappe
common_default_keys = ["__default", "__global"]
doctypes_for_mapping = {
"Energy Point Rule",
"Assignment Rule",
"Milestone Tracker",
"Document Naming Rule",

View file

@ -123,7 +123,7 @@ def get_docinfo(doc=None, doctype=None, name=None):
"permissions": get_doc_permissions(doc),
"shared": get_docshares(doc),
"views": get_view_logs(doc),
"energy_point_logs": get_point_logs(doc.doctype, doc.name),
# "energy_point_logs": get_point_logs(doc.doctype, doc.name),
"additional_timeline_content": get_additional_timeline_content(doc.doctype, doc.name),
"milestones": get_milestones(doc.doctype, doc.name),
"is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user),

View file

@ -177,7 +177,6 @@ doc_events = {
"frappe.core.doctype.file.utils.attach_files_to_document",
],
"on_change": [
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points",
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone",
],
"after_delete": ["frappe.core.doctype.permission_log.permission_log.make_perm_log"],
@ -248,7 +247,6 @@ scheduler_events = {
"frappe.email.doctype.notification.notification.trigger_daily_alerts",
"frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record",
"frappe.desk.form.document_follow.send_daily_updates",
"frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points",
"frappe.integrations.doctype.google_contacts.google_contacts.sync",
"frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry",
],
@ -263,13 +261,11 @@ scheduler_events = {
"frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_weekly",
"frappe.desk.form.document_follow.send_weekly_updates",
"frappe.utils.change_log.check_for_update",
"frappe.social.doctype.energy_point_log.energy_point_log.send_weekly_summary",
"frappe.integrations.doctype.google_drive.google_drive.weekly_backup",
"frappe.desk.doctype.changelog_feed.changelog_feed.fetch_changelog_feed",
],
"monthly": [
"frappe.email.doctype.auto_email_report.auto_email_report.send_monthly",
"frappe.social.doctype.energy_point_log.energy_point_log.send_monthly_summary",
],
"monthly_long": [
"frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_monthly"

View file

@ -8,5 +8,4 @@ Desk
Integrations
Printing
Contacts
Social
Automation

View file

@ -1,7 +1,6 @@
import "./assign_to";
import "./attachments";
import "./share";
import "./review";
import "./document_follow";
import "./user_image";
import "./form_sidebar_users";
@ -28,7 +27,6 @@ frappe.ui.form.Sidebar = class {
this.image_wrapper = this.image_section.find(".sidebar-image-wrapper");
this.make_assignments();
this.make_attachments();
this.make_review();
this.make_shared();
this.make_tags();
@ -214,18 +212,6 @@ frappe.ui.form.Sidebar = class {
refresh_image() {}
make_review() {
const review_wrapper = this.sidebar.find(".form-reviews");
if (frappe.boot.energy_points_enabled && !this.frm.is_new()) {
this.frm.reviews = new frappe.ui.form.Review({
parent: review_wrapper,
frm: this.frm,
});
} else {
review_wrapper.remove();
}
}
reload_docinfo(callback) {
frappe.call({
method: "frappe.desk.form.load.get_docinfo",

View file

@ -74,18 +74,6 @@
</span>
</a>
</div>
<div class="sidebar-section form-reviews">
<div class="reviews">
<span class="form-sidebar-items">
<span>
<span class="ellipsis">{%= __("Reviews") %}</span>
</span>
<button class="add-review-btn btn btn-link icon-btn">
<svg class="es-icon icon-sm"><use href="#es-line-add"></use></svg>
</button>
</span>
</div>
</div>
<div class="sidebar-section form-tags">
<div>
<span class="form-sidebar-items">

View file

@ -871,60 +871,6 @@ body {
}
}
.form-reviews {
.reviews {
display: flex;
flex-wrap: wrap;
}
.review {
display: flex;
font-weight: 500;
height: 28px;
border-radius: 14px;
font-size: var(--text-xs);
margin-bottom: var(--margin-sm);
margin-right: var(--margin-xs);
border: 1px solid var(--dark-border-color);
padding: 2px 3px;
align-items: center;
min-width: 60px;
background: var(--fg-color);
.avatar {
width: 20px;
height: 20px;
}
.review-points {
margin-left: 3px;
flex: 1;
text-align: center;
}
}
}
.review-popover {
padding: 0px;
min-width: 200px;
max-width: 250px;
.popover-body,
.popover-content {
padding: 0;
}
.body {
border-bottom: 1px solid $border-color;
}
.subject,
.body {
padding: var(--padding-sm);
overflow-wrap: break-word;
p {
margin-top: var(--margin-xs);
margin-bottom: 0px;
}
}
}
.liked-by-popover {
.popover-body {
min-height: 30px;

View file

@ -1,50 +0,0 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Energy Point Log", {
refresh: function (frm) {
frm.events.make_reference_name_link(frm);
if (frm.doc.reverted) {
frm.set_intro(__("This document has been reverted"));
} else if (frm.doc.type === "Auto" && frappe.user_roles.includes("System Manager")) {
frm.add_custom_button(__("Revert"), () => frm.events.show_revert_dialog(frm));
}
},
show_revert_dialog(frm) {
const revert_dialog = new frappe.ui.Dialog({
title: __("Revert"),
fields: [
{
fieldname: "reason",
fieldtype: "Small Text",
label: __("Reason"),
reqd: 1,
},
],
primary_action: (values) => {
return frm
.call("revert", {
reason: values.reason,
})
.then((res) => {
let revert_log = res.message;
revert_dialog.hide();
revert_dialog.clear();
frappe.model.docinfo[frm.doc.reference_doctype][
frm.doc.reference_name
].energy_point_logs.unshift(revert_log);
frm.refresh();
});
},
primary_action_label: __("Submit"),
});
revert_dialog.show();
},
make_reference_name_link(frm) {
let dt = frm.doc.reference_doctype;
let dn = frm.doc.reference_name;
frm.fields_dict.reference_name.$input_wrapper
.find(".control-value")
.wrapInner(`<a href='/app/${frappe.router.slug(dt)}/${dn}'></a>`);
},
});

View file

@ -1,134 +0,0 @@
{
"actions": [],
"creation": "2018-06-21 14:58:55.913619",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"user",
"type",
"points",
"rule",
"column_break_5",
"reference_doctype",
"reference_name",
"reverted",
"revert_of",
"section_break_10",
"reason",
"seen"
],
"fields": [
{
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "User",
"options": "User",
"read_only": 1,
"reqd": 1,
"search_index": 1
},
{
"fieldname": "type",
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Type",
"options": "Auto\nAppreciation\nCriticism\nReview\nRevert",
"read_only": 1
},
{
"fieldname": "rule",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Rule",
"options": "Energy Point Rule",
"read_only": 1
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Reference Document Type",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "reference_name",
"fieldtype": "Data",
"label": "Reference Name",
"read_only": 1,
"search_index": 1
},
{
"fieldname": "points",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Points",
"read_only": 1
},
{
"fieldname": "reason",
"fieldtype": "Text",
"in_list_view": 1,
"label": "Reason",
"read_only": 1
},
{
"default": "0",
"fieldname": "reverted",
"fieldtype": "Check",
"hidden": 1,
"label": "Reverted"
},
{
"depends_on": "eval:doc.type === 'Revert'",
"fieldname": "revert_of",
"fieldtype": "Link",
"label": "Revert Of",
"options": "Energy Point Log",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break"
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Seen"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-03-23 16:03:25.006938",
"modified_by": "Administrator",
"module": "Social",
"name": "Energy Point Log",
"owner": "Administrator",
"permissions": [
{
"read": 1,
"report": 1,
"role": "System Manager"
},
{
"read": 1,
"report": 1,
"role": "Desk User"
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "user"
}

View file

@ -1,391 +0,0 @@
# Copyright (c) 2018, Frappe Technologies and contributors
# License: MIT. See LICENSE
import json
import frappe
from frappe import _
from frappe.desk.doctype.notification_log.notification_log import (
enqueue_create_notification,
get_title,
get_title_html,
)
from frappe.desk.doctype.notification_settings.notification_settings import (
is_email_notifications_enabled,
is_email_notifications_enabled_for_type,
)
from frappe.model.document import Document
from frappe.utils import cint, get_fullname, get_link_to_form, getdate
class EnergyPointLog(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
points: DF.Int
reason: DF.Text | None
reference_doctype: DF.Link | None
reference_name: DF.Data | None
revert_of: DF.Link | None
reverted: DF.Check
rule: DF.Link | None
seen: DF.Check
type: DF.Literal["Auto", "Appreciation", "Criticism", "Review", "Revert"]
user: DF.Link
# end: auto-generated types
def validate(self):
self.map_milestone_reference()
if self.type in ["Appreciation", "Criticism"] and self.user == self.owner:
frappe.throw(_("You cannot give review points to yourself"))
def map_milestone_reference(self):
# link energy point to the original reference, if set by milestone
if self.reference_doctype == "Milestone":
self.reference_doctype, self.reference_name = frappe.db.get_value(
"Milestone", self.reference_name, ["reference_type", "reference_name"]
)
def after_insert(self):
alert_dict = get_alert_dict(self)
if alert_dict:
frappe.publish_realtime(
"energy_point_alert", message=alert_dict, user=self.user, after_commit=True
)
frappe.cache.hdel("energy_points", self.user)
if self.type != "Review" and frappe.get_cached_value(
"Notification Settings", self.user, "energy_points_system_notifications"
):
reference_user = self.user if self.type == "Auto" else self.owner
notification_doc = {
"type": "Energy Point",
"document_type": self.reference_doctype,
"document_name": self.reference_name,
"subject": get_notification_message(self),
"from_user": reference_user,
"email_content": f"<div>{self.reason}</div>" if self.reason else None,
}
enqueue_create_notification(self.user, notification_doc)
def on_trash(self):
if self.type == "Revert":
reference_log = frappe.get_doc("Energy Point Log", self.revert_of)
reference_log.reverted = 0
reference_log.save()
@frappe.whitelist()
def revert(self, reason, ignore_permissions=False):
if not ignore_permissions:
frappe.only_for("System Manager")
if self.type != "Auto":
frappe.throw(_("This document cannot be reverted"))
if self.get("reverted"):
return
self.reverted = 1
self.save(ignore_permissions=True)
return frappe.get_doc(
{
"doctype": "Energy Point Log",
"points": -(self.points),
"type": "Revert",
"user": self.user,
"reason": reason,
"reference_doctype": self.reference_doctype,
"reference_name": self.reference_name,
"revert_of": self.name,
}
).insert(ignore_permissions=True)
def get_notification_message(doc):
owner_name = get_fullname(doc.owner)
points = doc.points
title = get_title(doc.reference_doctype, doc.reference_name)
if doc.type == "Auto":
owner_name = frappe.bold("You")
if points == 1:
message = _("{0} gained {1} point for {2} {3}")
else:
message = _("{0} gained {1} points for {2} {3}")
message = message.format(owner_name, frappe.bold(points), doc.rule, get_title_html(title))
elif doc.type == "Appreciation":
if points == 1:
message = _("{0} appreciated your work on {1} with {2} point")
else:
message = _("{0} appreciated your work on {1} with {2} points")
message = message.format(frappe.bold(owner_name), get_title_html(title), frappe.bold(points))
elif doc.type == "Criticism":
if points == 1:
message = _("{0} criticized your work on {1} with {2} point")
else:
message = _("{0} criticized your work on {1} with {2} points")
message = message.format(frappe.bold(owner_name), get_title_html(title), frappe.bold(points))
elif doc.type == "Revert":
if points == 1:
message = _("{0} reverted your point on {1}")
else:
message = _("{0} reverted your points on {1}")
message = message.format(frappe.bold(owner_name), get_title_html(title))
return message
def get_alert_dict(doc):
alert_dict = frappe._dict()
owner_name = get_fullname(doc.owner)
if doc.reference_doctype:
doc_link = get_link_to_form(doc.reference_doctype, doc.reference_name)
points = doc.points
bold_points = frappe.bold(doc.points)
if doc.type == "Auto":
if points == 1:
message = _("You gained {0} point")
else:
message = _("You gained {0} points")
alert_dict.message = message.format(bold_points)
alert_dict.indicator = "green"
elif doc.type == "Appreciation":
if points == 1:
message = _("{0} appreciated your work on {1} with {2} point")
else:
message = _("{0} appreciated your work on {1} with {2} points")
alert_dict.message = message.format(owner_name, doc_link, bold_points)
alert_dict.indicator = "green"
elif doc.type == "Criticism":
if points == 1:
message = _("{0} criticized your work on {1} with {2} point")
else:
message = _("{0} criticized your work on {1} with {2} points")
alert_dict.message = message.format(owner_name, doc_link, bold_points)
alert_dict.indicator = "red"
elif doc.type == "Revert":
if points == 1:
message = _("{0} reverted your point on {1}")
else:
message = _("{0} reverted your points on {1}")
alert_dict.message = message.format(
owner_name,
doc_link,
)
alert_dict.indicator = "red"
return alert_dict
def create_energy_points_log(ref_doctype, ref_name, doc, apply_only_once=False):
doc = frappe._dict(doc)
if doc.rule:
log_exists = check_if_log_exists(
ref_doctype, ref_name, doc.rule, None if apply_only_once else doc.user
)
if log_exists:
return frappe.get_doc("Energy Point Log", log_exists)
new_log = frappe.new_doc("Energy Point Log")
new_log.reference_doctype = ref_doctype
new_log.reference_name = ref_name
new_log.update(doc)
new_log.insert(ignore_permissions=True)
return new_log
def check_if_log_exists(ref_doctype, ref_name, rule, user=None):
"""'Checks if Energy Point Log already exists"""
filters = frappe._dict(
{"rule": rule, "reference_doctype": ref_doctype, "reference_name": ref_name, "reverted": 0}
)
if user:
filters.user = user
return frappe.db.exists("Energy Point Log", filters)
def create_review_points_log(user, points, reason=None, doctype=None, docname=None):
return frappe.get_doc(
{
"doctype": "Energy Point Log",
"points": points,
"type": "Review",
"user": user,
"reason": reason,
"reference_doctype": doctype,
"reference_name": docname,
}
).insert(ignore_permissions=True)
@frappe.whitelist()
def add_review_points(user, points):
frappe.only_for("System Manager")
create_review_points_log(user, points)
@frappe.whitelist()
def get_energy_points(user):
points = get_user_energy_and_review_points(user)
return frappe._dict(points.get(user, {}))
@frappe.whitelist()
def get_user_energy_and_review_points(user=None, from_date=None, as_dict=True):
conditions = ""
given_points_condition = ""
values = frappe._dict()
if user:
conditions = "WHERE `user` = %(user)s"
values.user = user
if from_date:
conditions += "WHERE" if not conditions else "AND"
given_points_condition += "AND `creation` >= %(from_date)s"
conditions += " `creation` >= %(from_date)s OR `type`='Review'"
values.from_date = from_date
points_list = frappe.db.sql(
f"""
SELECT
SUM(CASE WHEN `type` != 'Review' THEN `points` ELSE 0 END) AS energy_points,
SUM(CASE WHEN `type` = 'Review' THEN `points` ELSE 0 END) AS review_points,
SUM(CASE
WHEN `type`='Review' AND `points` < 0 {given_points_condition}
THEN ABS(`points`)
ELSE 0
END) as given_points,
`user`
FROM `tabEnergy Point Log`
{conditions}
GROUP BY `user`
ORDER BY `energy_points` DESC
""",
values=values,
as_dict=1,
)
if not as_dict:
return points_list
dict_to_return = frappe._dict()
for d in points_list:
dict_to_return[d.pop("user")] = d
return dict_to_return
@frappe.whitelist()
def review(doc, points, to_user, reason, review_type="Appreciation"):
current_review_points = get_energy_points(frappe.session.user).review_points
doc = doc.as_dict() if hasattr(doc, "as_dict") else frappe._dict(json.loads(doc))
points = abs(cint(points))
if current_review_points < points:
frappe.msgprint(_("You do not have enough review points"))
return
review_doc = create_energy_points_log(
doc.doctype,
doc.name,
{
"type": review_type,
"reason": reason,
"points": points if review_type == "Appreciation" else -points,
"user": to_user,
},
)
# deduct review points from reviewer
create_review_points_log(
user=frappe.session.user,
points=-points,
reason=reason,
doctype=review_doc.doctype,
docname=review_doc.name,
)
return review_doc
@frappe.whitelist()
def get_reviews(doctype, docname):
return frappe.get_all(
"Energy Point Log",
filters={
"reference_doctype": doctype,
"reference_name": docname,
"type": ["in", ("Appreciation", "Criticism")],
},
fields=["points", "owner", "type", "user", "reason", "creation"],
)
def send_weekly_summary():
send_summary("Weekly")
def send_monthly_summary():
send_summary("Monthly")
def send_summary(timespan):
from frappe.social.doctype.energy_point_settings.energy_point_settings import (
is_energy_point_enabled,
)
from frappe.utils.user import get_enabled_system_users
if not is_energy_point_enabled():
return
if not is_email_notifications_enabled_for_type(frappe.session.user, "Energy Point"):
return
from_date = frappe.utils.add_to_date(None, weeks=-1)
if timespan == "Monthly":
from_date = frappe.utils.add_to_date(None, months=-1)
user_points = get_user_energy_and_review_points(from_date=from_date, as_dict=False)
# do not send report if no activity found
if not user_points or not user_points[0].energy_points:
return
from_date = getdate(from_date)
to_date = getdate()
# select only those users that have energy point email notifications enabled
all_users = [
user.email
for user in get_enabled_system_users()
if is_email_notifications_enabled_for_type(user.name, "Energy Point")
]
frappe.sendmail(
subject=f"{timespan} energy points summary",
recipients=all_users,
template="energy_points_summary",
args={
"top_performer": user_points[0],
"top_reviewer": max(user_points, key=lambda x: x["given_points"]),
"standings": user_points[:10], # top 10
"footer_message": get_footer_message(timespan).format(from_date, to_date),
},
with_container=1,
)
def get_footer_message(timespan):
if timespan == "Monthly":
return _("Stats based on last month's performance (from {0} to {1})")
else:
return _("Stats based on last week's performance (from {0} to {1})")

View file

@ -1,29 +0,0 @@
frappe.listview_settings["Energy Point Log"] = {
hide_name_column: true,
add_fields: ["type", "reference_doctype", "reference_name"],
get_indicator: function (doc) {
let colors = {
Appreciation: "green",
Criticism: "red",
Auto: "blue",
Revert: "orange",
Review: "grey",
};
return [__(doc.type), colors[doc.type], "type,=," + doc.type];
},
button: {
show: function (doc) {
return doc.reference_name;
},
get_label: function () {
return __("View Ref");
},
get_description: function (doc) {
return __("Open {0}", [`${doc.reference_doctype} ${doc.reference_name}`]);
},
action: function (doc) {
frappe.set_route("Form", doc.reference_doctype, doc.reference_name);
},
},
};

View file

@ -1,389 +0,0 @@
# Copyright (c) 2019, Frappe Technologies and Contributors
# License: MIT. See LICENSE
from unittest.case import skipIf
import frappe
from frappe.cache_manager import clear_doctype_map
from frappe.desk.form.assign_to import add as assign_to
from frappe.desk.page.user_profile.user_profile import get_energy_points_heatmap_data
from frappe.tests import IntegrationTestCase, UnitTestCase
from frappe.utils.testutils import add_custom_field, clear_custom_fields
from .energy_point_log import create_review_points_log, review
from .energy_point_log import get_energy_points as _get_energy_points
class UnitTestEnergyPointLog(UnitTestCase):
"""
Unit tests for EnergyPointLog.
Use this class for testing individual functions and methods.
"""
pass
class TestEnergyPointLog(IntegrationTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
settings = frappe.get_single("Energy Point Settings")
settings.enabled = 1
settings.save()
@classmethod
def tearDownClass(cls):
settings = frappe.get_single("Energy Point Settings")
settings.enabled = 0
settings.save()
def setUp(self):
clear_doctype_map("Energy Point Rule")
def tearDown(self):
frappe.set_user("Administrator")
frappe.db.delete("Energy Point Log")
frappe.db.delete("Energy Point Rule")
clear_doctype_map("Energy Point Rule")
def test_user_energy_point(self):
frappe.set_user("test@example.com")
todo_point_rule = create_energy_point_rule_for_todo()
energy_point_of_user = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
points_after_closing_todo = get_points("test@example.com")
self.assertEqual(points_after_closing_todo, energy_point_of_user + todo_point_rule.points)
created_todo.save()
points_after_double_save = get_points("test@example.com")
# point should not be awarded more than once for same doc
self.assertEqual(points_after_double_save, energy_point_of_user + todo_point_rule.points)
def test_points_based_on_multiplier_field(self):
frappe.set_user("test@example.com")
add_custom_field("ToDo", "multiplier", "Float")
multiplier_value = 0.51
todo_point_rule = create_energy_point_rule_for_todo("multiplier")
energy_point_of_user = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.multiplier = multiplier_value
created_todo.save()
points_after_closing_todo = get_points("test@example.com")
self.assertEqual(
points_after_closing_todo,
energy_point_of_user + round(todo_point_rule.points * multiplier_value),
)
clear_custom_fields("ToDo")
def test_points_based_on_max_points(self):
frappe.set_user("test@example.com")
# here multiplier is high
# let see if points get capped to max_point limit
multiplier_value = 15
max_points = 50
add_custom_field("ToDo", "multiplier", "Float")
todo_point_rule = create_energy_point_rule_for_todo("multiplier", max_points=max_points)
energy_point_of_user = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.multiplier = multiplier_value
created_todo.save()
points_after_closing_todo = get_points("test@example.com")
# test max_points cap
self.assertNotEqual(
points_after_closing_todo,
energy_point_of_user + round(todo_point_rule.points * multiplier_value),
)
self.assertEqual(points_after_closing_todo, energy_point_of_user + max_points)
clear_custom_fields("ToDo")
def test_disabled_energy_points(self):
settings = frappe.get_single("Energy Point Settings")
settings.enabled = 0
settings.save()
frappe.set_user("test@example.com")
create_energy_point_rule_for_todo()
energy_point_of_user = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
points_after_closing_todo = get_points("test@example.com")
# no change in points
self.assertEqual(points_after_closing_todo, energy_point_of_user)
settings.enabled = 1
settings.save()
def test_review(self):
created_todo = create_a_todo()
review_points = 20
create_review_points_log("test2@example.com", review_points)
# reviewer
frappe.set_user("test2@example.com")
review_points_before_review = get_points("test2@example.com", "review_points")
self.assertEqual(review_points_before_review, review_points)
# for appreciation
appreciation_points = 5
energy_points_before_review = get_points("test@example.com")
review(created_todo, appreciation_points, "test@example.com", "good job")
energy_points_after_review = get_points("test@example.com")
review_points_after_review = get_points("test2@example.com", "review_points")
self.assertEqual(energy_points_after_review, energy_points_before_review + appreciation_points)
self.assertEqual(review_points_after_review, review_points_before_review - appreciation_points)
# for criticism
criticism_points = 2
todo = create_a_todo(description="Bad patch")
energy_points_before_review = energy_points_after_review
review_points_before_review = review_points_after_review
review(todo, criticism_points, "test@example.com", "You could have done better.", "Criticism")
energy_points_after_review = get_points("test@example.com")
review_points_after_review = get_points("test2@example.com", "review_points")
self.assertEqual(energy_points_after_review, energy_points_before_review - criticism_points)
self.assertEqual(review_points_after_review, review_points_before_review - criticism_points)
def test_user_energy_point_as_admin(self):
frappe.set_user("Administrator")
create_energy_point_rule_for_todo()
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
points_after_closing_todo = get_points("Administrator")
# no points for admin
self.assertEqual(points_after_closing_todo, 0)
def test_revert_points_on_cancelled_doc(self):
frappe.set_user("test@example.com")
create_energy_point_rule_for_todo()
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
energy_point_logs = frappe.get_all("Energy Point Log")
self.assertEqual(len(energy_point_logs), 1)
# for submit and cancel permission
frappe.set_user("Administrator")
# submit
created_todo.docstatus = 1
created_todo.save()
# cancel
created_todo.docstatus = 2
created_todo.save()
energy_point_logs = frappe.get_all("Energy Point Log", fields=["reference_name", "type", "reverted"])
self.assertListEqual(
energy_point_logs,
[
{"reference_name": created_todo.name, "type": "Revert", "reverted": 0},
{"reference_name": created_todo.name, "type": "Auto", "reverted": 1},
],
)
def test_energy_point_for_new_document_creation(self):
frappe.set_user("test@example.com")
todo_point_rule = create_energy_point_rule_for_todo(for_doc_event="New")
points_before_todo_creation = get_points("test@example.com")
create_a_todo()
points_after_todo_creation = get_points("test@example.com")
self.assertEqual(points_after_todo_creation, points_before_todo_creation + todo_point_rule.points)
def test_point_allocation_for_assigned_users(self):
todo = create_a_todo()
assign_users_to_todo(todo.name, ["test@example.com", "test2@example.com"])
test_user_before_points = get_points("test@example.com")
test2_user_before_points = get_points("test2@example.com")
rule = create_energy_point_rule_for_todo(for_assigned_users=1)
todo.status = "Closed"
todo.save()
test_user_after_points = get_points("test@example.com")
test2_user_after_points = get_points("test2@example.com")
self.assertEqual(test_user_after_points, test_user_before_points + rule.points)
self.assertEqual(test2_user_after_points, test2_user_before_points + rule.points)
@skipIf(
frappe.conf.db_type == "sqlite",
"Not for SQLite for now",
)
def test_eps_heatmap_query(self):
# Just asserts that query works, not correctness.
self.assertIsInstance(get_energy_points_heatmap_data(user="test@example.com", date=None), dict)
def test_points_on_field_value_change(self):
rule = create_energy_point_rule_for_todo(for_doc_event="Value Change", field_to_check="description")
frappe.set_user("test@example.com")
points_before_todo_creation = get_points("test@example.com")
todo = create_a_todo()
todo.status = "Closed"
todo.save()
points_after_closing_todo = get_points("test@example.com")
self.assertEqual(points_after_closing_todo, points_before_todo_creation)
todo.description = "This is new todo"
todo.save()
points_after_changing_todo_description = get_points("test@example.com")
self.assertEqual(points_after_changing_todo_description, points_before_todo_creation + rule.points)
def test_apply_only_once(self):
frappe.set_user("test@example.com")
todo_point_rule = create_energy_point_rule_for_todo(apply_once=True, user_field="modified_by")
first_user_points = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
first_user_points_after_closing_todo = get_points("test@example.com")
self.assertEqual(first_user_points_after_closing_todo, first_user_points + todo_point_rule.points)
frappe.set_user("test2@example.com")
second_user_points = get_points("test2@example.com")
created_todo.save(ignore_permissions=True)
second_user_points_after_closing_todo = get_points("test2@example.com")
# point should not be awarded more than once for same doc (irrespective of user)
self.assertEqual(second_user_points_after_closing_todo, second_user_points)
def test_allow_creation_of_new_log_if_the_previous_log_was_reverted(self):
frappe.set_user("test@example.com")
todo_point_rule = create_energy_point_rule_for_todo()
energy_point_of_user = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
points_after_closing_todo = get_points("test@example.com")
log_name = frappe.db.exists("Energy Point Log", {"reference_name": created_todo.name})
frappe.get_doc("Energy Point Log", log_name).revert("Just for test")
points_after_reverting_todo = get_points("test@example.com")
created_todo.save()
points_after_saving_todo_again = get_points("test@example.com")
rule_points = todo_point_rule.points
self.assertEqual(points_after_closing_todo, energy_point_of_user + rule_points)
self.assertEqual(points_after_reverting_todo, points_after_closing_todo - rule_points)
self.assertEqual(points_after_saving_todo_again, points_after_reverting_todo + rule_points)
def test_energy_points_disabled_user(self):
frappe.set_user("test@example.com")
user = frappe.get_doc("User", "test@example.com")
user.enabled = 0
user.save()
todo_point_rule = create_energy_point_rule_for_todo()
energy_point_of_user = get_points("test@example.com")
created_todo = create_a_todo()
created_todo.status = "Closed"
created_todo.save()
points_after_closing_todo = get_points("test@example.com")
# do not update energy points for disabled user
self.assertEqual(points_after_closing_todo, energy_point_of_user)
with self.set_user("Administrator"):
user.enabled = 1
user.save()
created_todo.save()
points_after_re_saving_todo = get_points("test@example.com")
self.assertEqual(points_after_re_saving_todo, energy_point_of_user + todo_point_rule.points)
def create_energy_point_rule_for_todo(
multiplier_field=None,
for_doc_event="Custom",
max_points=None,
for_assigned_users=0,
field_to_check=None,
apply_once=False,
user_field="owner",
):
name = "ToDo Closed"
point_rule_exists = frappe.db.exists("Energy Point Rule", name)
if point_rule_exists:
return frappe.get_doc("Energy Point Rule", name)
return frappe.get_doc(
{
"doctype": "Energy Point Rule",
"rule_name": name,
"points": 5,
"reference_doctype": "ToDo",
"condition": 'doc.status == "Closed"',
"for_doc_event": for_doc_event,
"user_field": user_field,
"for_assigned_users": for_assigned_users,
"multiplier_field": multiplier_field,
"max_points": max_points,
"field_to_check": field_to_check,
"apply_only_once": apply_once,
}
).insert(ignore_permissions=1)
def create_a_todo(description=None):
if not description:
description = "Fix a bug"
return frappe.get_doc(
{
"doctype": "ToDo",
"description": description,
}
).insert(ignore_permissions=True)
def get_points(user, point_type="energy_points"):
return _get_energy_points(user).get(point_type, 0)
def assign_users_to_todo(todo_name, users):
for user in users:
assign_to({"assign_to": [user], "doctype": "ToDo", "name": todo_name})

View file

@ -1,58 +0,0 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Energy Point Rule", {
validate(frm) {
frm.set_df_property("user_field", "reqd", !frm.doc.for_assigned_users);
frm.set_df_property("condition", "reqd", frm.doc.for_doc_event === "Custom");
},
refresh(frm) {
frm.events.set_field_options(frm);
},
for_doc_event(frm) {
if (frm.doc.for_assigned_users) {
frm.set_value("for_assigned_users", !frm.doc.for_doc_event === "New");
}
},
reference_doctype(frm) {
frm.events.set_field_options(frm);
},
set_field_options(frm) {
// sets options for field_to_check, user_field and multiplier fields
// based on reference doctype
const reference_doctype = frm.doc.reference_doctype;
if (!reference_doctype) return;
frappe.model.with_doctype(reference_doctype, () => {
const map_for_options = (df) => ({ label: df.label, value: df.fieldname });
const fields = frappe.meta
.get_docfields(frm.doc.reference_doctype)
.filter(frappe.model.is_value_type);
const fields_to_check = fields.map(map_for_options);
const user_fields = fields
.filter(
(df) =>
(df.fieldtype === "Link" && df.options === "User") ||
df.fieldtype === "Data"
)
.map(map_for_options)
.concat([
{ label: __("Owner"), value: "owner" },
{ label: __("Modified By"), value: "modified_by" },
]);
const multiplier_fields = fields
.filter((df) => ["Int", "Float"].includes(df.fieldtype))
.map(map_for_options);
// blank option for the ability to unset the multiplier field
multiplier_fields.unshift(null);
frm.set_df_property("field_to_check", "options", fields_to_check);
frm.set_df_property("user_field", "options", user_fields);
frm.set_df_property("multiplier_field", "options", multiplier_fields);
});
},
});

View file

@ -1,143 +0,0 @@
{
"actions": [],
"autoname": "field:rule_name",
"creation": "2018-06-20 10:08:36.275253",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enabled",
"section_break_2",
"rule_name",
"reference_doctype",
"for_doc_event",
"field_to_check",
"points",
"for_assigned_users",
"user_field",
"multiplier_field",
"max_points",
"column_break_12",
"apply_only_once",
"condition"
],
"fields": [
{
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Enabled",
"reqd": 1
},
{
"fieldname": "rule_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Rule Name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Reference Document Type",
"options": "DocType",
"reqd": 1
},
{
"depends_on": "eval:['Custom', 'Value Change'].includes(doc.for_doc_event)",
"description": "If the condition is satisfied user will be rewarded with the points. eg. doc.status == 'Closed'\n",
"fieldname": "condition",
"fieldtype": "Code",
"in_list_view": 1,
"label": "Condition"
},
{
"fieldname": "points",
"fieldtype": "Int",
"label": "Points",
"reqd": 1
},
{
"depends_on": "eval:!doc.for_assigned_users || doc.for_doc_event==='New'",
"description": "The user from this field will be rewarded points",
"fieldname": "user_field",
"fieldtype": "Select",
"label": "User Field"
},
{
"fieldname": "multiplier_field",
"fieldtype": "Select",
"label": "Multiplier Field"
},
{
"depends_on": "eval:doc.multiplier_field",
"description": "Maximum points allowed after multiplying points with the multiplier value\n(Note: For no limit leave this field empty or set 0)",
"fieldname": "max_points",
"fieldtype": "Int",
"label": "Maximum Points"
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"default": "Custom",
"fieldname": "for_doc_event",
"fieldtype": "Select",
"label": "For Document Event",
"options": "New\nSubmit\nCancel\nValue Change\nCustom"
},
{
"default": "0",
"depends_on": "eval:doc.for_doc_event !=='New'",
"description": "Users assigned to the reference document will get points.",
"fieldname": "for_assigned_users",
"fieldtype": "Check",
"label": "Allot Points To Assigned Users"
},
{
"depends_on": "eval:doc.for_doc_event=='Value Change'",
"fieldname": "field_to_check",
"fieldtype": "Select",
"label": "Field To Check"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Apply this rule only once per document",
"fieldname": "apply_only_once",
"fieldtype": "Check",
"label": "Apply Only Once"
}
],
"links": [],
"modified": "2024-03-23 16:03:25.142267",
"modified_by": "Administrator",
"module": "Social",
"name": "Energy Point Rule",
"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": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View file

@ -1,158 +0,0 @@
# Copyright (c) 2018, Frappe Technologies and contributors
# License: MIT. See LICENSE
import frappe
import frappe.cache_manager
from frappe import _
from frappe.core.doctype.user.user import get_enabled_users
from frappe.model import log_types
from frappe.model.document import Document
from frappe.social.doctype.energy_point_log.energy_point_log import create_energy_points_log
from frappe.social.doctype.energy_point_settings.energy_point_settings import (
is_energy_point_enabled,
)
class EnergyPointRule(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
apply_only_once: DF.Check
condition: DF.Code | None
enabled: DF.Check
field_to_check: DF.Literal[None]
for_assigned_users: DF.Check
for_doc_event: DF.Literal["New", "Submit", "Cancel", "Value Change", "Custom"]
max_points: DF.Int
multiplier_field: DF.Literal[None]
points: DF.Int
reference_doctype: DF.Link
rule_name: DF.Data
user_field: DF.Literal[None]
# end: auto-generated types
def on_update(self):
frappe.cache_manager.clear_doctype_map("Energy Point Rule", self.reference_doctype)
def on_trash(self):
frappe.cache_manager.clear_doctype_map("Energy Point Rule", self.reference_doctype)
def apply(self, doc):
if self.rule_condition_satisfied(doc):
multiplier = 1
points = self.points
if self.multiplier_field:
multiplier = doc.get(self.multiplier_field) or 1
points = round(points * multiplier)
max_points = self.max_points
if max_points and points > max_points:
points = max_points
reference_doctype = doc.doctype
reference_name = doc.name
users = []
if self.for_assigned_users:
users = doc.get_assigned_users()
else:
users = [doc.get(self.user_field)]
rule = self.name
# incase of zero as result after roundoff
if not points:
return
try:
for user in users:
if not is_eligible_user(user):
continue
create_energy_points_log(
reference_doctype,
reference_name,
{"points": points, "user": user, "rule": rule},
self.apply_only_once,
)
except Exception:
self.log_error("Energy points failed")
def rule_condition_satisfied(self, doc):
if self.for_doc_event == "New":
# indicates that this was a new doc
return doc.get_doc_before_save() is None
if self.for_doc_event == "Submit":
return doc.docstatus.is_submitted()
if self.for_doc_event == "Cancel":
return doc.docstatus.is_cancelled()
if self.for_doc_event == "Value Change":
field_to_check = self.field_to_check
if not field_to_check:
return False
doc_before_save = doc.get_doc_before_save()
# check if the field has been changed
# if condition is set check if it is satisfied
return (
doc_before_save
and doc_before_save.get(field_to_check) != doc.get(field_to_check)
and (not self.condition or self.eval_condition(doc))
)
if self.for_doc_event == "Custom" and self.condition:
return self.eval_condition(doc)
return False
def eval_condition(self, doc):
return self.condition and frappe.safe_eval(self.condition, None, {"doc": doc.as_dict()})
def process_energy_points(doc, state):
if (
frappe.flags.in_patch
or frappe.flags.in_install
or frappe.flags.in_migrate
or frappe.flags.in_import
or frappe.flags.in_setup_wizard
or doc.doctype in log_types
):
return
if not is_energy_point_enabled():
return
old_doc = doc.get_doc_before_save()
# check if doc has been cancelled
if old_doc and old_doc.docstatus.is_submitted() and doc.docstatus.is_cancelled():
return revert_points_for_cancelled_doc(doc)
for d in frappe.cache_manager.get_doctype_map(
"Energy Point Rule", doc.doctype, dict(reference_doctype=doc.doctype, enabled=1)
):
frappe.get_doc("Energy Point Rule", d.get("name")).apply(doc)
def revert_points_for_cancelled_doc(doc):
energy_point_logs = frappe.get_all(
"Energy Point Log",
{"reference_doctype": doc.doctype, "reference_name": doc.name, "type": "Auto"},
)
for log in energy_point_logs:
reference_log = frappe.get_doc("Energy Point Log", log.name)
reference_log.revert(_("Reference document has been cancelled"), ignore_permissions=True)
def get_energy_point_doctypes():
return [
d.reference_doctype
for d in frappe.get_all("Energy Point Rule", ["reference_doctype"], {"enabled": 1})
]
def is_eligible_user(user):
"""Checks if user is eligible to get energy points"""
enabled_users = get_enabled_users()
return user and user in enabled_users and user != "Administrator"

View file

@ -1,50 +0,0 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Energy Point Settings", {
refresh: function (frm) {
if (frm.doc.enabled) {
frm.add_custom_button(__("Give Review Points"), show_review_points_dialog);
}
},
});
function show_review_points_dialog() {
const dialog = new frappe.ui.Dialog({
title: __("Give Review Points"),
fields: [
{
label: "User",
fieldname: "user",
fieldtype: "Link",
options: "User",
reqd: 1,
},
{
label: "Points",
fieldname: "points",
fieldtype: "Int",
reqd: 1,
},
],
primary_action: function (values) {
frappe
.xcall(
"frappe.social.doctype.energy_point_log.energy_point_log.add_review_points",
{
user: values.user,
points: values.points,
}
)
.then(() => {
frappe.show_alert({
message: __("Successfully Done"),
indicator: "green",
});
})
.finally(() => dialog.hide());
},
primary_action_label: __("Submit"),
});
dialog.show();
}

View file

@ -1,71 +0,0 @@
{
"actions": [],
"creation": "2019-03-19 13:17:51.710241",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enabled",
"section_break_2",
"review_levels",
"point_allocation_periodicity",
"last_point_allocation_date"
],
"fields": [
{
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"depends_on": "enabled",
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "review_levels",
"fieldtype": "Table",
"label": "Review Levels",
"options": "Review Level"
},
{
"default": "Weekly",
"fieldname": "point_allocation_periodicity",
"fieldtype": "Select",
"label": "Point Allocation Periodicity",
"options": "Daily\nWeekly\nMonthly"
},
{
"fieldname": "last_point_allocation_date",
"fieldtype": "Date",
"label": "Last Point Allocation Date",
"read_only": 1
}
],
"hide_toolbar": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-23 16:03:25.273422",
"modified_by": "Administrator",
"module": "Social",
"name": "Energy Point Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}

View file

@ -1,80 +0,0 @@
# Copyright (c) 2019, Frappe Technologies and contributors
# License: MIT. See LICENSE
import frappe
from frappe.model.document import Document
from frappe.social.doctype.energy_point_log.energy_point_log import create_review_points_log
from frappe.utils import add_to_date, getdate, today
class EnergyPointSettings(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.social.doctype.review_level.review_level import ReviewLevel
from frappe.types import DF
enabled: DF.Check
last_point_allocation_date: DF.Date | None
point_allocation_periodicity: DF.Literal["Daily", "Weekly", "Monthly"]
review_levels: DF.Table[ReviewLevel]
# end: auto-generated types
def on_update(self):
if self.has_value_changed("enabled"):
frappe.cache.delete_key("bootinfo")
def is_energy_point_enabled():
return frappe.client_cache.get_doc("Energy Point Settings").enabled
def allocate_review_points():
settings = frappe.get_single("Energy Point Settings")
if not can_allocate_today(settings.last_point_allocation_date, settings.point_allocation_periodicity):
return
user_point_map = {}
for level in settings.review_levels:
users = get_users_with_role(level.role)
for user in users:
user_point_map.setdefault(user, 0)
# to avoid duplicate point allocation
user_point_map[user] = max([user_point_map[user], level.review_points])
for user, points in user_point_map.items():
create_review_points_log(user, points)
settings.last_point_allocation_date = today()
settings.save(ignore_permissions=True)
def can_allocate_today(last_date, periodicity):
if not last_date:
return True
days_to_add = {"Daily": 1, "Weekly": 7, "Monthly": 30}.get(periodicity, 1)
next_allocation_date = add_to_date(last_date, days=days_to_add)
return getdate(next_allocation_date) <= getdate()
def get_users_with_role(role):
return [
p[0]
for p in frappe.db.sql(
"""SELECT DISTINCT `tabUser`.`name`
FROM `tabHas Role`, `tabUser`
WHERE `tabHas Role`.`role`=%s
AND `tabUser`.`name`!='Administrator'
AND `tabHas Role`.`parent`=`tabUser`.`name`
AND `tabUser`.`enabled`=1""",
role,
)
]

View file

@ -1,18 +0,0 @@
# Copyright (c) 2021, Frappe Technologies and Contributors
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
class UnitTestEnergyPointSettings(UnitTestCase):
"""
Unit tests for EnergyPointSettings.
Use this class for testing individual functions and methods.
"""
pass
class TestEnergyPointSettings(IntegrationTestCase):
pass

View file

@ -1,7 +0,0 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Review Level", {
// refresh: function(frm) {
// }
});

View file

@ -1,51 +0,0 @@
{
"actions": [],
"creation": "2019-03-19 13:16:12.762352",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"level_name",
"role",
"review_points"
],
"fields": [
{
"fieldname": "level_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Level Name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "role",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Role",
"options": "Role",
"reqd": 1,
"unique": 1
},
{
"fieldname": "review_points",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Review Points",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2024-03-23 16:03:36.743533",
"modified_by": "Administrator",
"module": "Social",
"name": "Review Level",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View file

@ -1,25 +0,0 @@
# Copyright (c) 2019, Frappe Technologies and contributors
# License: MIT. See LICENSE
# import frappe
from frappe.model.document import Document
class ReviewLevel(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
level_name: DF.Data
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
review_points: DF.Int
role: DF.Link
# end: auto-generated types
pass