Merge pull request #349 from frappe/develop

chore: Merge develop to main
This commit is contained in:
Shariq Ansari 2024-09-13 18:08:21 +05:30 committed by GitHub
commit 014a550e88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 443 additions and 37 deletions

View File

@ -682,6 +682,9 @@ def get_fields(doctype: str, allow_all_fieldtypes: bool = False):
"mandatory": field.reqd,
"read_only": field.read_only,
"hidden": field.hidden,
"depends_on": field.depends_on,
"mandatory_depends_on": field.mandatory_depends_on,
"read_only_depends_on": field.read_only_depends_on,
})
return _fields

View File

@ -8,7 +8,29 @@ frappe.ui.form.on("CRM Form Script", {
istable: 0,
},
});
if (frm.doc.is_standard && !frappe.boot.developer_mode) {
frm.disable_form();
frappe.show_alert(
__(
"Standard Form Scripts can not be modified, duplicate the Form Script instead."
)
);
}
frm.trigger("add_enable_button");
},
add_enable_button(frm) {
frm.add_custom_button(
frm.doc.enabled ? __("Disable") : __("Enable"),
() => {
frm.set_value("enabled", !frm.doc.enabled);
frm.save();
}
);
},
view(frm) {
let has_form_boilerplate = frm.doc.script.includes(
"function setupForm("

View File

@ -10,6 +10,7 @@
"view",
"column_break_gboh",
"enabled",
"is_standard",
"section_break_xeox",
"script"
],
@ -52,11 +53,18 @@
"label": "Apply To",
"options": "Form\nList",
"set_only_once": 1
},
{
"default": "0",
"fieldname": "is_standard",
"fieldtype": "Check",
"label": "Is Standard",
"no_copy": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-04-10 18:27:17.617602",
"modified": "2024-09-11 12:56:09.288849",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Form Script",

View File

@ -2,11 +2,26 @@
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
class CRMFormScript(Document):
pass
def validate(self):
in_user_env = not (
frappe.flags.in_install
or frappe.flags.in_patch
or frappe.flags.in_test
or frappe.flags.in_fixtures
)
if in_user_env and self.is_standard and not frappe.conf.developer_mode:
# only enabled can be changed for standard form scripts
if self.has_value_changed("enabled"):
enabled_value = self.enabled
self.reload()
self.enabled = enabled_value
else:
frappe.throw(_("You need to be in developer mode to edit a Standard Form Script"))
def get_form_script(dt, view="Form"):
"""Returns the form script for the given doctype"""

View File

@ -15,7 +15,8 @@
"column_break_pnpp",
"website",
"territory",
"industry"
"industry",
"address"
],
"fields": [
{
@ -68,12 +69,18 @@
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
},
{
"fieldname": "address",
"fieldtype": "Link",
"label": "Address",
"options": "Address"
}
],
"image_field": "organization_logo",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-06-20 12:59:55.297752",
"modified": "2024-09-13 15:52:05.106389",
"modified_by": "Administrator",
"module": "FCRM",
"name": "CRM Organization",

View File

