fix: renamed component names from Agent to User

(cherry picked from commit b534aae70bc6304c818ef33e05c8a0a50146a4cf)
This commit is contained in:
Shariq Ansari 2025-06-17 12:37:08 +05:30 committed by Mergify
parent c918a76635
commit 4ed8a54a98
9 changed files with 92 additions and 93 deletions

View File

@ -1,7 +1,7 @@
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
// frappe.ui.form.on("CRM Agent", { // frappe.ui.form.on("CRM User", {
// refresh(frm) { // refresh(frm) {
// }, // },

View File

@ -13,7 +13,7 @@
"middle_name", "middle_name",
"last_name", "last_name",
"column_break_kpoq", "column_break_kpoq",
"agent_name", "user_name",
"image" "image"
], ],
"fields": [ "fields": [
@ -34,11 +34,6 @@
"fieldname": "section_break_qdxc", "fieldname": "section_break_qdxc",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{
"fieldname": "agent_name",
"fieldtype": "Data",
"label": "Full Name"
},
{ {
"fieldname": "column_break_kpoq", "fieldname": "column_break_kpoq",
"fieldtype": "Column Break" "fieldtype": "Column Break"
@ -66,16 +61,21 @@
"fieldname": "last_name", "fieldname": "last_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Last Name" "label": "Last Name"
},
{
"fieldname": "user_name",
"fieldtype": "Data",
"label": "Full Name"
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2025-05-21 16:35:07.667954", "modified": "2025-06-17 12:08:16.616216",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "FCRM", "module": "FCRM",
"name": "CRM Agent", "name": "CRM User",
"naming_rule": "By fieldname", "naming_rule": "By fieldname",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
@ -96,5 +96,5 @@
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"title_field": "agent_name" "title_field": "user_name"
} }

View File

@ -5,7 +5,7 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class CRMAgent(Document): class CRMUser(Document):
def validate(self): def validate(self):
if self.user: if self.user:
user = frappe.get_doc("User", self.user) user = frappe.get_doc("User", self.user)
@ -15,16 +15,16 @@ class CRMAgent(Document):
self.middle_name = user.middle_name self.middle_name = user.middle_name
if not self.last_name: if not self.last_name:
self.last_name = user.last_name self.last_name = user.last_name
if not self.agent_name: if not self.user_name:
self.agent_name = user.full_name self.user_name = user.full_name
if not self.image: if not self.image:
self.image = user.user_image self.image = user.user_image
@frappe.whitelist() @frappe.whitelist()
def update_agent_role(user, new_role): def update_user_role(user, new_role):
""" """
Update the role of the user to Agent Update the role of the user to Sales Manager, Sales User, or System Manager.
:param user: The name of the user :param user: The name of the user
:param new_role: The new role to assign (Sales Manager or Sales User) :param new_role: The new role to assign (Sales Manager or Sales User)
""" """
@ -49,12 +49,12 @@ def update_agent_role(user, new_role):
@frappe.whitelist() @frappe.whitelist()
def update_agent_status(agent, status): def update_user_status(user, status):
""" """
Activate or deactivate the agent Activate or deactivate the user
:param agent: The name of the agent :param user: The name of the user
:param status: The status to set (1 for active, 0 for inactive) :param status: The status to set (1 for active, 0 for inactive)
""" """
frappe.only_for("Sales Manager") frappe.only_for("Sales Manager")
frappe.db.set_value("CRM Agent", agent, "is_active", status) frappe.db.set_value("CRM User", user, "is_active", status)

View File

@ -11,18 +11,18 @@ EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
class UnitTestCRMAgent(UnitTestCase): class UnitTestCRMUser(UnitTestCase):
""" """
Unit tests for CRMAgent. Unit tests for CRMUser.
Use this class for testing individual functions and methods. Use this class for testing individual functions and methods.
""" """
pass pass
class IntegrationTestCRMAgent(IntegrationTestCase): class IntegrationTestCRMUser(IntegrationTestCase):
""" """
Integration tests for CRMAgent. Integration tests for CRMUser.
Use this class for testing interactions between multiple components. Use this class for testing interactions between multiple components.
""" """

View File

@ -138,6 +138,7 @@ declare module 'vue' {
IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default'] IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default']
InviteAgentPage: typeof import('./src/components/Settings/InviteAgentPage.vue')['default'] InviteAgentPage: typeof import('./src/components/Settings/InviteAgentPage.vue')['default']
InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default'] InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default']
InviteUserPage: typeof import('./src/components/Settings/InviteUserPage.vue')['default']
KanbanIcon: typeof import('./src/components/Icons/KanbanIcon.vue')['default'] KanbanIcon: typeof import('./src/components/Icons/KanbanIcon.vue')['default']
KanbanSettings: typeof import('./src/components/Kanban/KanbanSettings.vue')['default'] KanbanSettings: typeof import('./src/components/Kanban/KanbanSettings.vue')['default']
KanbanView: typeof import('./src/components/Kanban/KanbanView.vue')['default'] KanbanView: typeof import('./src/components/Kanban/KanbanView.vue')['default']
@ -229,6 +230,7 @@ declare module 'vue' {
UnpinIcon: typeof import('./src/components/Icons/UnpinIcon.vue')['default'] UnpinIcon: typeof import('./src/components/Icons/UnpinIcon.vue')['default']
UserAvatar: typeof import('./src/components/UserAvatar.vue')['default'] UserAvatar: typeof import('./src/components/UserAvatar.vue')['default']
UserDropdown: typeof import('./src/components/UserDropdown.vue')['default'] UserDropdown: typeof import('./src/components/UserDropdown.vue')['default']
Users: typeof import('./src/components/Settings/Users.vue')['default']
ViewBreadcrumbs: typeof import('./src/components/ViewBreadcrumbs.vue')['default'] ViewBreadcrumbs: typeof import('./src/components/ViewBreadcrumbs.vue')['default']
ViewControls: typeof import('./src/components/ViewControls.vue')['default'] ViewControls: typeof import('./src/components/ViewControls.vue')['default']
ViewModal: typeof import('./src/components/Modals/ViewModal.vue')['default'] ViewModal: typeof import('./src/components/Modals/ViewModal.vue')['default']

View File

@ -46,9 +46,9 @@ import ERPNextIcon from '@/components/Icons/ERPNextIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import InviteIcon from '@/components/Icons/InviteIcon.vue' import InviteIcon from '@/components/Icons/InviteIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue' import Email2Icon from '@/components/Icons/Email2Icon.vue'
import Agents from '@/components/Settings/Agents.vue' import Users from '@/components/Settings/Users.vue'
import GeneralSettings from '@/components/Settings/GeneralSettings.vue' import GeneralSettings from '@/components/Settings/GeneralSettings.vue'
import InviteAgentPage from '@/components/Settings/InviteAgentPage.vue' import InviteUserPage from '@/components/Settings/InviteUserPage.vue'
import ProfileSettings from '@/components/Settings/ProfileSettings.vue' import ProfileSettings from '@/components/Settings/ProfileSettings.vue'
import WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue' import WhatsAppSettings from '@/components/Settings/WhatsAppSettings.vue'
import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue' import ERPNextSettings from '@/components/Settings/ERPNextSettings.vue'
@ -84,21 +84,21 @@ const tabs = computed(() => {
}), }),
component: markRaw(ProfileSettings), component: markRaw(ProfileSettings),
}, },
{
label: __('Users'),
icon: 'user',
component: markRaw(Agents),
},
{ {
label: __('General'), label: __('General'),
icon: 'settings', icon: 'settings',
component: markRaw(GeneralSettings), component: markRaw(GeneralSettings),
condition: () => isManager(), condition: () => isManager(),
}, },
{
label: __('Users'),
icon: 'user',
component: markRaw(Users),
},
{ {
label: __('Invite User'), label: __('Invite User'),
icon: InviteIcon, icon: InviteIcon,
component: markRaw(InviteAgentPage), component: markRaw(InviteUserPage),
condition: () => isManager(), condition: () => isManager(),
}, },
{ {

View File

@ -31,15 +31,15 @@
:label="__('Add User')" :label="__('Add User')"
icon-left="plus" icon-left="plus"
variant="solid" variant="solid"
@click="$emit('add-agent')" @click="$emit('add-user')"
/> />
</div> </div>
</div> </div>
<!-- loading state --> <!-- loading state -->
<div v-if="agents.loading" class="flex mt-28 justify-between w-full h-full"> <div v-if="users.loading" class="flex mt-28 justify-between w-full h-full">
<Button <Button
:loading="agents.loading" :loading="users.loading"
variant="ghost" variant="ghost"
class="w-full" class="w-full"
size="2xl" size="2xl"
@ -47,30 +47,30 @@
</div> </div>
<!-- Empty State --> <!-- Empty State -->
<div <div
v-if="!agents.loading && !agents.data?.length" v-if="!users.loading && !users.data?.length"
class="flex mt-28 justify-between w-full h-full" class="flex mt-28 justify-between w-full h-full"
> >
<p class="text-sm text-gray-500 w-full flex justify-center"> <p class="text-sm text-gray-500 w-full flex justify-center">
{{ __('No agents found') }} {{ __('No users found') }}
</p> </p>
</div> </div>
<!-- Agents List --> <!-- Users List -->
<ul <ul
v-if="!agents.loading && Boolean(agents.data?.length)" v-if="!users.loading && Boolean(users.data?.length)"
class="divide-y overflow-auto" class="divide-y overflow-auto"
> >
<li <li
class="flex items-center justify-between py-2" class="flex items-center justify-between py-2"
v-for="agent in agents.data" v-for="user in users.data"
:key="agent.name" :key="user.name"
> >
<div class="flex items-center"> <div class="flex items-center">
<Avatar :image="agent.image" :label="agent.agent_name" size="xl" /> <Avatar :image="user.image" :label="user.user_name" size="xl" />
<div class="flex flex-col gap-1 ml-3"> <div class="flex flex-col gap-1 ml-3">
<div class="flex items-center gap-2 text-base text-ink-gray-9 h-4"> <div class="flex items-center gap-2 text-base text-ink-gray-9 h-4">
{{ agent.agent_name }} {{ user.user_name }}
<Badge <Badge
v-if="!agent.is_active" v-if="!user.is_active"
variant="subtle" variant="subtle"
theme="gray" theme="gray"
size="sm" size="sm"
@ -78,22 +78,22 @@
/> />
</div> </div>
<div class="text-base text-ink-gray-5"> <div class="text-base text-ink-gray-5">
{{ agent.name }} {{ user.name }}
</div> </div>
</div> </div>
</div> </div>
<div class="flex gap-2 items-center flex-row-reverse"> <div class="flex gap-2 items-center flex-row-reverse">
<Dropdown <Dropdown
:options="getMoreOptions(agent)" :options="getMoreOptions(user)"
:button="{ :button="{
icon: 'more-horizontal', icon: 'more-horizontal',
}" }"
placement="right" placement="right"
/> />
<Dropdown <Dropdown
:options="getDropdownOptions(agent)" :options="getDropdownOptions(user)"
:button="{ :button="{
label: roleMap[getUserRole(agent.name)], label: roleMap[getUserRole(user.name)],
iconRight: 'chevron-down', iconRight: 'chevron-down',
}" }"
placement="right" placement="right"
@ -102,13 +102,13 @@
</li> </li>
<!-- Load More Button --> <!-- Load More Button -->
<div <div
v-if="!agents.loading && agents.hasNextPage" v-if="!users.loading && users.hasNextPage"
class="flex justify-center" class="flex justify-center"
> >
<Button <Button
class="mt-3.5 p-2" class="mt-3.5 p-2"
@click="() => agents.next()" @click="() => users.next()"
:loading="agents.loading" :loading="users.loading"
:label="__('Load More')" :label="__('Load More')"
icon-left="refresh-cw" icon-left="refresh-cw"
/> />
@ -130,12 +130,12 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { ref, h, watch, onMounted } from 'vue' import { ref, h, watch, onMounted } from 'vue'
const { users, getUserRole } = usersStore() const { users: usersResource, getUserRole } = usersStore()
const agents = createListResource({ const users = createListResource({
doctype: 'CRM Agent', doctype: 'CRM User',
cache: 'CRM Agents', cache: 'CRM Users',
fields: ['name', 'image', 'is_active', 'agent_name'], fields: ['name', 'image', 'is_active', 'user_name'],
filters: { is_active: ['=', 1] }, filters: { is_active: ['=', 1] },
auto: true, auto: true,
start: 0, start: 0,
@ -149,27 +149,27 @@ const roleMap = {
'Sales User': __('Sales User'), 'Sales User': __('Sales User'),
} }
function getMoreOptions(agent) { function getMoreOptions(user) {
let options = [ let options = [
{ {
label: __('Activate'), label: __('Activate'),
icon: 'check-circle', icon: 'check-circle',
onClick: () => updateStatus(agent, true), onClick: () => updateStatus(user, true),
condition: () => !agent.is_active, condition: () => !user.is_active,
}, },
{ {
label: __('Deactivate'), label: __('Deactivate'),
icon: 'x-circle', icon: 'x-circle',
onClick: () => updateStatus(agent, false), onClick: () => updateStatus(user, false),
condition: () => agent.is_active, condition: () => user.is_active,
}, },
] ]
return options.filter((option) => option.condition()) return options.filter((option) => option.condition())
} }
function getDropdownOptions(agent) { function getDropdownOptions(user) {
const agentRole = getUserRole(agent.name) const userRole = getUserRole(user.name)
return [ return [
{ {
label: __('Admin'), label: __('Admin'),
@ -177,8 +177,8 @@ function getDropdownOptions(agent) {
RoleOption({ RoleOption({
role: __('Admin'), role: __('Admin'),
active: props.active, active: props.active,
selected: agentRole === 'System Manager', selected: userRole === 'System Manager',
onClick: () => updateRole(agent, 'System Manager'), onClick: () => updateRole(user, 'System Manager'),
}), }),
}, },
{ {
@ -187,8 +187,8 @@ function getDropdownOptions(agent) {
RoleOption({ RoleOption({
role: __('Manager'), role: __('Manager'),
active: props.active, active: props.active,
selected: agentRole === 'Sales Manager', selected: userRole === 'Sales Manager',
onClick: () => updateRole(agent, 'Sales Manager'), onClick: () => updateRole(user, 'Sales Manager'),
}), }),
}, },
{ {
@ -197,8 +197,8 @@ function getDropdownOptions(agent) {
RoleOption({ RoleOption({
role: __('Sales User'), role: __('Sales User'),
active: props.active, active: props.active,
selected: agentRole === 'Sales User', selected: userRole === 'Sales User',
onClick: () => updateRole(agent, 'Sales User'), onClick: () => updateRole(user, 'Sales User'),
}), }),
}, },
] ]
@ -226,40 +226,37 @@ function RoleOption({ active, role, onClick, selected }) {
) )
} }
function updateRole(agent, newRole) { function updateRole(user, newRole) {
const currentRole = getUserRole(agent.name) const currentRole = getUserRole(user.name)
if (currentRole === newRole) return if (currentRole === newRole) return
call('crm.fcrm.doctype.crm_agent.crm_agent.update_agent_role', { call('crm.fcrm.doctype.crm_user.crm_user.update_user_role', {
user: agent.name, user: user.name,
new_role: newRole, new_role: newRole,
}).then(() => { }).then(() => {
toast.success( toast.success(
__('{0} has been granted {1} access', [ __('{0} has been granted {1} access', [user.user_name, roleMap[newRole]]),
agent.agent_name,
roleMap[newRole],
]),
) )
usersResource.reload()
users.reload() users.reload()
agents.reload()
}) })
} }
function updateStatus(agent, status) { function updateStatus(user, status) {
const currentStatus = agent.is_active const currentStatus = user.is_active
if (currentStatus === status) return if (currentStatus === status) return
call('crm.fcrm.doctype.crm_agent.crm_agent.update_agent_status', { call('crm.fcrm.doctype.crm_user.crm_user.update_user_status', {
agent: agent.name, user: user.name,
status, status,
}).then(() => { }).then(() => {
toast.success( toast.success(
__('{0} has been {1}', [ __('{0} has been {1}', [
agent.agent_name, user.user_name,
status ? 'activated' : 'deactivated', status ? 'activated' : 'deactivated',
]), ]),
) )
agents.reload() users.reload()
}) })
} }
@ -273,30 +270,30 @@ function changeStatus(status) {
function updateFilters() { function updateFilters() {
const status = currentStatus.value || 'Active' const status = currentStatus.value || 'Active'
agents.filters = {} users.filters = {}
if (status === 'Active') { if (status === 'Active') {
agents.filters.is_active = ['=', 1] users.filters.is_active = ['=', 1]
} else if (status === 'Inactive') { } else if (status === 'Inactive') {
agents.filters.is_active = ['=', 0] users.filters.is_active = ['=', 0]
} }
agents.reload() users.reload()
} }
onMounted(() => updateFilters()) onMounted(() => updateFilters())
const search = ref('') const search = ref('')
watch(search, (newValue) => { watch(search, (newValue) => {
agents.filters = { users.filters = {
is_active: ['=', 1], is_active: ['=', 1],
agent_name: ['like', `%${newValue}%`], user_name: ['like', `%${newValue}%`],
} }
if (!newValue) { if (!newValue) {
agents.filters = { users.filters = {
is_active: ['=', 1], is_active: ['=', 1],
} }
agents.start = 0 users.start = 0
agents.pageLength = 10 users.pageLength = 10
} }
agents.reload() users.reload()
}) })
</script> </script>