1
0
forked from test/crm

Merge pull request #895 from frappe/mergify/bp/main-hotfix/pr-894

This commit is contained in:
Shariq Ansari 2025-06-06 15:02:09 +05:30 committed by GitHub
commit 31c649d6f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 129 additions and 46 deletions

View File

@ -3,6 +3,7 @@ import json
import frappe
from frappe import _
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.form.assign_to import set_status
from frappe.model import no_value_fields
from frappe.model.document import get_controller
from frappe.utils import make_filter_tuple
@ -658,6 +659,24 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False, only_re
return fields_meta
@frappe.whitelist()
def remove_assignments(doctype, name, assignees, ignore_permissions=False):
assignees = json.loads(assignees)
if not assignees:
return
for assign_to in assignees:
set_status(
doctype,
name,
todo=None,
assign_to=assign_to,
status="Cancelled",
ignore_permissions=ignore_permissions,
)
@frappe.whitelist()
def get_assigned_users(doctype, name, default_assigned_to=None):
assigned_users = frappe.get_all(
"ToDo",

View File

@ -13,7 +13,6 @@ def get_deal(name):
deal["fields_meta"] = get_fields_meta("CRM Deal")
deal["_form_script"] = get_form_script("CRM Deal")
deal["_assign"] = get_assigned_users("CRM Deal", deal.name)
return deal

View File

@ -13,5 +13,4 @@ def get_lead(name):
lead["fields_meta"] = get_fields_meta("CRM Lead")
lead["_form_script"] = get_form_script("CRM Lead")
lead["_assign"] = get_assigned_users("CRM Lead", lead.name)
return lead

View File

@ -365,7 +365,11 @@
</div>
</div>
<div v-else-if="title == 'Data'" class="h-full flex flex-col px-3 sm:px-10">
<DataFields :doctype="doctype" :docname="doc.data.name" />
<DataFields
:doctype="doctype"
:docname="doc.data.name"
@afterSave="(data) => emit('afterSave', data)"
/>
</div>
<div
v-else
@ -514,6 +518,8 @@ const props = defineProps({
},
})
const emit = defineEmits(['afterSave'])
const route = useRoute()
const doc = defineModel()

View File

