Compare commits
49 Commits
mergify/bp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf849f7dff | ||
|
|
3e9fdb8d20 | ||
|
|
260755fd2e | ||
|
|
85df56b464 | ||
|
|
a9e956b5fc | ||
|
|
905eb63c5f | ||
|
|
3eee437014 | ||
|
|
4498de2041 | ||
|
|
bfb0d25765 | ||
|
|
9dd6ffa1e1 | ||
|
|
05322e805f | ||
|
|
8dc6db3218 | ||
|
|
1b63384eac | ||
|
|
37bbbb6b4b | ||
|
|
6f0244c2b6 | ||
|
|
e08bc9cd20 | ||
|
|
2043157567 | ||
|
|
8093a422cd | ||
|
|
4bef919d38 | ||
|
|
874947b8ae | ||
|
|
ca90c0406e | ||
|
|
6bf27d852b | ||
|
|
793cb76789 | ||
|
|
ff07054ca3 | ||
|
|
2ee5269d3e | ||
|
|
e27954da52 | ||
|
|
2faa0d0f04 | ||
|
|
f1664eec2f | ||
|
|
7f2efea7cb | ||
|
|
d2cc6b7c2e | ||
|
|
1afb6d6827 | ||
|
|
ccaf136830 | ||
|
|
58a41d1b11 | ||
|
|
777f3ac06c | ||
|
|
105c78e264 | ||
|
|
46cc1d2924 | ||
|
|
ff4ca9fe66 | ||
|
|
4989dc0921 | ||
|
|
1e613ebcd1 | ||
|
|
4f8f195d77 | ||
|
|
af64b86a04 | ||
|
|
7e9bc0524e | ||
|
|
9d0a0d1d32 | ||
|
|
9c84a8be7f | ||
|
|
d24537489e | ||
|
|
803e639961 | ||
|
|
ff312d964b | ||
|
|
17b7c6ecef | ||
|
|
8d7a155d78 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,5 @@ dev-dist
|
||||
tags
|
||||
node_modules
|
||||
crm/public/frontend
|
||||
frontend/yarn.lock
|
||||
crm/www/crm.html
|
||||
build
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
|
||||
__version__ = "1.52.10"
|
||||
__version__ = "1.53.1"
|
||||
__title__ = "Frappe CRM"
|
||||
|
||||
|
||||
137
crm/api/doc.py
137
crm/api/doc.py
@ -750,7 +750,11 @@ def getCounts(d, doctype):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_linked_docs_of_document(doctype, docname):
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
try:
|
||||
doc = frappe.get_doc(doctype, docname)
|
||||
except frappe.DoesNotExistError:
|
||||
return []
|
||||
|
||||
linked_docs = get_linked_docs(doc)
|
||||
dynamic_linked_docs = get_dynamic_linked_docs(doc)
|
||||
|
||||
@ -759,7 +763,14 @@ def get_linked_docs_of_document(doctype, docname):
|
||||
|
||||
docs_data = []
|
||||
for doc in linked_docs:
|
||||
data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"])
|
||||
if not doc.get("reference_doctype") or not doc.get("reference_docname"):
|
||||
continue
|
||||
|
||||
try:
|
||||
data = frappe.get_doc(doc["reference_doctype"], doc["reference_docname"])
|
||||
except (frappe.DoesNotExistError, frappe.ValidationError):
|
||||
continue
|
||||
|
||||
title = data.get("title")
|
||||
if data.doctype == "CRM Call Log":
|
||||
title = f"Call from {data.get('from')} to {data.get('to')}"
|
||||
@ -767,6 +778,9 @@ def get_linked_docs_of_document(doctype, docname):
|
||||
if data.doctype == "CRM Deal":
|
||||
title = data.get("organization")
|
||||
|
||||
if data.doctype == "CRM Notification":
|
||||
title = data.get("message")
|
||||
|
||||
docs_data.append(
|
||||
{
|
||||
"doc": data.doctype,
|
||||
@ -779,25 +793,51 @@ def get_linked_docs_of_document(doctype, docname):
|
||||
|
||||
|
||||
def remove_doc_link(doctype, docname):
|
||||
linked_doc_data = frappe.get_doc(doctype, docname)
|
||||
linked_doc_data.update(
|
||||
{
|
||||
"reference_doctype": None,
|
||||
"reference_docname": None,
|
||||
}
|
||||
)
|
||||
linked_doc_data.save(ignore_permissions=True)
|
||||
if not doctype or not docname:
|
||||
return
|
||||
|
||||
try:
|
||||
linked_doc_data = frappe.get_doc(doctype, docname)
|
||||
if doctype == "CRM Notification":
|
||||
delete_notification_type = {
|
||||
"notification_type_doctype": "",
|
||||
"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):
|
||||
linked_doc_data = frappe.get_doc(doctype, docname)
|
||||
linked_doc_data.update(
|
||||
{
|
||||
"contact": None,
|
||||
"contacts": [],
|
||||
}
|
||||
)
|
||||
linked_doc_data.save(ignore_permissions=True)
|
||||
if not doctype or not docname:
|
||||
return
|
||||
|
||||
try:
|
||||
linked_doc_data = frappe.get_doc(doctype, docname)
|
||||
linked_doc_data.update(
|
||||
{
|
||||
"contact": None,
|
||||
"contacts": [],
|
||||
}
|
||||
)
|
||||
linked_doc_data.save(ignore_permissions=True)
|
||||
except (frappe.DoesNotExistError, frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -806,13 +846,19 @@ def remove_linked_doc_reference(items, remove_contact=None, delete=False):
|
||||
items = frappe.parse_json(items)
|
||||
|
||||
for item in items:
|
||||
if remove_contact:
|
||||
remove_contact_link(item["doctype"], item["docname"])
|
||||
else:
|
||||
remove_doc_link(item["doctype"], item["docname"])
|
||||
if not item.get("doctype") or not item.get("docname"):
|
||||
continue
|
||||
|
||||
if delete:
|
||||
frappe.delete_doc(item["doctype"], item["docname"])
|
||||
try:
|
||||
if remove_contact:
|
||||
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"
|
||||
|
||||
@ -821,19 +867,40 @@ def remove_linked_doc_reference(items, remove_contact=None, delete=False):
|
||||
def delete_bulk_docs(doctype, items, delete_linked=False):
|
||||
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)
|
||||
if not isinstance(items, list):
|
||||
frappe.throw("Items must be a list")
|
||||
|
||||
for doc in items:
|
||||
linked_docs = get_linked_docs_of_document(doctype, doc)
|
||||
for linked_doc in linked_docs:
|
||||
remove_linked_doc_reference(
|
||||
[
|
||||
{
|
||||
"doctype": linked_doc["reference_doctype"],
|
||||
"docname": linked_doc["reference_docname"],
|
||||
}
|
||||
],
|
||||
remove_contact=doctype == "Contact",
|
||||
delete=delete_linked,
|
||||
try:
|
||||
if not frappe.db.exists(doctype, doc):
|
||||
frappe.log_error(f"Document {doctype} {doc} does not exist", "Bulk Delete Error")
|
||||
continue
|
||||
|
||||
linked_docs = get_linked_docs_of_document(doctype, doc)
|
||||
for linked_doc in linked_docs:
|
||||
if not linked_doc.get("reference_doctype") or not linked_doc.get("reference_docname"):
|
||||
continue
|
||||
|
||||
remove_linked_doc_reference(
|
||||
[
|
||||
{
|
||||
"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:
|
||||
|
||||
543
crm/locale/ar.po
543
crm/locale/ar.po
File diff suppressed because it is too large
Load Diff
549
crm/locale/bs.po
549
crm/locale/bs.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/cs.po
543
crm/locale/cs.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/da.po
543
crm/locale/da.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/de.po
543
crm/locale/de.po
File diff suppressed because it is too large
Load Diff
549
crm/locale/eo.po
549
crm/locale/eo.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/es.po
543
crm/locale/es.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/fa.po
543
crm/locale/fa.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/fr.po
543
crm/locale/fr.po
File diff suppressed because it is too large
Load Diff
549
crm/locale/hr.po
549
crm/locale/hr.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/hu.po
543
crm/locale/hu.po
File diff suppressed because it is too large
Load Diff
545
crm/locale/id.po
545
crm/locale/id.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/it.po
543
crm/locale/it.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
577
crm/locale/nb.po
577
crm/locale/nb.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/nl.po
543
crm/locale/nl.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/pl.po
543
crm/locale/pl.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/pt.po
543
crm/locale/pt.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
543
crm/locale/ru.po
543
crm/locale/ru.po
File diff suppressed because it is too large
Load Diff
549
crm/locale/sr.po
549
crm/locale/sr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
549
crm/locale/sv.po
549
crm/locale/sv.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/th.po
543
crm/locale/th.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/tr.po
543
crm/locale/tr.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/vi.po
543
crm/locale/vi.po
File diff suppressed because it is too large
Load Diff
543
crm/locale/zh.po
543
crm/locale/zh.po
File diff suppressed because it is too large
Load Diff
@ -1 +1 @@
|
||||
Subproject commit 02fc126fd5c49f0ecf6cce117585f89c4ea585c3
|
||||
Subproject commit c9a0fc937cc897864857271b3708a0c675379015
|
||||
@ -13,7 +13,7 @@
|
||||
"@tiptap/extension-paragraph": "^2.12.0",
|
||||
"@twilio/voice-sdk": "^2.10.2",
|
||||
"@vueuse/integrations": "^10.3.0",
|
||||
"frappe-ui": "^0.1.197",
|
||||
"frappe-ui": "^0.1.201",
|
||||
"gemoji": "^8.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mime": "^4.0.1",
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-ink-gray-5">
|
||||
<div class="text-ink-gray-5 text-base">
|
||||
{{
|
||||
__('Are you sure you want to delete {0} items?', [
|
||||
props.items?.length,
|
||||
@ -53,7 +53,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-ink-gray-5">
|
||||
<div class="text-ink-gray-5 text-base">
|
||||
{{
|
||||
confirmDeleteInfo.delete
|
||||
? __(
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<Dialog v-model="show" :options="{ size: 'xl' }">
|
||||
<template #body v-if="!confirmDeleteInfo.show">
|
||||
<div class="bg-surface-modal px-4 pb-6 pt-5 sm:px-6">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-2xl leading-6 text-ink-gray-9 font-semibold">
|
||||
{{
|
||||
@ -32,11 +32,12 @@
|
||||
{
|
||||
label: 'Document',
|
||||
key: 'title',
|
||||
width: '19rem',
|
||||
},
|
||||
{
|
||||
label: 'Master',
|
||||
key: 'reference_doctype',
|
||||
width: '30%',
|
||||
width: '12rem',
|
||||
},
|
||||
]"
|
||||
@selectionsChanged="
|
||||
|
||||
@ -26,13 +26,14 @@
|
||||
<ListRowItem
|
||||
:item="item"
|
||||
@click="listViewRef.toggleRow(row['reference_docname'])"
|
||||
class="!w-full"
|
||||
>
|
||||
<template #default="{ label }">
|
||||
<div
|
||||
v-if="column.key === 'title'"
|
||||
class="truncate text-base flex gap-2"
|
||||
class="truncate text-base flex gap-2 w-full"
|
||||
>
|
||||
<span>
|
||||
<span class="max-w-[90%] truncate">
|
||||
{{ label }}
|
||||
</span>
|
||||
<FeatherIcon
|
||||
@ -102,6 +103,7 @@ const listViewRef = ref(null)
|
||||
const viewLinkedDoc = (doc) => {
|
||||
let page = ''
|
||||
let id = ''
|
||||
let openDesk = false
|
||||
switch (doc.reference_doctype) {
|
||||
case 'CRM Lead':
|
||||
page = 'leads'
|
||||
@ -123,6 +125,11 @@ const viewLinkedDoc = (doc) => {
|
||||
page = 'organizations'
|
||||
id = doc.reference_docname
|
||||
break
|
||||
case 'CRM Notification':
|
||||
page = 'crm-notification'
|
||||
id = doc.reference_docname
|
||||
openDesk = true
|
||||
break
|
||||
case 'FCRM Note':
|
||||
page = 'notes'
|
||||
id = `view?open=${doc.reference_docname}`
|
||||
@ -130,7 +137,11 @@ const viewLinkedDoc = (doc) => {
|
||||
default:
|
||||
break
|
||||
}
|
||||
window.open(`/crm/${page}/${id}`)
|
||||
let base = '/crm'
|
||||
if (openDesk) {
|
||||
base = '/app'
|
||||
}
|
||||
window.open(`${base}/${page}/${id}`)
|
||||
}
|
||||
|
||||
const getDoctypeName = (doctype) => {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { getScript } from '@/data/script'
|
||||
import { globalStore } from '@/stores/global'
|
||||
import { getMeta } from '@/stores/meta'
|
||||
import { showSettings, activeSettingsPage } from '@/composables/settings'
|
||||
import { runSequentially, parseAssignees } from '@/utils'
|
||||
import { runSequentially, parseAssignees, evaluateExpression } from '@/utils'
|
||||
import { createDocumentResource, createResource, toast } from 'frappe-ui'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
@ -11,6 +12,7 @@ const assigneesCache = {}
|
||||
|
||||
export function useDocument(doctype, docname) {
|
||||
const { setupScript, scripts } = getScript(doctype)
|
||||
const meta = getMeta(doctype)
|
||||
|
||||
documentsCache[doctype] = documentsCache[doctype] || {}
|
||||
|
||||
@ -37,6 +39,7 @@ export function useDocument(doctype, docname) {
|
||||
}
|
||||
},
|
||||
setValue: {
|
||||
validate,
|
||||
onSuccess: () => {
|
||||
triggerOnSave()
|
||||
toast.success(__('Document updated successfully'))
|
||||
@ -152,6 +155,42 @@ export function useDocument(doctype, docname) {
|
||||
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() {
|
||||
const handler = async function () {
|
||||
await (this.onLoad?.() || this.on_load?.() || this.onload?.())
|
||||
@ -280,6 +319,7 @@ export function useDocument(doctype, docname) {
|
||||
assignees: assigneesCache[doctype][docname || ''],
|
||||
scripts,
|
||||
error,
|
||||
validate,
|
||||
getControllers,
|
||||
triggerOnLoad,
|
||||
triggerOnBeforeCreate,
|
||||
|
||||
@ -421,6 +421,36 @@ export function evaluateDependsOnValue(expression, doc) {
|
||||
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) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let unitIndex = 0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user