Compare commits

..

54 Commits

Author SHA1 Message Date
Frappe PR Bot
cf849f7dff chore(release): Bumped to Version 1.53.1 2025-09-25 16:33:11 +00:00
Shariq Ansari
3e9fdb8d20
Merge pull request #1287 from frappe/main-hotfix 2025-09-25 22:02:44 +05:30
Shariq Ansari
260755fd2e
Merge pull request #1286 from frappe/mergify/bp/main-hotfix/pr-1279 2025-09-25 21:57:28 +05:30
Shariq Ansari
85df56b464
Merge pull request #1285 from frappe/mergify/bp/main-hotfix/pr-1284 2025-09-25 21:53:53 +05:30
Shariq Ansari
a9e956b5fc chore: Norwegian Bokmal translations
(cherry picked from commit 9e92282e25cd1d3e5c33b87b02ad6dfdc09158cc)
2025-09-25 16:23:42 +00:00
Shariq Ansari
905eb63c5f chore: Serbian (Latin) translations
(cherry picked from commit 0138716e070b8098fd37c3ee67496c7a0d1ff473)
2025-09-25 16:23:42 +00:00
Shariq Ansari
3eee437014 chore: Serbian (Cyrillic) translations
(cherry picked from commit 589c95e263bf70fda044ef841a623ccda510c744)
2025-09-25 16:23:42 +00:00
Shariq Ansari
4498de2041 chore: Norwegian Bokmal translations
(cherry picked from commit a4e2663b2418c935c8279fab97934dff0d2221b7)
2025-09-25 16:23:41 +00:00
Shariq Ansari
bfb0d25765 chore: Norwegian Bokmal translations
(cherry picked from commit f91ac266ca921427fa2d38af4e8d8f847d46e111)
2025-09-25 16:23:41 +00:00
Shariq Ansari
9dd6ffa1e1 chore: Danish translations
(cherry picked from commit 415d5410ba707a59a6cc6a7cf8d0bafc8a6a7e50)
2025-09-25 16:23:41 +00:00
Shariq Ansari
05322e805f chore: Esperanto translations
(cherry picked from commit 9fc20a3078922b45440551343f7fb032d17d814b)
2025-09-25 16:23:40 +00:00
Shariq Ansari
8dc6db3218 chore: Croatian translations
(cherry picked from commit 43dadfe746f3ade875bf5420edca1b3bc92cc372)
2025-09-25 16:23:40 +00:00
Shariq Ansari
1b63384eac chore: Thai translations
(cherry picked from commit 56071d5e0dd520d96b24ce92d9079823921402aa)
2025-09-25 16:23:40 +00:00
Shariq Ansari
37bbbb6b4b chore: Persian translations
(cherry picked from commit b0e79d0aef3a3fe637c6d0d2d734118b095a52a5)
2025-09-25 16:23:40 +00:00
Shariq Ansari
6f0244c2b6 chore: Vietnamese translations
(cherry picked from commit 5848649955c084076afda007e21df1b0940394f6)
2025-09-25 16:23:39 +00:00
Shariq Ansari
e08bc9cd20 chore: Chinese Simplified translations
(cherry picked from commit 3c5ee979f86d9a0cdbd6717c91253622e47e3f8d)
2025-09-25 16:23:39 +00:00
Shariq Ansari
2043157567 chore: Turkish translations
(cherry picked from commit 2acd7476c8e1f92f2747a84666badafe5b204910)
2025-09-25 16:23:39 +00:00
Shariq Ansari
8093a422cd chore: Russian translations
(cherry picked from commit 66c586582836bcbd3210a4cd2e254edf3fc9b9f5)
2025-09-25 16:23:38 +00:00
Shariq Ansari
4bef919d38 chore: Portuguese translations
(cherry picked from commit 103a137af12912bcf388b06a14c84abadbad8979)
2025-09-25 16:23:38 +00:00
Shariq Ansari
874947b8ae chore: Dutch translations
(cherry picked from commit 52cc70d704a284bd41696e67e2c1fb24142dc6ee)
2025-09-25 16:23:38 +00:00
Shariq Ansari
ca90c0406e chore: Hungarian translations
(cherry picked from commit d72dcee7b663d35c83a5658815a1cdd529b445c3)
2025-09-25 16:23:38 +00:00
Shariq Ansari
6bf27d852b chore: Czech translations
(cherry picked from commit b473b27f9ace8d76023b13ec1cf57e81cc487835)
2025-09-25 16:23:37 +00:00
Shariq Ansari
793cb76789 chore: Arabic translations
(cherry picked from commit 8805560144baa224c6f3ac5758e9d3d288e60f45)
2025-09-25 16:23:37 +00:00
Shariq Ansari
ff07054ca3 chore: Spanish translations
(cherry picked from commit 3f0c4e9614d8081344dd8ef213bed525c69a4b1a)
2025-09-25 16:23:37 +00:00
Shariq Ansari
2ee5269d3e chore: French translations
(cherry picked from commit 2b13c3f27704a8b50facb7636f4a82810e843bd6)
2025-09-25 16:23:36 +00:00
Shariq Ansari
e27954da52 chore: German translations
(cherry picked from commit b6fa3bf32b3c21e82917061da94f450e81f48323)
2025-09-25 16:23:36 +00:00
Shariq Ansari
2faa0d0f04 chore: Serbian (Latin) translations
(cherry picked from commit ae9e59aa0002112fa22192d15df43491b1145c8c)
2025-09-25 16:23:36 +00:00
Shariq Ansari
f1664eec2f chore: Bosnian translations
(cherry picked from commit 4533becc620ea06bc61043eb58e8350b90445950)
2025-09-25 16:23:36 +00:00
Shariq Ansari
7f2efea7cb chore: Indonesian translations
(cherry picked from commit 57bd9fe70a6f09d336b8b17dd1215481f2f5d03d)
2025-09-25 16:23:35 +00:00
Shariq Ansari
d2cc6b7c2e chore: Portuguese, Brazilian translations
(cherry picked from commit 013c21a5d1b564632b0383893d4b45512afa9e4c)
2025-09-25 16:23:35 +00:00
Shariq Ansari
1afb6d6827 chore: Swedish translations
(cherry picked from commit 9a780039e589e369f76f4fe9b4e0dff0902bd859)
2025-09-25 16:23:35 +00:00
Shariq Ansari
ccaf136830 chore: Serbian (Cyrillic) translations
(cherry picked from commit 1bd62289dc720edc9515a3677ac377732ae1dc60)
2025-09-25 16:23:34 +00:00
Shariq Ansari
58a41d1b11 chore: Polish translations
(cherry picked from commit f7382f40ac78be3804f34866162f30329f742d76)
2025-09-25 16:23:34 +00:00
Shariq Ansari
777f3ac06c chore: Italian translations
(cherry picked from commit 3c870ce042a7d165ad914f90cf84fd5473ad8243)
2025-09-25 16:23:34 +00:00
Shariq Ansari
105c78e264
Merge pull request #1283 from frappe/mergify/bp/main-hotfix/pr-1282
fix: add validation for mandatory fields in useDocument (backport #1282)
2025-09-25 21:53:09 +05:30
Shariq Ansari
46cc1d2924 build(deps): bump frappeui to 0.1.201
(cherry picked from commit 171060df8ade73f6b9ef4b51945c76604c759299)
2025-09-25 16:22:33 +00:00
Shariq Ansari
ff4ca9fe66 fix: add validation for mandatory fields in useDocument
(cherry picked from commit dbcda4c548270f4b030d819857b1f393fdaadecb)
2025-09-25 16:18:30 +00:00
Shariq Ansari
4989dc0921
Merge pull request #1277 from shariquerik/backport-1125
fix: Bulk Delete "Reference Doctype must be set first" Error (backport #1125)
2025-09-22 16:12:16 +05:30
Shariq Ansari
1e613ebcd1 fix: Bulk Delete 'Reference Doctype must be set first' Error backport (#1125) 2025-09-22 16:04:53 +05:30
Frappe PR Bot
4f8f195d77 chore(release): Bumped to Version 1.53.0 2025-09-22 09:55:37 +00:00
Shariq Ansari
af64b86a04
Merge pull request #1276 from frappe/main-hotfix 2025-09-22 15:25:13 +05:30
Shariq Ansari
7e9bc0524e
Merge pull request #1275 from frappe/mergify/bp/main-hotfix/pr-1266 2025-09-22 15:18:12 +05:30
Shariq Ansari
9d0a0d1d32
Merge pull request #1273 from frappe/mergify/bp/main-hotfix/pr-1272 2025-09-22 15:17:51 +05:30
Shariq Ansari
9c84a8be7f
Merge pull request #1274 from frappe/mergify/bp/main-hotfix/pr-1262 2025-09-22 15:17:42 +05:30
frappe-pr-bot
d24537489e chore: update POT file
(cherry picked from commit 625e472303a4d759024a744bd93bc8d721537a0a)
2025-09-22 09:41:24 +00:00
Shariq Ansari
bd89b3b356 chore: Norwegian Bokmal translations
(cherry picked from commit ce632c69c1e634dc0feed03eec3f4c76370e7dcf)
2025-09-22 09:41:23 +00:00
Shariq Ansari
988fb90ddb chore: Norwegian Bokmal translations
(cherry picked from commit 93ed6fcdddac8a903e470ee35d1cbcca7e9ba5cf)
2025-09-22 09:41:23 +00:00
Shariq Ansari
8018b1766c chore: Norwegian Bokmal translations
(cherry picked from commit e3eff7f78de04f49bb78a3e1401048a42d4bf2eb)
2025-09-22 09:41:23 +00:00
Shariq Ansari
9c4c2a0aca chore: Norwegian Bokmal translations
(cherry picked from commit 394da5e0024dfce029f90346ff695dc7f7a67e5f)
2025-09-22 09:41:23 +00:00
Shariq Ansari
803e639961 build(deps): bump frappeui to 0.1.200
(cherry picked from commit 96c0c99939b30880ddf27b91f3f6b18c95ef3409)
2025-09-22 09:40:21 +00:00
Shariq Ansari
fabd362b2a
Merge pull request #1260 from frappe/mergify/bp/main-hotfix/pr-1256 2025-09-18 15:44:32 +05:30
Shariq Ansari
ff312d964b
Merge pull request #1251 from frappe/main-hotfix 2025-09-16 15:23:38 +05:30
Frappe PR Bot
17b7c6ecef chore(release): Bumped to Version 1.52.11 2025-09-08 14:20:41 +00:00
Shariq Ansari
8d7a155d78
Merge pull request #1239 from frappe/main-hotfix 2025-09-08 19:49:39 +05:30
38 changed files with 9837 additions and 8134 deletions

1
.gitignore vendored
View File

@ -7,6 +7,5 @@ dev-dist
tags
node_modules
crm/public/frontend
frontend/yarn.lock
crm/www/crm.html
build

View File

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

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

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit 02fc126fd5c49f0ecf6cce117585f89c4ea585c3
Subproject commit c9a0fc937cc897864857271b3708a0c675379015

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.197",
"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

File diff suppressed because it is too large Load Diff