@ -76,6 +76,9 @@ const props = defineProps({
required: true,
},
})
const emit = defineEmits(['afterSave'])
const { isManager } = usersStore()
const showDataFieldsModal = ref(false)
@ -90,7 +93,21 @@ const tabs = createResource({
})
function saveChanges() {
document.save.submit()
if (!document.isDirty) return
const updatedDoc = { ...document.doc }
const oldDoc = { ...document.originalDoc }
const changes = Object.keys(updatedDoc).reduce((acc, key) => {
if (JSON.stringify(updatedDoc[key]) !== JSON.stringify(oldDoc[key])) {
acc[key] = updatedDoc[key]
}
return acc
}, {})
document.save.submit(null, {
onSuccess: () => emit('afterSave', changes),
})
}
watch(

View File

@ -145,13 +145,11 @@ function updateAssignees() {
.map((assignee) => assignee.name)
if (removedAssignees.length) {
for (let a of removedAssignees) {
call('frappe.desk.form.assign_to.remove', {
doctype: props.doctype,
name: props.doc.name,
assign_to: a,
})
}
call('crm.api.doc.remove_assignments', {
doctype: props.doctype,
name: props.doc.name,
assignees: JSON.stringify(removedAssignees),
})
}
if (addedAssignees.length) {

View File

@ -417,13 +417,13 @@ const props = defineProps({
},
})
const emit = defineEmits(['afterFieldChange', 'reload'])
const { getFormattedPercent, getFormattedFloat, getFormattedCurrency } =
getMeta(props.doctype)
const { isManager, getUser } = usersStore()
const emit = defineEmits(['reload'])
const showSidePanelModal = ref(false)
let document = { doc: {} }
@ -493,7 +493,13 @@ async function fieldChange(value, df) {
await triggerOnChange(df.fieldname)
document.save.submit()
document.save.submit(null, {
onSuccess: () => {
emit('afterFieldChange', {
[df.fieldname]: value,
})
},
})
}
function parsedSection(section, editButtonAdded) {

View File

@ -1,6 +1,6 @@
import { getScript } from '@/data/script'
import { runSequentially } from '@/utils'
import { createDocumentResource, toast } from 'frappe-ui'
import { runSequentially, parseAssignees } from '@/utils'
import { createDocumentResource, createResource, toast } from 'frappe-ui'
import { reactive } from 'vue'
const documentsCache = {}
@ -35,6 +35,17 @@ export function useDocument(doctype, docname) {
}
}
const assignees = createResource({
url: 'crm.api.doc.get_assigned_users',
cache: `assignees:${doctype}:${docname}`,
auto: true,
params: {
doctype: doctype,
name: docname,
},
transform: (data) => parseAssignees(data),
})
async function setupFormScript() {
if (
controllersCache[doctype] &&
@ -177,6 +188,7 @@ export function useDocument(doctype, docname) {
return {
document: documentsCache[doctype][docname || ''],
assignees,
triggerOnChange,
triggerOnRowAdd,
triggerOnRowRemove,

View File

@ -13,8 +13,8 @@
:actions="deal.data._customActions"
/>
<AssignTo
v-model="deal.data._assignedTo"
:data="deal.data"
v-model="assignees.data"
:data="document.doc"
doctype="CRM Deal"
/>
<Dropdown
@ -46,6 +46,7 @@
v-model:reload="reload"
v-model:tabIndex="tabIndex"
v-model="deal"
@afterSave="reloadAssignees"
/>
</template>
</Tabs>
@ -134,6 +135,7 @@
doctype="CRM Deal"
:docname="deal.data.name"
@reload="sections.reload"
@afterFieldChange="reloadAssignees"
>
<template #actions="{ section }">
<div v-if="section.name == 'contacts_section'" class="pr-2">
@ -332,17 +334,13 @@ import Section from '@/components/Section.vue'
import SidePanelLayout from '@/components/SidePanelLayout.vue'
import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue'
import {
openWebsite,
setupAssignees,
setupCustomizations,
copyToClipboard,
} from '@/utils'
import { openWebsite, setupCustomizations, copyToClipboard } from '@/utils'
import { getView } from '@/utils/view'
import { getSettings } from '@/stores/settings'
import { globalStore } from '@/stores/global'
import { statusesStore } from '@/stores/statuses'
import { getMeta } from '@/stores/meta'
import { useDocument } from '@/data/document'
import { whatsappEnabled, callEnabled } from '@/composables/settings'
import {
createResource,
@ -396,7 +394,6 @@ const deal = createResource({
organization.fetch()
}
setupAssignees(deal)
setupCustomizations(deal, {
doc: data,
$dialog,
@ -721,4 +718,12 @@ const activities = ref(null)
function openEmailBox() {
activities.value.emailBox.show = true
}
const { assignees, document } = useDocument('CRM Deal', props.dealId)
function reloadAssignees(data) {
if (data?.hasOwnProperty('deal_owner')) {
assignees.reload()
}
}
</script>

View File

@ -13,8 +13,8 @@
:actions="lead.data._customActions"
/>
<AssignTo
v-model="lead.data._assignedTo"
:data="lead.data"
v-model="assignees.data"
:data="document.doc"
doctype="CRM Lead"
/>
<Dropdown
@ -51,6 +51,7 @@
v-model:reload="reload"
v-model:tabIndex="tabIndex"
v-model="lead"
@afterSave="reloadAssignees"
/>
</template>
</Tabs>
@ -186,6 +187,7 @@
doctype="CRM Lead"
:docname="lead.data.name"
@reload="sections.reload"
@afterFieldChange="reloadAssignees"
/>
</div>
</Resizer>
@ -335,12 +337,7 @@ import SidePanelLayout from '@/components/SidePanelLayout.vue'
import FieldLayout from '@/components/FieldLayout/FieldLayout.vue'
import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue'
import {
openWebsite,
setupAssignees,
setupCustomizations,
copyToClipboard,
} from '@/utils'
import { openWebsite, setupCustomizations, copyToClipboard } from '@/utils'
import { showQuickEntryModal, quickEntryProps } from '@/composables/modals'
import { getView } from '@/utils/view'
import { getSettings } from '@/stores/settings'
@ -403,7 +400,6 @@ const lead = createResource({
onSuccess: (data) => {
errorTitle.value = ''
errorMessage.value = ''
setupAssignees(lead)
setupCustomizations(lead, {
doc: data,
$dialog,
@ -609,7 +605,10 @@ const existingOrganizationChecked = ref(false)
const existingContact = ref('')
const existingOrganization = ref('')
const { triggerConvertToDeal } = useDocument('CRM Lead', props.leadId)
const { triggerConvertToDeal, assignees, document } = useDocument(
'CRM Lead',
props.leadId,
)
async function convertToDeal() {
if (existingContactChecked.value && !existingContact.value) {
@ -711,4 +710,10 @@ function openQuickEntryModal() {
}
showConvertToDealModal.value = false
}
function reloadAssignees(data) {
if (data?.hasOwnProperty('lead_owner')) {
assignees.reload()
}
}
</script>

View File

@ -36,8 +36,8 @@
class="flex h-12 items-center justify-between gap-2 border-b px-3 py-2.5"
>
<AssignTo
v-model="deal.data._assignedTo"
:data="deal.data"
v-model="assignees.data"
:data="document.doc"
doctype="CRM Deal"
/>
<div class="flex items-center gap-2">
@ -66,6 +66,7 @@
doctype="CRM Deal"
:docname="deal.data.name"
@reload="sections.reload"
@afterFieldChange="reloadAssignees"
>
<template #actions="{ section }">
<div v-if="section.name == 'contacts_section'" class="pr-2">
@ -258,12 +259,13 @@ import Link from '@/components/Controls/Link.vue'
import SidePanelLayout from '@/components/SidePanelLayout.vue'
import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue'
import { setupAssignees, setupCustomizations } from '@/utils'
import { setupCustomizations } from '@/utils'
import { getView } from '@/utils/view'
import { getSettings } from '@/stores/settings'
import { globalStore } from '@/stores/global'
import { statusesStore } from '@/stores/statuses'
import { getMeta } from '@/stores/meta'
import { useDocument } from '@/data/document'
import {
whatsappEnabled,
callEnabled,
@ -311,7 +313,6 @@ const deal = createResource({
organization.fetch()
}
setupAssignees(deal)
setupCustomizations(deal, {
doc: data,
$dialog,
@ -605,4 +606,12 @@ async function deleteDeal(name) {
})
router.push({ name: 'Deals' })
}
const { assignees, document } = useDocument('CRM Deal', props.dealId)
function reloadAssignees(data) {
if (data?.hasOwnProperty('deal_owner')) {
assignees.reload()
}
}
</script>

View File

@ -36,8 +36,8 @@
class="flex h-12 items-center justify-between gap-2 border-b px-3 py-2.5"
>
<AssignTo
v-model="lead.data._assignedTo"
:data="lead.data"
v-model="assignees.data"
:data="document.doc"
doctype="CRM Lead"
/>
<div class="flex items-center gap-2">
@ -71,6 +71,7 @@
doctype="CRM Lead"
:docname="lead.data.name"
@reload="sections.reload"
@afterFieldChange="reloadAssignees"
/>
</div>
</div>
@ -173,12 +174,13 @@ import Link from '@/components/Controls/Link.vue'
import SidePanelLayout from '@/components/SidePanelLayout.vue'
import SLASection from '@/components/SLASection.vue'
import CustomActions from '@/components/CustomActions.vue'
import { setupAssignees, setupCustomizations } from '@/utils'
import { setupCustomizations } from '@/utils'
import { getView } from '@/utils/view'
import { getSettings } from '@/stores/settings'
import { globalStore } from '@/stores/global'
import { statusesStore } from '@/stores/statuses'
import { getMeta } from '@/stores/meta'
import { useDocument } from '@/data/document'
import {
whatsappEnabled,
callEnabled,
@ -220,7 +222,6 @@ const lead = createResource({
params: { name: props.leadId },
cache: ['lead', props.leadId],
onSuccess: (data) => {
setupAssignees(lead)
setupCustomizations(lead, {
doc: data,
$dialog,
@ -454,4 +455,12 @@ async function convertToDeal() {
router.push({ name: 'Deal', params: { dealId: deal } })
}
}
const { assignees, document } = useDocument('CRM Lead', props.leadId)
function reloadAssignees(data) {
if (data?.hasOwnProperty('lead_owner')) {
assignees.reload()
}
}
</script>

View File

@ -211,10 +211,9 @@ export function validateEmail(email) {
return regExp.test(email)
}
export function setupAssignees(doc) {
export function parseAssignees(assignees) {
let { getUser } = usersStore()
let assignees = doc.data?._assign || []
doc.data._assignedTo = assignees.map((user) => ({
return assignees.map((user) => ({
name: user,
image: getUser(user).user_image,
label: getUser(user).full_name,