Compare commits

..

No commits in common. "main" and "v1.53.0" have entirely different histories.

35 changed files with 6325 additions and 8112 deletions

View File

@ -1,4 +1,4 @@
__version__ = "1.53.1" __version__ = "1.53.0"
__title__ = "Frappe CRM" __title__ = "Frappe CRM"

View File

@ -750,11 +750,7 @@ def getCounts(d, doctype):
@frappe.whitelist() @frappe.whitelist()
def get_linked_docs_of_document(doctype, docname): def get_linked_docs_of_document(doctype, docname):
try: doc = frappe.get_doc(doctype, docname)
doc = frappe.get_doc(doctype, docname)
except frappe.DoesNotExistError:
return []
linked_docs = get_linked_docs(doc) linked_docs = get_linked_docs(doc)
dynamic_linked_docs = get_dynamic_linked_docs(doc) dynamic_linked_docs = get_dynamic_linked_docs(doc)
@ -763,14 +759,7 @@ def get_linked_docs_of_document(doctype, docname):
docs_data = [] docs_data = []
for doc in linked_docs: for doc in linked_docs:
if not doc.get("reference_doctype") or not doc.get("reference_docname"): data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"])
continue
try:
data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"])
except (frappe.DoesNotExistError, frappe.ValidationError):
continue
title = data.get("title") title = data.get("title")
if data.doctype == "CRM Call Log": if data.doctype == "CRM Call Log":
title = f"Call from {data.get('from')} to {data.get('to')}" title = f"Call from {data.get('from')} to {data.get('to')}"
@ -778,9 +767,6 @@ def get_linked_docs_of_document(doctype, docname):
if data.doctype == "CRM Deal": if data.doctype == "CRM Deal":
title = data.get("organization") title = data.get("organization")
if data.doctype == "CRM Notification":
title = data.get("message")
docs_data.append( docs_data.append(
{ {
"doc": data.doctype, "doc": data.doctype,
@ -793,51 +779,25 @@ def get_linked_docs_of_document(doctype, docname):
def remove_doc_link(doctype, docname): def remove_doc_link(doctype, docname):
if not doctype or not docname: linked_doc_data = frappe.get_doc(doctype, docname)
return linked_doc_data.update(
{
try: "reference_doctype": None,
linked_doc_data = frappe.get_doc(doctype, docname) "reference_docname": None,
if doctype == "CRM Notification": }
delete_notification_type = { )
"notification_type_doctype": "", linked_doc_data.save(ignore_permissions=True)
"notification_type_doc": "",
}
delete_references = {
"reference_doctype": "",
"reference_name": "",
}
if linked_doc_data.get("notification_type_doctype") == linked_doc_data.get("reference_doctype"):
delete_references.update(delete_notification_type)
linked_doc_data.update(delete_references)
else:
linked_doc_data.update(
{
"reference_doctype": "",
"reference_docname": "",
}
)
linked_doc_data.save(ignore_permissions=True)
except (frappe.DoesNotExistError, frappe.ValidationError):
pass
def remove_contact_link(doctype, docname): def remove_contact_link(doctype, docname):
if not doctype or not docname: linked_doc_data = frappe.get_doc(doctype, docname)
return linked_doc_data.update(
{
try: "contact": None,
linked_doc_data = frappe.get_doc(doctype, docname) "contacts": [],
linked_doc_data.update( }
{ )
"contact": None, linked_doc_data.save(ignore_permissions=True)
"contacts": [],
}
)
linked_doc_data.save(ignore_permissions=True)
except (frappe.DoesNotExistError, frappe.ValidationError):
pass
@frappe.whitelist() @frappe.whitelist()
@ -846,19 +806,13 @@ def remove_linked_doc_reference(items, remove_contact=None, delete=False):
items = frappe.parse_json(items) items = frappe.parse_json(items)
for item in items: for item in items:
if not item.get("doctype") or not item.get("docname"): if remove_contact:
continue remove_contact_link(item["doctype"], item["docname"])
else:
remove_doc_link(item["doctype"], item["docname"])
try: if delete:
if remove_contact: frappe.delete_doc(item["doctype"], item["docname"])
remove_contact_link(item["doctype"], item["docname"])
else:
remove_doc_link(item["doctype"], item["docname"])
if delete:
frappe.delete_doc(item["doctype"], item["docname"])
except (frappe.DoesNotExistError, frappe.ValidationError):
# Skip if document doesn't exist or has validation errors
continue
return "success" return "success"
@ -867,40 +821,19 @@ def remove_linked_doc_reference(items, remove_contact=None, delete=False):
def delete_bulk_docs(doctype, items, delete_linked=False): def delete_bulk_docs(doctype, items, delete_linked=False):
from frappe.desk.reportview import delete_bulk from frappe.desk.reportview import delete_bulk
if not doctype:
frappe.throw("Doctype is required")
if not items:
frappe.throw("Items are required")
items = frappe.parse_json(items) items = frappe.parse_json(items)
if not isinstance(items, list):
frappe.throw("Items must be a list")
for doc in items: for doc in items:
try: linked_docs = get_linked_docs_of_document(doctype, doc)
if not frappe.db.exists(doctype, doc): for linked_doc in linked_docs:
frappe.log_error(f"Document {doctype} {doc} does not exist", "Bulk Delete Error") remove_linked_doc_reference(
continue [
{
linked_docs = get_linked_docs_of_document(doctype, doc) "doctype": linked_doc["reference_doctype"],
for linked_doc in linked_docs: "docname": linked_doc["reference_docname"],
if not linked_doc.get("reference_doctype") or not linked_doc.get("reference_docname"): }
continue ],
remove_contact=doctype == "Contact",
remove_linked_doc_reference( delete=delete_linked,
[
{
"doctype": linked_doc["reference_doctype"],
"docname": linked_doc["reference_docname"],
}
],
remove_contact=doctype == "Contact",
delete=delete_linked,
)
except Exception as e:
frappe.log_error(
f"Error processing linked docs for {doctype} {doc}: {str(e)}", "Bulk Delete Error"
) )
if len(items) > 10: if len(items) > 10:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
"@tiptap/extension-paragraph": "^2.12.0", "@tiptap/extension-paragraph": "^2.12.0",
"@twilio/voice-sdk": "^2.10.2", "@twilio/voice-sdk": "^2.10.2",
"@vueuse/integrations": "^10.3.0", "@vueuse/integrations": "^10.3.0",
"frappe-ui": "^0.1.201", "frappe-ui": "^0.1.200",
"gemoji": "^8.1.0", "gemoji": "^8.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mime": "^4.0.1", "mime": "^4.0.1",

View File

@ -13,7 +13,7 @@
</div> </div>
</div> </div>
<div> <div>
<div class="text-ink-gray-5 text-base"> <div class="text-ink-gray-5">
{{ {{
__('Are you sure you want to delete {0} items?', [ __('Are you sure you want to delete {0} items?', [
props.items?.length, props.items?.length,
@ -53,7 +53,7 @@
</div> </div>
</div> </div>
<div> <div>
<div class="text-ink-gray-5 text-base"> <div class="text-ink-gray-5">
{{ {{
confirmDeleteInfo.delete confirmDeleteInfo.delete
? __( ? __(

View File

@ -2,7 +2,7 @@
<Dialog v-model="show" :options="{ size: 'xl' }"> <Dialog v-model="show" :options="{ size: 'xl' }">
<template #body v-if="!confirmDeleteInfo.show"> <template #body v-if="!confirmDeleteInfo.show">
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6"> <div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
<div class="mb-6 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<div> <div>
<h3 class="text-2xl leading-6 text-ink-gray-9 font-semibold"> <h3 class="text-2xl leading-6 text-ink-gray-9 font-semibold">
{{ {{
@ -32,12 +32,11 @@
{ {
label: 'Document', label: 'Document',
key: 'title', key: 'title',
width: '19rem',
}, },
{ {
label: 'Master', label: 'Master',
key: 'reference_doctype', key: 'reference_doctype',
width: '12rem', width: '30%',
}, },
]" ]"
@selectionsChanged=" @selectionsChanged="

View File

@ -26,14 +26,13 @@
<ListRowItem <ListRowItem
:item="item" :item="item"
@click="listViewRef.toggleRow(row['reference_docname'])" @click="listViewRef.toggleRow(row['reference_docname'])"
class="!w-full"
> >
<template #default="{ label }"> <template #default="{ label }">
<div <div
v-if="column.key === 'title'" v-if="column.key === 'title'"
class="truncate text-base flex gap-2 w-full" class="truncate text-base flex gap-2"
> >
<span class="max-w-[90%] truncate"> <span>
{{ label }} {{ label }}
</span> </span>
<FeatherIcon <FeatherIcon
@ -103,7 +102,6 @@ const listViewRef = ref(null)
const viewLinkedDoc = (doc) => { const viewLinkedDoc = (doc) => {
let page = '' let page = ''
let id = '' let id = ''
let openDesk = false
switch (doc.reference_doctype) { switch (doc.reference_doctype) {
case 'CRM Lead': case 'CRM Lead':
page = 'leads' page = 'leads'
@ -125,11 +123,6 @@ const viewLinkedDoc = (doc) => {
page = 'organizations' page = 'organizations'
id = doc.reference_docname id = doc.reference_docname
break break
case 'CRM Notification':
page = 'crm-notification'
id = doc.reference_docname
openDesk = true
break
case 'FCRM Note': case 'FCRM Note':
page = 'notes' page = 'notes'
id = `view?open=${doc.reference_docname}` id = `view?open=${doc.reference_docname}`
@ -137,11 +130,7 @@ const viewLinkedDoc = (doc) => {
default: default:
break break
} }
let base = '/crm' window.open(`/crm/${page}/${id}`)
if (openDesk) {
base = '/app'
}
window.open(`${base}/${page}/${id}`)
} }
const getDoctypeName = (doctype) => { const getDoctypeName = (doctype) => {

View File

@ -1,8 +1,7 @@
import { getScript } from '@/data/script' import { getScript } from '@/data/script'
import { globalStore } from '@/stores/global' import { globalStore } from '@/stores/global'
import { getMeta } from '@/stores/meta'
import { showSettings, activeSettingsPage } from '@/composables/settings' import { showSettings, activeSettingsPage } from '@/composables/settings'
import { runSequentially, parseAssignees, evaluateExpression } from '@/utils' import { runSequentially, parseAssignees } from '@/utils'
import { createDocumentResource, createResource, toast } from 'frappe-ui' import { createDocumentResource, createResource, toast } from 'frappe-ui'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
@ -12,7 +11,6 @@ const assigneesCache = {}
export function useDocument(doctype, docname) { export function useDocument(doctype, docname) {
const { setupScript, scripts } = getScript(doctype) const { setupScript, scripts } = getScript(doctype)
const meta = getMeta(doctype)
documentsCache[doctype] = documentsCache[doctype] || {} documentsCache[doctype] = documentsCache[doctype] || {}
@ -39,7 +37,6 @@ export function useDocument(doctype, docname) {
} }
}, },
setValue: { setValue: {
validate,
onSuccess: () => { onSuccess: () => {
triggerOnSave() triggerOnSave()
toast.success(__('Document updated successfully')) toast.success(__('Document updated successfully'))
@ -155,42 +152,6 @@ export function useDocument(doctype, docname) {
return [] return []
} }
function validate(d) {
checkMandatory(d.doc || d.fieldname)
}
function checkMandatory(doc) {
let fields = meta?.getFields() || []
if (!fields || fields.length === 0) return
let missingFields = []
fields.forEach((df) => {
let parent = meta?.doctypeMeta?.[df.parent] || null
if (evaluateExpression(df.mandatory_depends_on, doc, parent)) {
const value = doc[df.fieldname]
if (
value === undefined ||
value === null ||
(typeof value === 'string' && value.trim() === '') ||
(Array.isArray(value) && value.length === 0)
) {
missingFields.push(df.label || df.fieldname)
}
}
})
if (missingFields.length > 0) {
toast.error(
__('Mandatory fields required: {0}', [missingFields.join(', ')]),
)
throw new Error(
__('Mandatory fields required: {0}', [missingFields.join(', ')]),
)
}
}
async function triggerOnLoad() { async function triggerOnLoad() {
const handler = async function () { const handler = async function () {
await (this.onLoad?.() || this.on_load?.() || this.onload?.()) await (this.onLoad?.() || this.on_load?.() || this.onload?.())
@ -319,7 +280,6 @@ export function useDocument(doctype, docname) {
assignees: assigneesCache[doctype][docname || ''], assignees: assigneesCache[doctype][docname || ''],
scripts, scripts,
error, error,
validate,
getControllers, getControllers,
triggerOnLoad, triggerOnLoad,
triggerOnBeforeCreate, triggerOnBeforeCreate,

View File

@ -421,36 +421,6 @@ export function evaluateDependsOnValue(expression, doc) {
return out return out
} }
export function evaluateExpression(expression, doc, parent) {
if (!expression) return false
if (!doc) return false
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, parent })
if (parent && parent.istable && expression.includes('is_submittable')) {
out = true
}
} catch (e) {
out = true
}
} else {
let value = doc[expression]
if (Array.isArray(value)) {
out = !!value.length
} else {
out = !!value
}
}
return out
}
export function convertSize(size) { export function convertSize(size) {
const units = ['B', 'KB', 'MB', 'GB', 'TB'] const units = ['B', 'KB', 'MB', 'GB', 'TB']
let unitIndex = 0 let unitIndex = 0

View File

@ -2572,10 +2572,10 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.201: frappe-ui@^0.1.200:
version "0.1.201" version "0.1.200"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.201.tgz#12a18f08b97489facd4a170b4cbfc8adbbf1f109" resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.200.tgz#2cdaa24708f0dbe98b0dd6c2536b5974418fdfef"
integrity sha512-kVz9K3W22ZuxGScSCct2kUFexZ1uZPM6FCc5BWAep3UcEzHRSbHzmLyEKCu0FKzftXtDHwNG8AoUDyHrDPkvCw== integrity sha512-mlkGS5oZxKYcpAit6qehjmoYqo6NvrWmSrbhvkpYDXecEBWU3IgJkMzt13753Z3QTbWbJv7y3iadlVge8u+FMw==
dependencies: dependencies:
"@floating-ui/vue" "^1.1.6" "@floating-ui/vue" "^1.1.6"
"@headlessui/vue" "^1.7.14" "@headlessui/vue" "^1.7.14"