feat: set contact as primary from UI
This commit is contained in:
parent
36f4ddee20
commit
d408d066c7
@ -22,7 +22,7 @@ def get_deal(name):
|
||||
deal["contacts"] = frappe.get_all(
|
||||
"CRM Contacts",
|
||||
filters={"parenttype": "CRM Deal", "parent": deal.name},
|
||||
fields=["contact"],
|
||||
fields=["contact", "is_primary"],
|
||||
)
|
||||
|
||||
return deal
|
||||
|
||||
@ -75,4 +75,14 @@ def remove_contact(deal, contact):
|
||||
deal = frappe.get_cached_doc("CRM Deal", deal)
|
||||
deal.contacts = [d for d in deal.contacts if d.contact != contact]
|
||||
deal.save()
|
||||
return True
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_primary_contact(deal, contact):
|
||||
if not frappe.has_permission("CRM Deal", "write", deal):
|
||||
frappe.throw(_("Not allowed to set primary contact for Deal"), frappe.PermissionError)
|
||||
|
||||
deal = frappe.get_cached_doc("CRM Deal", deal)
|
||||
deal.set_primary_contact(contact)
|
||||
deal.save()
|
||||
return True
|
||||
16
frontend/src/components/Icons/SuccessIcon.vue
Normal file
16
frontend/src/components/Icons/SuccessIcon.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14 8C14 11.3137 11.3137 14 8 14C4.68629 14 2 11.3137 2 8C2 4.68629 4.68629 2 8 2C11.3137 2 14 4.68629 14 8ZM15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8ZM11.2909 5.98482C11.4666 5.77175 11.4363 5.45663 11.2232 5.28096C11.0101 5.1053 10.695 5.13561 10.5193 5.34868L7.07001 9.53239L5.72845 7.79857C5.55946 7.58018 5.24543 7.54012 5.02703 7.70911C4.80863 7.8781 4.76858 8.19214 4.93756 8.41053L6.66217 10.6394C6.7552 10.7596 6.89788 10.831 7.04988 10.8334C7.20188 10.8357 7.3467 10.7688 7.4434 10.6515L11.2909 5.98482Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@ -209,37 +209,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</Autocomplete>
|
||||
<Dropdown
|
||||
v-else-if="field.type === 'dropdown'"
|
||||
:options="
|
||||
statusDropdownOptions(deal.data, 'deal', updateField)
|
||||
"
|
||||
class="w-full flex-1"
|
||||
>
|
||||
<template #default="{ open }">
|
||||
<Button
|
||||
:label="deal.data[field.name]"
|
||||
class="w-full justify-between"
|
||||
>
|
||||
<template #prefix>
|
||||
<IndicatorIcon
|
||||
:class="
|
||||
dealStatuses[deal.data[field.name]].color
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #default>{{
|
||||
deal.data[field.name]
|
||||
}}</template>
|
||||
<template #suffix>
|
||||
<FeatherIcon
|
||||
:name="open ? 'chevron-up' : 'chevron-down'"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</template>
|
||||
</Dropdown>
|
||||
<FormControl
|
||||
v-else-if="field.type === 'date'"
|
||||
type="date"
|
||||
@ -307,28 +276,41 @@
|
||||
size="md"
|
||||
/>
|
||||
{{ getContactByName(contact.name).full_name }}
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<FeatherIcon
|
||||
name="trash-2"
|
||||
class="h-4 w-4"
|
||||
@click="removeContact(contact.name)"
|
||||
<Badge
|
||||
v-if="contact.is_primary"
|
||||
class="ml-2"
|
||||
variant="outline"
|
||||
label="Primary"
|
||||
theme="green"
|
||||
/>
|
||||
<ExternalLinkIcon
|
||||
class="h-4 w-4"
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<Dropdown :options="contactOptions(contact)">
|
||||
<Button variant="ghost">
|
||||
<FeatherIcon
|
||||
name="more-horizontal"
|
||||
class="h-4 text-gray-600"
|
||||
/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@click="
|
||||
router.push({
|
||||
name: 'Contact',
|
||||
params: { contactId: contact.name },
|
||||
})
|
||||
"
|
||||
/>
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 w-4 text-gray-900 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': cOpened }"
|
||||
@click="cToggle()"
|
||||
/>
|
||||
>
|
||||
<ExternalLinkIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" @click="cToggle()">
|
||||
<FeatherIcon
|
||||
name="chevron-right"
|
||||
class="h-4 w-4 text-gray-900 transition-all duration-300 ease-in-out"
|
||||
:class="{ 'rotate-90': cOpened }"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<transition
|
||||
@ -403,6 +385,7 @@ import NoteIcon from '@/components/Icons/NoteIcon.vue'
|
||||
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
|
||||
import LinkIcon from '@/components/Icons/LinkIcon.vue'
|
||||
import ExternalLinkIcon from '@/components/Icons/ExternalLinkIcon.vue'
|
||||
import SuccessIcon from '@/components/Icons/SuccessIcon.vue'
|
||||
import LayoutHeader from '@/components/LayoutHeader.vue'
|
||||
import Toggler from '@/components/Toggler.vue'
|
||||
import Activities from '@/components/Activities.vue'
|
||||
@ -430,8 +413,9 @@ import {
|
||||
Tabs,
|
||||
Breadcrumbs,
|
||||
call,
|
||||
Badge,
|
||||
} from 'frappe-ui'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { getUser } = usersStore()
|
||||
@ -588,6 +572,7 @@ const detailSections = computed(() => {
|
||||
contacts: deal.data.contacts.map((contact) => {
|
||||
return {
|
||||
name: contact.contact,
|
||||
is_primary: contact.is_primary,
|
||||
opened: false,
|
||||
}
|
||||
}),
|
||||
@ -598,10 +583,30 @@ const detailSections = computed(() => {
|
||||
const showContactModal = ref(false)
|
||||
const _contact = ref({})
|
||||
|
||||
async function addContact(value) {
|
||||
function contactOptions(contact) {
|
||||
let options = [
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: 'trash-2',
|
||||
onClick: () => removeContact(contact.name),
|
||||
},
|
||||
]
|
||||
|
||||
if (!contact.is_primary) {
|
||||
options.push({
|
||||
label: 'Set as primary contact',
|
||||
icon: h(SuccessIcon, { class: 'h-4 w-4' }),
|
||||
onClick: () => setPrimaryContact(contact.name),
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
async function addContact(contact) {
|
||||
let d = await call('crm.fcrm.doctype.crm_deal.crm_deal.add_contact', {
|
||||
deal: props.dealId,
|
||||
contact: value,
|
||||
contact,
|
||||
})
|
||||
if (d) {
|
||||
await contacts.reload()
|
||||
@ -614,10 +619,10 @@ async function addContact(value) {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeContact(value) {
|
||||
async function removeContact(contact) {
|
||||
let d = await call('crm.fcrm.doctype.crm_deal.crm_deal.remove_contact', {
|
||||
deal: props.dealId,
|
||||
contact: value,
|
||||
contact,
|
||||
})
|
||||
if (d) {
|
||||
deal.reload()
|
||||
@ -630,6 +635,22 @@ async function removeContact(value) {
|
||||
}
|
||||
}
|
||||
|
||||
async function setPrimaryContact(contact) {
|
||||
let d = await call('crm.fcrm.doctype.crm_deal.crm_deal.set_primary_contact', {
|
||||
deal: props.dealId,
|
||||
contact,
|
||||
})
|
||||
if (d) {
|
||||
await contacts.reload()
|
||||
deal.reload()
|
||||
createToast({
|
||||
title: 'Primary contact set',
|
||||
icon: 'check',
|
||||
iconClasses: 'text-green-600',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const organization = computed(() => {
|
||||
return getOrganization(deal.data.organization)
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user