Merge pull request #1144 from shariquerik/helpdesk-integration
This commit is contained in:
commit
d173d5584a
@ -1008,7 +1008,7 @@ def get_deals_by_territory(from_date="", to_date="", user=""):
|
||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||
{deal_conds}
|
||||
GROUP BY d.territory
|
||||
ORDER BY value DESC
|
||||
ORDER BY deals DESC, value DESC
|
||||
""",
|
||||
{"from": from_date, "to": to_date},
|
||||
as_dict=True,
|
||||
@ -1065,7 +1065,7 @@ def get_deals_by_salesperson(from_date="", to_date="", user=""):
|
||||
WHERE DATE(d.creation) BETWEEN %(from)s AND %(to)s
|
||||
{deal_conds}
|
||||
GROUP BY d.deal_owner
|
||||
ORDER BY value DESC
|
||||
ORDER BY deals DESC, value DESC
|
||||
""",
|
||||
{"from": from_date, "to": to_date},
|
||||
as_dict=True,
|
||||
|
||||
@ -135,7 +135,7 @@ def get_quotation_url(crm_deal, organization):
|
||||
"party_name": crm_deal,
|
||||
"company": erpnext_crm_settings.erpnext_company,
|
||||
"contact_person": contact,
|
||||
"customer_address": address
|
||||
"customer_address": address,
|
||||
}
|
||||
else:
|
||||
site_url = erpnext_crm_settings.get("erpnext_site_url")
|
||||
@ -147,14 +147,11 @@ def get_quotation_url(crm_deal, organization):
|
||||
"party_name": prospect,
|
||||
"company": erpnext_crm_settings.erpnext_company,
|
||||
"contact_person": contact,
|
||||
"customer_address": address
|
||||
"customer_address": address,
|
||||
}
|
||||
|
||||
|
||||
# Filter out None values and build query string
|
||||
query_string = "&".join(
|
||||
f"{key}={value}" for key, value in params.items()
|
||||
if value is not None
|
||||
)
|
||||
query_string = "&".join(f"{key}={value}" for key, value in params.items() if value is not None)
|
||||
|
||||
return f"{base_url}?{query_string}"
|
||||
|
||||
|
||||
0
crm/fcrm/doctype/helpdesk_crm_settings/__init__.py
Normal file
0
crm/fcrm/doctype/helpdesk_crm_settings/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Helpdesk CRM Settings", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@ -0,0 +1,102 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-08-18 17:25:49.638398",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"column_break_idaw",
|
||||
"is_helpdesk_in_different_site",
|
||||
"helpdesk_site_url",
|
||||
"helpdesk_site_apis_section",
|
||||
"api_key",
|
||||
"column_break_tqsm",
|
||||
"api_secret"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_idaw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "is_helpdesk_in_different_site",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Helpdesk installed on a different site?"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enabled && doc.is_helpdesk_in_different_site",
|
||||
"fieldname": "helpdesk_site_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Helpdesk Site URL",
|
||||
"mandatory_depends_on": "is_helpdesk_in_different_site"
|
||||
},
|
||||
{
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "helpdesk_site_apis_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Helpdesk Site API's"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enabled && doc.is_helpdesk_in_different_site",
|
||||
"fieldname": "api_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "API Key",
|
||||
"mandatory_depends_on": "is_helpdesk_in_different_site"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_tqsm",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enabled && doc.is_helpdesk_in_different_site",
|
||||
"fieldname": "api_secret",
|
||||
"fieldtype": "Password",
|
||||
"label": "API Secret",
|
||||
"mandatory_depends_on": "is_helpdesk_in_different_site"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-18 17:33:38.616328",
|
||||
"modified_by": "Administrator",
|
||||
"module": "FCRM",
|
||||
"name": "Helpdesk CRM Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Sales Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
178
crm/fcrm/doctype/helpdesk_crm_settings/helpdesk_crm_settings.py
Normal file
178
crm/fcrm/doctype/helpdesk_crm_settings/helpdesk_crm_settings.py
Normal file
@ -0,0 +1,178 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class HelpdeskCRMSettings(Document):
|
||||
def validate(self):
|
||||
if self.enabled:
|
||||
self.validate_if_helpdesk_installed()
|
||||
self.create_helpdesk_script()
|
||||
|
||||
def validate_if_helpdesk_installed(self):
|
||||
if not self.is_helpdesk_in_different_site:
|
||||
if "helpdesk" not in frappe.get_installed_apps():
|
||||
frappe.throw(_("Helpdesk is not installed in the current site"))
|
||||
|
||||
def create_helpdesk_script(self):
|
||||
if not frappe.db.exists("CRM Form Script", "Helpdesk Integration Script"):
|
||||
script = get_helpdesk_script()
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "CRM Form Script",
|
||||
"name": "Helpdesk Integration Script",
|
||||
"dt": "CRM Deal",
|
||||
"view": "Form",
|
||||
"script": script,
|
||||
"enabled": 1,
|
||||
"is_standard": 1,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_customer_in_helpdesk(name, email):
|
||||
helpdesk_crm_settings = frappe.get_single("Helpdesk CRM Settings")
|
||||
if not helpdesk_crm_settings.enabled:
|
||||
frappe.throw(_("Helpdesk is not integrated with the CRM"))
|
||||
|
||||
if not helpdesk_crm_settings.is_helpdesk_in_different_site:
|
||||
# from helpdesk.integrations.crm.api import create_customer
|
||||
return create_customer(name, email)
|
||||
|
||||
|
||||
def get_helpdesk_script():
|
||||
return """class CRMDeal {
|
||||
onLoad() {
|
||||
this.actions.push(
|
||||
{
|
||||
group: "Helpdesk",
|
||||
hideLabel: true,
|
||||
items: [
|
||||
{
|
||||
label: "Create customer in Helpdesk",
|
||||
onClick: () => {
|
||||
call('crm.fcrm.doctype.helpdesk_crm_settings.helpdesk_crm_settings.create_customer_in_helpdesk', {
|
||||
name: this.doc.organization,
|
||||
email: this.doc.email
|
||||
}).then((a) => {
|
||||
toast.success("Customer created successfully, " + a.customer)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
}"""
|
||||
|
||||
# Helpdesk methods TODO: move to helpdesk.integrations.crm.api
|
||||
def create_customer(name, email):
|
||||
customer = frappe.db.exists("HD Customer", name)
|
||||
if not customer:
|
||||
customer = frappe.get_doc(
|
||||
{
|
||||
"doctype": "HD Customer",
|
||||
"customer_name": name,
|
||||
}
|
||||
)
|
||||
customer.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
else:
|
||||
customer = frappe.get_doc("HD Customer", customer)
|
||||
|
||||
contact = frappe.db.exists("Contact", {"email_id": email})
|
||||
if contact:
|
||||
contact = frappe.get_doc("Contact", contact)
|
||||
contact.append(
|
||||
"links", {"link_doctype": "HD Customer", "link_name": customer.name}
|
||||
)
|
||||
contact.save(ignore_permissions=True)
|
||||
else:
|
||||
contact = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Contact",
|
||||
"first_name": email.split("@")[0],
|
||||
"email_ids": [{"email_id": email, "is_primary": 1}],
|
||||
"links": [{"link_doctype": "HD Customer", "link_name": customer.name}],
|
||||
}
|
||||
)
|
||||
contact.insert(ignore_permissions=True)
|
||||
|
||||
if not frappe.db.exists("User", contact.email_id):
|
||||
invite_user(contact.name)
|
||||
else:
|
||||
base_url = frappe.utils.get_url() + "/helpdesk"
|
||||
frappe.sendmail(
|
||||
recipients=[contact.email_id],
|
||||
subject="Welcome existing user to Helpdesk",
|
||||
message=f"""
|
||||
<h1>Hello,</h1>
|
||||
<button>{base_url}</button>
|
||||
""",
|
||||
now=True,
|
||||
)
|
||||
|
||||
return {"customer": customer.name, "contact": contact.name}
|
||||
|
||||
|
||||
def invite_user(contact: str):
|
||||
contact = frappe.get_doc("Contact", contact)
|
||||
contact.check_permission()
|
||||
|
||||
if not contact.email_id:
|
||||
frappe.throw(_("Please set Email Address"))
|
||||
|
||||
user = frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"first_name": contact.first_name,
|
||||
"last_name": contact.last_name,
|
||||
"email": contact.email_id,
|
||||
"user_type": "Website User",
|
||||
"send_welcome_email": 0
|
||||
}
|
||||
).insert()
|
||||
|
||||
contact.user = user.name
|
||||
contact.save(ignore_permissions=True)
|
||||
|
||||
send_welcome_mail_to_user(user)
|
||||
return user.name
|
||||
|
||||
|
||||
def send_welcome_mail_to_user(user):
|
||||
from frappe.utils import get_url
|
||||
from frappe.utils.user import get_user_fullname
|
||||
|
||||
link = user.reset_password()
|
||||
|
||||
frappe.cache.hset("redirect_after_login", user.name, "/helpdesk")
|
||||
|
||||
site_url = get_url()
|
||||
subject = _("Welcome to Helpdesk")
|
||||
|
||||
created_by = get_user_fullname(frappe.session["user"])
|
||||
if created_by == "Guest":
|
||||
created_by = "Administrator"
|
||||
|
||||
args = {
|
||||
"first_name": user.first_name or user.last_name or "user",
|
||||
"last_name": user.last_name,
|
||||
"user": user.name,
|
||||
"title": subject,
|
||||
"login_url": get_url(),
|
||||
"created_by": created_by,
|
||||
"site_url": site_url,
|
||||
"link": link
|
||||
}
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=[user.email],
|
||||
subject=subject,
|
||||
template="helpdesk_invitation",
|
||||
args=args,
|
||||
now=True,
|
||||
)
|
||||
@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
|
||||
class IntegrationTestHelpdeskCRMSettings(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for HelpdeskCRMSettings.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
24
crm/templates/emails/helpdesk_invitation.html
Normal file
24
crm/templates/emails/helpdesk_invitation.html
Normal file
@ -0,0 +1,24 @@
|
||||
<p>
|
||||
{{_("Hello")}} {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},
|
||||
</p>
|
||||
{% set site_link = "<a href='" + site_url + "'>" + site_url + "</a>" %}
|
||||
<p>{{_("A new account has been created for you at {0}").format(site_link)}}.</p>
|
||||
<p>{{_("Your login id is")}}: <b>{{ user }}</b>
|
||||
<p>{{_("Click on the link below to complete your registration and set a new password")}}.</p>
|
||||
|
||||
<p style="margin: 15px 0px;">
|
||||
<a href="{{ link }}" rel="nofollow" class="btn btn-primary">{{ _("Complete Registration") }}</a>
|
||||
</p>
|
||||
|
||||
{% if created_by != "Administrator" %}
|
||||
<br>
|
||||
<p style="margin-top: 15px">
|
||||
{{_("Thanks")}},<br>
|
||||
{{ created_by }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<br>
|
||||
<p>
|
||||
{{_("You can also copy-paste following link in your browser")}}<br>
|
||||
<a href="{{ link }}">{{ link }}</a>
|
||||
</p>
|
||||
2
frontend/components.d.ts
vendored
2
frontend/components.d.ts
vendored
@ -142,6 +142,8 @@ declare module 'vue' {
|
||||
GroupBy: typeof import('./src/components/GroupBy.vue')['default']
|
||||
GroupByIcon: typeof import('./src/components/Icons/GroupByIcon.vue')['default']
|
||||
HeartIcon: typeof import('./src/components/Icons/HeartIcon.vue')['default']
|
||||
HelpdeskIcon: typeof import('./src/components/Icons/HelpdeskIcon.vue')['default']
|
||||
HelpdeskSettings: typeof import('./src/components/Settings/HelpdeskSettings.vue')['default']
|
||||
HelpIcon: typeof import('./src/components/Icons/HelpIcon.vue')['default']
|
||||
HomeActions: typeof import('./src/components/Settings/General/HomeActions.vue')['default']
|
||||
Icon: typeof import('./src/components/Icon.vue')['default']
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1 5C1 2.79086 2.79086 1 5 1H13C15.2091 1 17 2.79086 17 5V13C17 15.2091 15.2091 17 13 17H5C2.79086 17 1 15.2091 1 13V5Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M11.7819 6.27142H11.5136H8.02453H6.28001V4.84002H11.7819V6.27142ZM8.02451 9.62623V11.5944H11.8267V13.0258H6.27999V8.19484H8.02451H11.5135V9.62623H8.02451Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.6611 8.2289V9.77773H7.88672V11.9066H11.999V13.4545H6V8.2289H11.6611ZM11.9512 4.6V6.14883H6V4.6H11.9512Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<rect x="1.5" y="1.5" width="15" height="15" rx="3.5" stroke="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
15
frontend/src/components/Icons/HelpdeskIcon.vue
Normal file
15
frontend/src/components/Icons/HelpdeskIcon.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="1.5" y="1.5" width="15" height="15" rx="3.5" stroke="currentColor" />
|
||||
<path
|
||||
d="M13.7928 8.0619V5H4.29999V6.39494H12.3621V7.72014C11.787 7.88056 11.37 8.39669 11.37 9.00349C11.37 9.61029 11.787 10.1194 12.3621 10.2799V11.6051L5.79999 11.6051V7.96425H4.29999V13H13.8V9.9381L12.9444 9.34525V8.66173L13.8 8.06888L13.7928 8.0619Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
11
frontend/src/components/Settings/HelpdeskSettings.vue
Normal file
11
frontend/src/components/Settings/HelpdeskSettings.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<SettingsPage
|
||||
doctype="Helpdesk CRM Settings"
|
||||
:title="__('Helpdesk settings')"
|
||||
:successMessage="__('Helpdesk settings updated')"
|
||||
class="p-8"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import SettingsPage from '@/components/Settings/SettingsPage.vue'
|
||||
</script>
|
||||
@ -43,6 +43,7 @@
|
||||
<script setup>
|
||||
import WhatsAppIcon from '@/components/Icons/WhatsAppIcon.vue'
|
||||
import ERPNextIcon from '@/components/Icons/ERPNextIcon.vue'
|
||||
import HelpdeskIcon from '@/components/Icons/HelpdeskIcon.vue'
|
||||
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
|
||||
import Email2Icon from '@/components/Icons/Email2Icon.vue'
|
||||
import EmailTemplateIcon from '@/components/Icons/EmailTemplateIcon.vue'
|
||||
@ -52,6 +53,7 @@ import InviteUserPage from '@/components/Settings/InviteUserPage.vue'
|
||||
import ProfileSettings from '@/components/Settings/ProfileSettings.vue'
|
||||
import WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue'
|
||||
import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue'
|
||||
import HelpdeskSettings from '@/components/Settings/HelpdeskSettings.vue'
|
||||
import EmailTemplatePage from '@/components/Settings/EmailTemplate/EmailTemplatePage.vue'
|
||||
import TelephonySettings from '@/components/Settings/TelephonySettings.vue'
|
||||
import EmailConfig from '@/components/Settings/EmailConfig.vue'
|
||||
@ -137,6 +139,12 @@ const tabs = computed(() => {
|
||||
component: markRaw(ERPNextSettings),
|
||||
condition: () => isManager(),
|
||||
},
|
||||
{
|
||||
label: __('Helpdesk'),
|
||||
icon: HelpdeskIcon,
|
||||
component: markRaw(HelpdeskSettings),
|
||||
condition: () => isManager(),
|
||||
},
|
||||
],
|
||||
condition: () => isManager() || isTelephonyAgent(),
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user