diff --git a/crm/api/doc.py b/crm/api/doc.py
index 8fc1f705..199c99b1 100644
--- a/crm/api/doc.py
+++ b/crm/api/doc.py
@@ -240,7 +240,8 @@ def get_list_data(
"views": get_views(doctype),
"total_count": len(frappe.get_list(doctype, filters=filters)),
"row_count": len(data),
- "form_script": get_form_script(doctype)
+ "form_script": get_form_script(doctype),
+ "list_script": get_form_script(doctype, "List"),
}
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.js b/crm/fcrm/doctype/crm_form_script/crm_form_script.js
index 7f6f150e..087714dc 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.js
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.js
@@ -1,8 +1,38 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-// frappe.ui.form.on("CRM Form Script", {
-// refresh(frm) {
+frappe.ui.form.on("CRM Form Script", {
+ refresh(frm) {
+ frm.set_query("dt", {
+ filters: {
+ istable: 0,
+ },
+ });
+ },
+ view(frm) {
+ let has_form_boilerplate = frm.doc.script.includes(
+ "function setupForm("
+ );
+ let has_list_boilerplate = frm.doc.script.includes(
+ "function setupList("
+ );
-// },
-// });
+ if (frm.doc.view == "Form" && !has_form_boilerplate) {
+ frm.doc.script = `
+function setupForm({ doc }) {
+ return {
+ actions: [],
+ }
+}`.trim();
+ }
+ if (frm.doc.view == "List" && !has_list_boilerplate) {
+ frm.doc.script = `
+function setupList({ docs }) {
+ return {
+ actions: [],
+ bulk_actions: [],
+ }
+}`.trim();
+ }
+ },
+});
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.json b/crm/fcrm/doctype/crm_form_script/crm_form_script.json
index b883e804..9a026098 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.json
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.json
@@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"dt",
+ "view",
"column_break_gboh",
"enabled",
"section_break_xeox",
@@ -36,16 +37,26 @@
"label": "Enabled"
},
{
- "default": "function setupForm({ doc }) {\n return {\n actions: []\n }\n}",
+ "default": "function setupForm({ doc }) {\n return {\n actions: [],\n }\n}",
+ "documentation_url": "https://docs.frappe.io/crm/custom-actions",
"fieldname": "script",
"fieldtype": "Code",
"label": "Script",
"options": "JS"
+ },
+ {
+ "default": "Form",
+ "fieldname": "view",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Apply To",
+ "options": "Form\nList",
+ "set_only_once": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-01-19 21:52:46.078626",
+ "modified": "2024-04-10 18:27:17.617602",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Form Script",
diff --git a/crm/fcrm/doctype/crm_form_script/crm_form_script.py b/crm/fcrm/doctype/crm_form_script/crm_form_script.py
index 32649d62..ff93ff4f 100644
--- a/crm/fcrm/doctype/crm_form_script/crm_form_script.py
+++ b/crm/fcrm/doctype/crm_form_script/crm_form_script.py
@@ -14,6 +14,7 @@ class CRMFormScript(Document):
if self.dt and self.enabled:
filters = {
"dt": self.dt,
+ "view": self.view,
"enabled": 1,
}
if self.name:
@@ -27,13 +28,14 @@ class CRMFormScript(Document):
frappe.DuplicateEntryError,
)
-def get_form_script(dt):
- """Returns the script for the given doctype"""
+def get_form_script(dt, view="Form"):
+ """Returns the form script for the given doctype"""
FormScript = frappe.qb.DocType("CRM Form Script")
query = (
frappe.qb.from_(FormScript)
.select("script")
.where(FormScript.dt == dt)
+ .where(FormScript.view == view)
.where(FormScript.enabled == 1)
.limit(1)
)
diff --git a/frontend/src/components/ListViews/CallLogsListView.vue b/frontend/src/components/ListViews/CallLogsListView.vue
index 5d2cbe80..45c34b74 100644
--- a/frontend/src/components/ListViews/CallLogsListView.vue
+++ b/frontend/src/components/ListViews/CallLogsListView.vue
@@ -103,7 +103,7 @@ import {
Dropdown,
call,
} from 'frappe-ui'
-import { setupBulkActions, createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import { globalStore } from '@/stores/global'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
@@ -181,6 +181,7 @@ function deleteValues(selections, unselectAll) {
}
const customBulkActions = ref([])
+const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
@@ -209,7 +210,18 @@ function bulkActions(selections, unselectAll) {
onMounted(() => {
if (!list.value?.data) return
- setupBulkActions(list.value.data)
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
})
diff --git a/frontend/src/components/ListViews/ContactsListView.vue b/frontend/src/components/ListViews/ContactsListView.vue
index 5a9781d7..64fc550b 100644
--- a/frontend/src/components/ListViews/ContactsListView.vue
+++ b/frontend/src/components/ListViews/ContactsListView.vue
@@ -103,7 +103,7 @@
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import EditValueModal from '@/components/Modals/EditValueModal.vue'
import { globalStore } from '@/stores/global'
-import { createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import {
Avatar,
ListView,
@@ -117,7 +117,8 @@ import {
Dropdown,
call,
} from 'frappe-ui'
-import { ref, watch } from 'vue'
+import { ref, watch, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
const props = defineProps({
rows: {
@@ -150,6 +151,8 @@ const emit = defineEmits([
const pageLengthCount = defineModel()
const list = defineModel('list')
+const router = useRouter()
+
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => {
@@ -199,6 +202,8 @@ function deleteValues(selections, unselectAll) {
})
}
+const customListActions = ref([])
+
function bulkActions(selections, unselectAll) {
let actions = [
{
@@ -212,4 +217,21 @@ function bulkActions(selections, unselectAll) {
]
return actions
}
+
+onMounted(() => {
+ if (!list.value?.data) return
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
+ // customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
+})
diff --git a/frontend/src/components/ListViews/DealsListView.vue b/frontend/src/components/ListViews/DealsListView.vue
index e4988649..57a93e57 100644
--- a/frontend/src/components/ListViews/DealsListView.vue
+++ b/frontend/src/components/ListViews/DealsListView.vue
@@ -146,7 +146,7 @@ import {
call,
Tooltip,
} from 'frappe-ui'
-import { setupBulkActions, createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import { globalStore } from '@/stores/global'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
@@ -234,6 +234,7 @@ function deleteValues(selections, unselectAll) {
}
const customBulkActions = ref([])
+const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
@@ -266,7 +267,18 @@ function bulkActions(selections, unselectAll) {
onMounted(() => {
if (!list.value?.data) return
- setupBulkActions(list.value.data)
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
})
diff --git a/frontend/src/components/ListViews/EmailTemplatesListView.vue b/frontend/src/components/ListViews/EmailTemplatesListView.vue
index 34afca15..b260d0e3 100644
--- a/frontend/src/components/ListViews/EmailTemplatesListView.vue
+++ b/frontend/src/components/ListViews/EmailTemplatesListView.vue
@@ -85,7 +85,7 @@
diff --git a/frontend/src/components/ListViews/LeadsListView.vue b/frontend/src/components/ListViews/LeadsListView.vue
index 1483b574..a78a62ec 100644
--- a/frontend/src/components/ListViews/LeadsListView.vue
+++ b/frontend/src/components/ListViews/LeadsListView.vue
@@ -155,7 +155,7 @@ import {
call,
Tooltip,
} from 'frappe-ui'
-import { setupBulkActions, createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import { globalStore } from '@/stores/global'
import { onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
@@ -243,6 +243,7 @@ function deleteValues(selections, unselectAll) {
}
const customBulkActions = ref([])
+const customListActions = ref([])
function bulkActions(selections, unselectAll) {
let actions = [
@@ -275,7 +276,18 @@ function bulkActions(selections, unselectAll) {
onMounted(() => {
if (!list.value?.data) return
- setupBulkActions(list.value.data)
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
})
diff --git a/frontend/src/components/ListViews/OrganizationsListView.vue b/frontend/src/components/ListViews/OrganizationsListView.vue
index cd8d677d..059f661b 100644
--- a/frontend/src/components/ListViews/OrganizationsListView.vue
+++ b/frontend/src/components/ListViews/OrganizationsListView.vue
@@ -88,7 +88,7 @@
diff --git a/frontend/src/components/ListViews/TasksListView.vue b/frontend/src/components/ListViews/TasksListView.vue
index ab6b21a1..33898cf3 100644
--- a/frontend/src/components/ListViews/TasksListView.vue
+++ b/frontend/src/components/ListViews/TasksListView.vue
@@ -106,7 +106,7 @@ import CalendarIcon from '@/components/Icons/CalendarIcon.vue'
import EditValueModal from '@/components/Modals/EditValueModal.vue'
import { dateFormat } from '@/utils'
import { globalStore } from '@/stores/global'
-import { createToast } from '@/utils'
+import { setupListActions, createToast } from '@/utils'
import {
Avatar,
ListView,
@@ -120,7 +120,8 @@ import {
call,
Tooltip,
} from 'frappe-ui'
-import { ref, watch } from 'vue'
+import { ref, watch, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
const props = defineProps({
rows: {
@@ -154,6 +155,8 @@ const emit = defineEmits([
const pageLengthCount = defineModel()
const list = defineModel('list')
+const router = useRouter()
+
const { $dialog } = globalStore()
watch(pageLengthCount, (val, old_value) => {
@@ -203,6 +206,8 @@ function deleteValues(selections, unselectAll) {
})
}
+const customListActions = ref([])
+
function bulkActions(selections, unselectAll) {
let actions = [
{
@@ -216,4 +221,21 @@ function bulkActions(selections, unselectAll) {
]
return actions
}
+
+onMounted(() => {
+ if (!list.value?.data) return
+ setupListActions(list.value.data, {
+ list: list.value,
+ call,
+ createToast,
+ $dialog,
+ router,
+ })
+ // customBulkActions.value = list.value?.data?.bulkActions || []
+ customListActions.value = list.value?.data?.listActions || []
+})
+
+defineExpose({
+ customListActions,
+})
diff --git a/frontend/src/pages/CallLogs.vue b/frontend/src/pages/CallLogs.vue
index 1c6b4ace..7d200827 100644
--- a/frontend/src/pages/CallLogs.vue
+++ b/frontend/src/pages/CallLogs.vue
@@ -3,6 +3,12 @@
+
+
+