@ -123,11 +123,15 @@ def add_default_fields_layout():
},
"Contact-Quick Entry": {
"doctype": "Contact",
"layout": '[{"label":"Salutation","columns":1,"fields":["salutation"],"hideLabel":true},{"label":"Full Name","columns":2,"hideBorder":true,"fields":["first_name","last_name"],"hideLabel":true},{"label":"Email","columns":1,"hideBorder":true,"fields":["email_id"],"hideLabel":true},{"label":"Mobile No. & Gender","columns":2,"hideBorder":true,"fields":["mobile_no","gender"],"hideLabel":true},{"label":"Organization","columns":1,"hideBorder":true,"fields":["company_name"],"hideLabel":true},{"label":"Designation","columns":1,"hideBorder":true,"fields":["designation"],"hideLabel":true}]'
"layout": '[{"label":"Salutation","columns":1,"fields":["salutation"],"hideLabel":true},{"label":"Full Name","columns":2,"hideBorder":true,"fields":["first_name","last_name"],"hideLabel":true},{"label":"Email","columns":1,"hideBorder":true,"fields":["email_id"],"hideLabel":true},{"label":"Mobile No. & Gender","columns":2,"hideBorder":true,"fields":["mobile_no","gender"],"hideLabel":true},{"label":"Organization","columns":1,"hideBorder":true,"fields":["company_name"],"hideLabel":true},{"label":"Designation","columns":1,"hideBorder":true,"fields":["designation"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]'
},
"CRM Organization-Quick Entry": {
"doctype": "CRM Organization",
"layout": '[{"label":"Organization Name","columns":1,"fields":["organization_name"],"hideLabel":true},{"label":"Website & Revenue","columns":2,"hideBorder":true,"fields":["website","annual_revenue"],"hideLabel":true},{"label":"Territory","columns":1,"hideBorder":true,"fields":["territory"],"hideLabel":true},{"label":"No of Employees & Industry","columns":2,"hideBorder":true,"fields":["no_of_employees","industry"],"hideLabel":true}]'
"layout": '[{"label":"Organization Name","columns":1,"fields":["organization_name"],"hideLabel":true},{"label":"Website & Revenue","columns":2,"hideBorder":true,"fields":["website","annual_revenue"],"hideLabel":true},{"label":"Territory","columns":1,"hideBorder":true,"fields":["territory"],"hideLabel":true},{"label":"No of Employees & Industry","columns":2,"hideBorder":true,"fields":["no_of_employees","industry"],"hideLabel":true},{"label":"Address","columns":1,"hideBorder":true,"fields":["address"],"hideLabel":true}]'
},
"Address-Quick Entry": {
"doctype": "Address",
"layout": '[{"label":"Address","columns":1,"fields":["address_title","address_type","address_line1","address_line2","city","state","country","pincode"],"hideLabel":true}]'
},
}

View File

@ -6,6 +6,6 @@ crm.patches.v1_0.move_crm_note_data_to_fcrm_note
[post_model_sync]
# Patches added in this section will be executed after doctypes are migrated
crm.patches.v1_0.create_email_template_custom_fields
crm.patches.v1_0.create_default_fields_layout
crm.patches.v1_0.create_default_fields_layout #13/09/2024
crm.patches.v1_0.create_default_sidebar_fields_layout
crm.patches.v1_0.update_deal_quick_entry_layout

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -23,9 +23,11 @@
<div v-for="field in section.fields" :key="field.name">
<div
v-if="
field.type == 'Check' ||
(field.read_only && data[field.name]) ||
!field.read_only || !field.hidden
(field.type == 'Check' ||
(field.read_only && data[field.name]) ||
!field.read_only ||
!field.hidden) &&
(!field.depends_on || field.display_depends_on)
"
>
<div
@ -74,15 +76,27 @@
<span class="text-red-500" v-if="field.mandatory">*</span>
</label>
</div>
<Link
v-else-if="field.type === 'Link'"
class="form-control"
:value="data[field.name]"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder || field.label)"
:onCreate="field.create"
/>
<div class="flex gap-1" v-else-if="field.type === 'Link'">
<Link
class="form-control flex-1"
:value="data[field.name]"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder || field.label)"
:onCreate="field.create"
/>
<Button
v-if="data[field.name] && field.edit"
class="shrink-0"
:label="__('Edit')"
@click="field.edit(data[field.name])"
>
<template #prefix>
<EditIcon class="h-4 w-4" />
</template>
</Button>
</div>
<Link
v-else-if="field.type === 'User'"
class="form-control"
@ -196,6 +210,7 @@
</template>
<script setup>
import EditIcon from '@/components/Icons/EditIcon.vue'
import NestedPopover from '@/components/NestedPopover.vue'
import DropdownItem from '@/components/DropdownItem.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'

View File

@ -1,18 +1,18 @@
<template>
<svg
width="118"
height="118"
viewBox="0 0 118 118"
width="300"
height="300"
viewBox="0 0 300 300"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M93.7101 0H23.8141C10.7986 0 0.247559 10.5511 0.247559 23.5665V93.4626C0.247559 106.478 10.7986 117.029 23.8141 117.029H93.7101C106.726 117.029 117.277 106.478 117.277 93.4626V23.5665C117.277 10.5511 106.726 0 93.7101 0Z"
fill="#DB4EE0"
d="M214.286 0H85.7143C38.3756 0 0 38.3756 0 85.7143V214.286C0 261.624 38.3756 300 85.7143 300H214.286C261.624 300 300 261.624 300 214.286V85.7143C300 38.3756 261.624 0 214.286 0Z"
fill="#EF0BF5"
/>
<path
d="M21.2487 27.5115V38.0121H85.7749V45.8875L62.5161 68.4638V80.9999H54.9556V68.4638C54.9556 68.4638 41.1474 55.0755 36.3171 50.3503H21.3013L42.6174 71.0889C43.825 72.244 44.5076 73.8716 44.5076 75.5517V87.9424L73.0167 88.0474V75.5517C73.0167 73.8716 73.6992 72.244 74.9068 71.0889L96.2755 50.2978V27.5115H21.2487Z"
fill="#F1FCFF"
d="M64.2858 90.9642V112.393H214.286V140.571L160.179 193.178V208.929L139.714 208.821V193.178L85.7143 140.571H64.2858V149.571L118.286 202.179V230.035L181.607 230.571V202.179L235.714 149.571V90.9642H64.2858Z"
fill="white"
/>
</svg>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div class="flex overflow-x-auto">
<div class="flex overflow-x-auto h-full">
<Draggable
v-if="columns"
:list="columns"

