Merge pull request #1287 from frappe/main-hotfix

This commit is contained in:
Shariq Ansari 2025-09-25 22:02:44 +05:30 committed by GitHub
commit 3e9fdb8d20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 8111 additions and 6324 deletions

View File

@ -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:

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",
"@twilio/voice-sdk": "^2.10.2",
"@vueuse/integrations": "^10.3.0",
"frappe-ui": "^0.1.200",
"frappe-ui": "^0.1.201",
"gemoji": "^8.1.0",
"lodash": "^4.17.21",
"mime": "^4.0.1",

View File

@ -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
? __(

View File

@ -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="

View File

@ -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) => {

View File

@ -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,

View File

@ -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

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"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.200:
version "0.1.200"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.200.tgz#2cdaa24708f0dbe98b0dd6c2536b5974418fdfef"
integrity sha512-mlkGS5oZxKYcpAit6qehjmoYqo6NvrWmSrbhvkpYDXecEBWU3IgJkMzt13753Z3QTbWbJv7y3iadlVge8u+FMw==
frappe-ui@^0.1.201:
version "0.1.201"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.201.tgz#12a18f08b97489facd4a170b4cbfc8adbbf1f109"
integrity sha512-kVz9K3W22ZuxGScSCct2kUFexZ1uZPM6FCc5BWAep3UcEzHRSbHzmLyEKCu0FKzftXtDHwNG8AoUDyHrDPkvCw==
dependencies:
"@floating-ui/vue" "^1.1.6"
"@headlessui/vue" "^1.7.14"