diff --git a/crm/api/doc.py b/crm/api/doc.py index b7b02511..b79de0c9 100644 --- a/crm/api/doc.py +++ b/crm/api/doc.py @@ -376,7 +376,7 @@ def get_type(field): return "read_only" return field.fieldtype.lower() -def get_assigned_users(doctype, name): +def get_assigned_users(doctype, name, default_assigned_to=None): assigned_users = frappe.get_all( "ToDo", fields=["allocated_to"], @@ -388,7 +388,12 @@ def get_assigned_users(doctype, name): pluck="allocated_to", ) - return list(set(assigned_users)) + users = list(set(assigned_users)) + + # if users is empty, add default_assigned_to + if not users and default_assigned_to: + users = [default_assigned_to] + return users @frappe.whitelist() diff --git a/crm/fcrm/doctype/crm_deal/api.py b/crm/fcrm/doctype/crm_deal/api.py index c05937bb..cb757b59 100644 --- a/crm/fcrm/doctype/crm_deal/api.py +++ b/crm/fcrm/doctype/crm_deal/api.py @@ -30,7 +30,7 @@ def get_deal(name): deal["doctype_fields"], deal["all_fields"] = get_doctype_fields("CRM Deal", name) deal["doctype"] = "CRM Deal" deal["_form_script"] = get_form_script('CRM Deal') - deal["_assign"] = get_assigned_users("CRM Deal", deal.name) + deal["_assign"] = get_assigned_users("CRM Deal", deal.name, deal.owner) return deal @frappe.whitelist() diff --git a/crm/fcrm/doctype/crm_deal/crm_deal.py b/crm/fcrm/doctype/crm_deal/crm_deal.py index d303c572..d4e3fcaa 100644 --- a/crm/fcrm/doctype/crm_deal/crm_deal.py +++ b/crm/fcrm/doctype/crm_deal/crm_deal.py @@ -108,8 +108,8 @@ class CRMDeal(Document): """ sla = get_sla(self) if not sla: - self.first_responded_on = None - self.first_response_time = None + # self.first_responded_on = None + # self.first_response_time = None return self.sla = sla.name diff --git a/crm/fcrm/doctype/crm_lead/api.py b/crm/fcrm/doctype/crm_lead/api.py index 74b6b7d1..b4dca9f7 100644 --- a/crm/fcrm/doctype/crm_lead/api.py +++ b/crm/fcrm/doctype/crm_lead/api.py @@ -18,5 +18,5 @@ def get_lead(name): lead["doctype_fields"], lead["all_fields"] = get_doctype_fields("CRM Lead", name) lead["doctype"] = "CRM Lead" lead["_form_script"] = get_form_script('CRM Lead') - lead["_assign"] = get_assigned_users("CRM Lead", lead.name) + lead["_assign"] = get_assigned_users("CRM Lead", lead.name, lead.owner) return lead diff --git a/crm/fcrm/doctype/crm_lead/crm_lead.py b/crm/fcrm/doctype/crm_lead/crm_lead.py index b8160525..0ec3ad81 100644 --- a/crm/fcrm/doctype/crm_lead/crm_lead.py +++ b/crm/fcrm/doctype/crm_lead/crm_lead.py @@ -234,8 +234,8 @@ class CRMLead(Document): """ sla = get_sla(self) if not sla: - self.first_responded_on = None - self.first_response_time = None + # self.first_responded_on = None + # self.first_response_time = None return self.sla = sla.name diff --git a/frontend/src/components/Activities.vue b/frontend/src/components/Activities.vue index a5c441da..c0157e95 100644 --- a/frontend/src/components/Activities.vue +++ b/frontend/src/components/Activities.vue @@ -15,6 +15,16 @@ {{ __('New Email') }} + + + + + {{ __('New Comment') }} + + + + + + + + + + + + + + {{ comment.owner_name }} + + {{ __('added a') }} + + {{ __('comment') }} + + + + + + {{ __(timeAgo(comment.creation)) }} + + + + + + + + + + + + + + + { label: __('New Email'), onClick: () => (emailBox.value.show = true), }, + { + icon: h(CommentIcon, { class: 'h-4 w-4' }), + label: __('New Comment'), + onClick: () => (emailBox.value.showComment = true), + }, { icon: h(PhoneIcon, { class: 'h-4 w-4' }), label: __('Make a Call'), @@ -1027,6 +1103,11 @@ const activities = computed(() => { activities = all_activities.data.versions.filter( (activity) => activity.activity_type === 'communication' ) + } else if (props.title == 'Comments') { + if (!all_activities.data?.versions) return [] + activities = all_activities.data.versions.filter( + (activity) => activity.activity_type === 'comment' + ) } else if (props.title == 'Calls') { if (!all_activities.data?.calls) return [] return sortByCreation(all_activities.data.calls) @@ -1089,6 +1170,8 @@ const emptyText = computed(() => { let text = 'No Activities' if (props.title == 'Emails') { text = 'No Email Communications' + } else if (props.title == 'Comments') { + text = 'No Comments' } else if (props.title == 'Calls') { text = 'No Call Logs' } else if (props.title == 'Notes') { @@ -1105,6 +1188,8 @@ const emptyTextIcon = computed(() => { let icon = ActivityIcon if (props.title == 'Emails') { icon = EmailIcon + } else if (props.title == 'Comments') { + icon = CommentIcon } else if (props.title == 'Calls') { icon = PhoneIcon } else if (props.title == 'Notes') { diff --git a/frontend/src/components/CommunicationArea.vue b/frontend/src/components/CommunicationArea.vue index dc18f3d3..07d3676f 100644 --- a/frontend/src/components/CommunicationArea.vue +++ b/frontend/src/components/CommunicationArea.vue @@ -248,5 +248,5 @@ function toggleCommentBox() { showCommentBox.value = !showCommentBox.value } -defineExpose({ show: showEmailBox, editor: newEmailEditor }) +defineExpose({ show: showEmailBox, showComment: showCommentBox, editor: newEmailEditor }) diff --git a/frontend/src/components/IconPicker.vue b/frontend/src/components/IconPicker.vue index 29fee1b7..c1249d25 100644 --- a/frontend/src/components/IconPicker.vue +++ b/frontend/src/components/IconPicker.vue @@ -66,7 +66,7 @@ diff --git a/frontend/src/components/ListBulkActions.vue b/frontend/src/components/ListBulkActions.vue new file mode 100644 index 00000000..6b54b15d --- /dev/null +++ b/frontend/src/components/ListBulkActions.vue @@ -0,0 +1,205 @@ + + + + + + diff --git a/frontend/src/components/ListViews/CallLogsListView.vue b/frontend/src/components/ListViews/CallLogsListView.vue index d4381818..e3aa425a 100644 --- a/frontend/src/components/ListViews/CallLogsListView.vue +++ b/frontend/src/components/ListViews/CallLogsListView.vue @@ -80,7 +80,9 @@ - + @@ -95,8 +97,18 @@ }" @loadMore="emit('loadMore')" /> + diff --git a/frontend/src/components/ListViews/ContactsListView.vue b/frontend/src/components/ListViews/ContactsListView.vue index e4fdda12..4a192497 100644 --- a/frontend/src/components/ListViews/ContactsListView.vue +++ b/frontend/src/components/ListViews/ContactsListView.vue @@ -82,7 +82,9 @@ - + @@ -98,19 +100,18 @@ }" @loadMore="emit('loadMore')" /> - diff --git a/frontend/src/components/ListViews/DealsListView.vue b/frontend/src/components/ListViews/DealsListView.vue index 40a0c934..8388c180 100644 --- a/frontend/src/components/ListViews/DealsListView.vue +++ b/frontend/src/components/ListViews/DealsListView.vue @@ -114,7 +114,9 @@ - + @@ -130,20 +132,14 @@ }" @loadMore="emit('loadMore')" /> - + diff --git a/frontend/src/components/ListViews/EmailTemplatesListView.vue b/frontend/src/components/ListViews/EmailTemplatesListView.vue index 1a35aa6d..52f3b4b0 100644 --- a/frontend/src/components/ListViews/EmailTemplatesListView.vue +++ b/frontend/src/components/ListViews/EmailTemplatesListView.vue @@ -69,7 +69,7 @@ - + @@ -84,18 +84,17 @@ }" @loadMore="emit('loadMore')" /> - diff --git a/frontend/src/components/ListViews/LeadsListView.vue b/frontend/src/components/ListViews/LeadsListView.vue index a7a0a005..1855e85e 100644 --- a/frontend/src/components/ListViews/LeadsListView.vue +++ b/frontend/src/components/ListViews/LeadsListView.vue @@ -123,7 +123,7 @@ - + @@ -139,20 +139,14 @@ }" @loadMore="emit('loadMore')" /> - + diff --git a/frontend/src/components/ListViews/OrganizationsListView.vue b/frontend/src/components/ListViews/OrganizationsListView.vue index e3ed5b99..7a3509e0 100644 --- a/frontend/src/components/ListViews/OrganizationsListView.vue +++ b/frontend/src/components/ListViews/OrganizationsListView.vue @@ -69,7 +69,9 @@ - + @@ -84,18 +86,17 @@ }" @loadMore="emit('loadMore')" /> - diff --git a/frontend/src/components/ListViews/TasksListView.vue b/frontend/src/components/ListViews/TasksListView.vue index 43a945aa..103a1a52 100644 --- a/frontend/src/components/ListViews/TasksListView.vue +++ b/frontend/src/components/ListViews/TasksListView.vue @@ -82,7 +82,7 @@ - + @@ -97,22 +97,21 @@ }" @loadMore="emit('loadMore')" /> - diff --git a/frontend/src/components/Modals/AssignmentModal.vue b/frontend/src/components/Modals/AssignmentModal.vue index d9c3b9fa..0e1a9682 100644 --- a/frontend/src/components/Modals/AssignmentModal.vue +++ b/frontend/src/components/Modals/AssignmentModal.vue @@ -27,6 +27,7 @@ value="" doctype="User" @change="(option) => addValue(option) && ($refs.input.value = '')" + :placeholder="__('John Doe')" :hideMe="true" > @@ -83,8 +84,18 @@ const props = defineProps({ type: Object, default: null, }, + docs: { + type: Array, + default: () => [], + }, + doctype: { + type: String, + default: '', + }, }) +const emit = defineEmits(['reload']) + const show = defineModel() const assignees = defineModel('assignees') const oldAssignees = ref([]) @@ -101,7 +112,7 @@ const removeValue = (value) => { const owner = computed(() => { if (!props.doc) return '' - if (props.doc.doctype == 'CRM Lead') return props.doc.lead_owner + if (props.doctype == 'CRM Lead') return props.doc.lead_owner return props.doc.deal_owner }) @@ -137,7 +148,7 @@ function updateAssignees() { if (removedAssignees.length) { for (let a of removedAssignees) { call('frappe.desk.form.assign_to.remove', { - doctype: props.doc.doctype, + doctype: props.doctype, name: props.doc.name, assign_to: a, }) @@ -145,11 +156,23 @@ function updateAssignees() { } if (addedAssignees.length) { - call('frappe.desk.form.assign_to.add', { - doctype: props.doc.doctype, - name: props.doc.name, - assign_to: addedAssignees, - }) + if (props.docs.size) { + call('frappe.desk.form.assign_to.add_multiple', { + doctype: props.doctype, + name: JSON.stringify(Array.from(props.docs)), + assign_to: addedAssignees, + bulk_assign: true, + re_assign: true, + }).then(() => { + emit('reload') + }) + } else { + call('frappe.desk.form.assign_to.add', { + doctype: props.doctype, + name: props.doc.name, + assign_to: addedAssignees, + }) + } } show.value = false } diff --git a/frontend/src/components/Modals/EditValueModal.vue b/frontend/src/components/Modals/EditValueModal.vue index 3d918d96..9f60a6fa 100644 --- a/frontend/src/components/Modals/EditValueModal.vue +++ b/frontend/src/components/Modals/EditValueModal.vue @@ -59,7 +59,6 @@ const props = defineProps({ }) const show = defineModel() -const unselectAll = defineModel('unselectAll') const emit = defineEmits(['reload']) @@ -114,7 +113,6 @@ function updateValues() { newValue.value = '' loading.value = false show.value = false - unselectAll.value() emit('reload') }) } @@ -130,6 +128,10 @@ function updateValue(v) { newValue.value = value } +function getSelectOptions(options) { + return options.split('\n') +} + function getValueComponent(f) { const { type, options } = f if (typeSelect.includes(type) || typeCheck.includes(type)) { @@ -140,6 +142,7 @@ function getValueComponent(f) { label: o, value: o, })), + modelValue: newValue.value, }) } else if (typeLink.includes(type)) { if (type == 'Dynamic Link') { diff --git a/frontend/src/components/WhatsAppBox.vue b/frontend/src/components/WhatsAppBox.vue index c3a1aa4c..76e88eac 100644 --- a/frontend/src/components/WhatsAppBox.vue +++ b/frontend/src/components/WhatsAppBox.vue @@ -67,7 +67,7 @@ import IconPicker from '@/components/IconPicker.vue' import SmileIcon from '@/components/Icons/SmileIcon.vue' import { createResource, Textarea, FileUploader, Dropdown } from 'frappe-ui' import FeatherIcon from 'frappe-ui/src/components/FeatherIcon.vue' -import { ref, computed, nextTick, watch } from 'vue' +import { ref, nextTick, watch } from 'vue' const props = defineProps({ doctype: String, diff --git a/frontend/src/pages/Deal.vue b/frontend/src/pages/Deal.vue index 3d849b09..9e352291 100644 --- a/frontend/src/pages/Deal.vue +++ b/frontend/src/pages/Deal.vue @@ -280,6 +280,7 @@ @@ -289,6 +290,7 @@ import Resizer from '@/components/Resizer.vue' import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue' import ActivityIcon from '@/components/Icons/ActivityIcon.vue' import EmailIcon from '@/components/Icons/EmailIcon.vue' +import CommentIcon from '@/components/Icons/CommentIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue' @@ -447,6 +449,11 @@ const tabs = computed(() => { label: __('Emails'), icon: EmailIcon, }, + { + name: 'Comments', + label: __('Comments'), + icon: CommentIcon, + }, { name: 'Calls', label: __('Calls'), diff --git a/frontend/src/pages/Lead.vue b/frontend/src/pages/Lead.vue index 5c983292..9eabfb6d 100644 --- a/frontend/src/pages/Lead.vue +++ b/frontend/src/pages/Lead.vue @@ -186,6 +186,7 @@ @@ -260,6 +261,7 @@ import Resizer from '@/components/Resizer.vue' import ActivityIcon from '@/components/Icons/ActivityIcon.vue' import EmailIcon from '@/components/Icons/EmailIcon.vue' +import CommentIcon from '@/components/Icons/CommentIcon.vue' import PhoneIcon from '@/components/Icons/PhoneIcon.vue' import TaskIcon from '@/components/Icons/TaskIcon.vue' import NoteIcon from '@/components/Icons/NoteIcon.vue' @@ -416,6 +418,11 @@ const tabs = computed(() => { label: __('Emails'), icon: EmailIcon, }, + { + name: 'Comments', + label: __('Comments'), + icon: CommentIcon, + }, { name: 'Calls', label: __('Calls'),