View File

@ -0,0 +1,206 @@
<template>
<Dialog v-model="show" :options="dialogOptions">
<template #body>
<div class="bg-white px-4 pb-6 pt-5 sm:px-6">
<div class="mb-5 flex items-center justify-between">
<div>
<h3 class="text-2xl font-semibold leading-6 text-gray-900">
{{ __(dialogOptions.title) || __('Untitled') }}
</h3>
</div>
<div class="flex items-center gap-1">
<Button
v-if="isManager()"
variant="ghost"
class="w-7"
@click="openQuickEntryModal"
>
<EditIcon class="h-4 w-4" />
</Button>
<Button variant="ghost" class="w-7" @click="show = false">
<FeatherIcon name="x" class="h-4 w-4" />
</Button>
</div>
</div>
<div v-if="sections.data">
<Fields :sections="sections.data" :data="_address" />
<ErrorMessage class="mt-2" :message="error" />
</div>
</div>
<div class="px-4 pb-7 pt-4 sm:px-6">
<div class="space-y-2">
<Button
class="w-full"
v-for="action in dialogOptions.actions"
:key="action.label"
v-bind="action"
:label="__(action.label)"
:loading="loading"
/>
</div>
</div>
</template>
</Dialog>
<QuickEntryModal
v-if="showQuickEntryModal"
v-model="showQuickEntryModal"
doctype="Address"
/>
</template>
<script setup>
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import Fields from '@/components/Fields.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import { usersStore } from '@/stores/users'
import { capture } from '@/telemetry'
import { call, FeatherIcon, createResource, ErrorMessage } from 'frappe-ui'
import { ref, nextTick, watch, computed } from 'vue'
const props = defineProps({
options: {
type: Object,
default: {
afterInsert: () => {},
},
},
})
const { isManager } = usersStore()
const show = defineModel()
const address = defineModel('address')
const loading = ref(false)
const error = ref(null)
const title = ref(null)
const editMode = ref(false)
let _address = ref({
name: '',
address_title: '',
address_type: 'Billing',
address_line1: '',
address_line2: '',
city: '',
county: '',
state: '',
country: '',
pincode: '',
})
const dialogOptions = computed(() => {
let title = !editMode.value
? __('New Address')
: __(_address.value.address_title)
let size = 'xl'
let actions = [
{
label: editMode.value ? __('Save') : __('Create'),
variant: 'solid',
onClick: () =>
editMode.value ? updateAddress() : createAddress.submit(),
},
]
return { title, size, actions }
})
const sections = createResource({
url: 'crm.fcrm.doctype.crm_fields_layout.crm_fields_layout.get_fields_layout',
cache: ['quickEntryFields', 'Address'],
params: { doctype: 'Address', type: 'Quick Entry' },
auto: true,
})
let doc = ref({})
function updateAddress() {
error.value = null
const old = { ...doc.value }
const newAddress = { ..._address.value }
const dirty = JSON.stringify(old) !== JSON.stringify(newAddress)
const values = newAddress
if (!dirty) {
show.value = false
return
}
loading.value = true
updateAddressValues.submit({
doctype: 'Address',
name: _address.value.name,
fieldname: values,
})
}
const updateAddressValues = createResource({
url: 'frappe.client.set_value',
onSuccess(doc) {
loading.value = false
if (doc.name) {
handleAddressUpdate(doc)
}
},
onError(err) {
loading.value = false
error.value = err
},
})
const createAddress = createResource({
url: 'frappe.client.insert',
makeParams() {
return {
doc: {
doctype: 'Address',
..._address.value,
},
}
},
onSuccess(doc) {
loading.value = false
if (doc.name) {
capture('address_created')
handleAddressUpdate(doc)
}
},
onError(err) {
loading.value = false
error.value = err
},
})
function handleAddressUpdate(doc) {
show.value = false
props.options.afterInsert && props.options.afterInsert(doc)
}
watch(
() => show.value,
(value) => {
if (!value) return
editMode.value = false
nextTick(() => {
// TODO: Issue with FormControl
// title.value.el.focus()
doc.value = address.value?.doc || address.value || {}
_address.value = { ...doc.value }
if (_address.value.name) {
editMode.value = true
}
})
},
)
const showQuickEntryModal = ref(false)
function openQuickEntryModal() {
showQuickEntryModal.value = true
nextTick(() => {
show.value = false
})
}
</script>

View File

@ -78,10 +78,12 @@
</div>
</template>
</Dialog>
<AddressModal v-model="showAddressModal" v-model:address="_address" />
</template>
<script setup>
import Fields from '@/components/Fields.vue'
import AddressModal from '@/components/Modals/AddressModal.vue'
import ContactIcon from '@/components/Icons/ContactIcon.vue'
import GenderIcon from '@/components/Icons/GenderIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue'
@ -121,6 +123,9 @@ const show = defineModel()
const detailMode = ref(false)
const editMode = ref(false)
let _contact = ref({})
let _address = ref({})
const showAddressModal = ref(false)
async function updateContact() {
if (!dirty.value) {
@ -357,6 +362,20 @@ const filteredSections = computed(() => {
isNew: true,
})
}
} else if (field.name == 'address') {
field.create = (value, close) => {
_contact.value.address = value
_address.value = {}
showAddressModal.value = true
close()
}
field.edit = async (addr) => {
_address.value = await call('frappe.client.get', {
doctype: 'Address',
name: addr,
})
showAddressModal.value = true
}
}
})
})

View File

@ -36,8 +36,8 @@
</div>
</div>
<Fields
v-else-if="sections.data"
:sections="sections.data"
v-else-if="filteredSections"
:sections="filteredSections"
:data="_organization"
/>
</div>
@ -56,10 +56,12 @@
</div>
</template>
</Dialog>
<AddressModal v-model="showAddressModal" v-model:address="_address" />
</template>
<script setup>
import Fields from '@/components/Fields.vue'
import AddressModal from '@/components/Modals/AddressModal.vue'
import EditIcon from '@/components/Icons/EditIcon.vue'
import MoneyIcon from '@/components/Icons/MoneyIcon.vue'
import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue'
@ -93,6 +95,7 @@ const loading = ref(false)
const title = ref(null)
const detailMode = ref(false)
const editMode = ref(false)
let _address = ref({})
let _organization = ref({
organization_name: '',
website: '',
@ -101,6 +104,8 @@ let _organization = ref({
industry: '',
})
const showAddressModal = ref(false)
let doc = ref({})
async function updateOrganization() {
@ -243,6 +248,33 @@ const sections = createResource({
auto: true,
})
const filteredSections = computed(() => {
let allSections = sections.data || []
if (!allSections.length) return []
allSections.forEach((s) => {
s.fields.forEach((field) => {
if (field.name == 'address') {
field.create = (value, close) => {
_organization.value.address = value
_address.value = {}
showAddressModal.value = true
close()
}
field.edit = async (addr) => {
_address.value = await call('frappe.client.get', {
doctype: 'Address',
name: addr,
})
showAddressModal.value = true
}
}
})
})
return allSections
})
watch(
() => show.value,
(value) => {

View File

@ -20,7 +20,13 @@
type="select"
class="w-1/4"
v-model="_doctype"
:options="['CRM Lead', 'CRM Deal', 'Contact', 'CRM Organization']"
:options="[
'CRM Lead',
'CRM Deal',
'Contact',
'CRM Organization',
'Address',
]"
@change="reload"
/>
<Switch
@ -54,7 +60,7 @@
</template>
<script setup>
import Fields from '@/components/Fields.vue'
import QuickEntryLayoutBuilder from '@/components/Settings/QuickEntryLayoutBuilder.vue'
import QuickEntryLayoutBuilder from '@/components/QuickEntryLayoutBuilder.vue'
import { useDebounceFn } from '@vueuse/core'
import { capture } from '@/telemetry'
import { Dialog, Badge, Switch, call, createResource } from 'frappe-ui'

View File

@ -37,6 +37,7 @@ import {
Spinner,
Badge,
} from 'frappe-ui'
import { evaluate_depends_on_value, createToast } from '@/utils'
import { computed } from 'vue'
const props = defineProps({
@ -44,6 +45,10 @@ const props = defineProps({
type: String,
required: true,
},
successMessage: {
type: String,
default: 'Updated Successfully',
},
})
const fields = createResource({
@ -62,6 +67,24 @@ const data = createDocumentResource({
fields: ['*'],
cache: props.doctype,
auto: true,
setValue: {
onSuccess: () => {
createToast({
title: __('Success'),
text: __(props.successMessage),
icon: 'check',
iconClasses: 'text-green-600',
})
},
onError: (err) => {
createToast({
title: __('Error'),
text: err.message + ': ' + err.messages[0],
icon: 'x',
iconClasses: 'text-red-600',
})
},
},
})
const sections = computed(() => {
@ -90,6 +113,10 @@ const sections = computed(() => {
} else {
_sections[_sections.length - 1].fields.push({
...field,
display_depends_on: evaluate_depends_on_value(
field.depends_on,
data.doc,
),
name: field.value,
})
}

View File

@ -230,7 +230,7 @@ import CameraIcon from '@/components/Icons/CameraIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue'
import ContactModal from '@/components/Modals/ContactModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import {
dateFormat,
dateTooltipFormat,

View File

@ -77,7 +77,7 @@ import CustomActions from '@/components/CustomActions.vue'
import ContactsIcon from '@/components/Icons/ContactsIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import ContactModal from '@/components/Modals/ContactModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import ContactsListView from '@/components/ListViews/ContactsListView.vue'
import ViewControls from '@/components/ViewControls.vue'
import { organizationsStore } from '@/stores/organizations.js'

View File

@ -279,7 +279,7 @@ import KanbanView from '@/components/Kanban/KanbanView.vue'
import DealModal from '@/components/Modals/DealModal.vue'
import NoteModal from '@/components/Modals/NoteModal.vue'
import TaskModal from '@/components/Modals/TaskModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import ViewControls from '@/components/ViewControls.vue'
import { globalStore } from '@/stores/global'
import { usersStore } from '@/stores/users'

View File

@ -301,7 +301,7 @@ import KanbanView from '@/components/Kanban/KanbanView.vue'
import LeadModal from '@/components/Modals/LeadModal.vue'
import NoteModal from '@/components/Modals/NoteModal.vue'
import TaskModal from '@/components/Modals/TaskModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import ViewControls from '@/components/ViewControls.vue'
import { globalStore } from '@/stores/global'
import { usersStore } from '@/stores/users'

View File

@ -233,7 +233,7 @@
import Icon from '@/components/Icon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue'
import ContactsListView from '@/components/ListViews/ContactsListView.vue'
import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue'

View File

@ -75,7 +75,7 @@ import CustomActions from '@/components/CustomActions.vue'
import OrganizationsIcon from '@/components/Icons/OrganizationsIcon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import QuickEntryModal from '@/components/Settings/QuickEntryModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import OrganizationsListView from '@/components/ListViews/OrganizationsListView.vue'
import ViewControls from '@/components/ViewControls.vue'
import {

View File

@ -241,3 +241,45 @@ export function isTouchScreenDevice() {
export function convertArrayToString(array) {
return array.map((item) => item).join(',')
}
export function _eval(code, context = {}) {
let variable_names = Object.keys(context)
let variables = Object.values(context)
code = `let out = ${code}; return out`
try {
let expression_function = new Function(...variable_names, code)
return expression_function(...variables)
} catch (error) {
console.log('Error evaluating the following expression:')
console.error(code)
throw error
}
}
export function evaluate_depends_on_value(expression, doc) {
if (!expression) return true
if (!doc) return true
let out = null
if (typeof expression === 'boolean') {
out = expression
} else if (typeof expression === 'function') {
out = expression(doc)
} else if (expression.substr(0, 5) == 'eval:') {
try {
out = _eval(expression.substr(5), { doc })
} catch (e) {
out = true
}
} else {
let value = doc[expression]
if (Array.isArray(value)) {
out = !!value.length
} else {
out = !!value
}
}
return out
}