Merge pull request #729 from frappe/mergify/bp/main-hotfix/pr-728

fix: added ErrorPage if user does not have access to doc (backport #728)
This commit is contained in:
Shariq Ansari 2025-04-08 15:48:11 +05:30 committed by GitHub
commit ff4693dde7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 113 additions and 16 deletions

View File

@ -23,11 +23,11 @@ def update_deals_email_mobile_no(doc):
@frappe.whitelist() @frappe.whitelist()
def get_contact(name): def get_contact(name):
Contact = frappe.qb.DocType("Contact") contact = frappe.get_doc("Contact", name)
contact.check_permission("read")
query = frappe.qb.from_(Contact).select("*").where(Contact.name == name).limit(1) contact = contact.as_dict()
contact = query.run(as_dict=True)
if not len(contact): if not len(contact):
frappe.throw(_("Contact not found"), frappe.DoesNotExistError) frappe.throw(_("Contact not found"), frappe.DoesNotExistError)
contact = contact.pop() contact = contact.pop()

View File

@ -6,7 +6,10 @@ from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
@frappe.whitelist() @frappe.whitelist()
def get_deal(name): def get_deal(name):
deal = frappe.get_doc("CRM Deal", name).as_dict() deal = frappe.get_doc("CRM Deal", name)
deal.check_permission("read")
deal = deal.as_dict()
deal["fields_meta"] = get_fields_meta("CRM Deal") deal["fields_meta"] = get_fields_meta("CRM Deal")
deal["_form_script"] = get_form_script("CRM Deal") deal["_form_script"] = get_form_script("CRM Deal")

View File

@ -6,7 +6,10 @@ from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script
@frappe.whitelist() @frappe.whitelist()
def get_lead(name): def get_lead(name):
lead = frappe.get_doc("CRM Lead", name).as_dict() lead = frappe.get_doc("CRM Lead", name)
lead.check_permission("read")
lead = lead.as_dict()
lead["fields_meta"] = get_fields_meta("CRM Lead") lead["fields_meta"] = get_fields_meta("CRM Lead")
lead["_form_script"] = get_form_script("CRM Lead") lead["_form_script"] = get_form_script("CRM Lead")

View File

@ -88,6 +88,7 @@ declare module 'vue' {
EmailTemplatesListView: typeof import('./src/components/ListViews/EmailTemplatesListView.vue')['default'] EmailTemplatesListView: typeof import('./src/components/ListViews/EmailTemplatesListView.vue')['default']
ERPNextIcon: typeof import('./src/components/Icons/ERPNextIcon.vue')['default'] ERPNextIcon: typeof import('./src/components/Icons/ERPNextIcon.vue')['default']
ERPNextSettings: typeof import('./src/components/Settings/ERPNextSettings.vue')['default'] ERPNextSettings: typeof import('./src/components/Settings/ERPNextSettings.vue')['default']
ErrorPage: typeof import('./src/components/ErrorPage.vue')['default']
ExotelCallUI: typeof import('./src/components/Telephony/ExotelCallUI.vue')['default'] ExotelCallUI: typeof import('./src/components/Telephony/ExotelCallUI.vue')['default']
ExportIcon: typeof import('./src/components/Icons/ExportIcon.vue')['default'] ExportIcon: typeof import('./src/components/Icons/ExportIcon.vue')['default']
ExternalLinkIcon: typeof import('./src/components/Icons/ExternalLinkIcon.vue')['default'] ExternalLinkIcon: typeof import('./src/components/Icons/ExternalLinkIcon.vue')['default']

View File

@ -0,0 +1,24 @@
<template>
<div
class="grid h-full place-items-center px-4 py-20 text-center text-lg text-ink-gray-5"
>
<div class="flex flex-col justify-between items-center gap-3">
<FeatherIcon name="x-octagon" class="h-12 w-12 text-ink-red-3" />
<div class="text-2xl font-semibold">{{ errorTitle }}</div>
<div v-html="errorMessage" />
</div>
</div>
</template>
<script setup>
const props = defineProps({
errorTitle: {
type: String,
required: true,
},
errorMessage: {
type: String,
required: true,
},
})
</script>

View File

@ -8,7 +8,7 @@
</Breadcrumbs> </Breadcrumbs>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div ref="parentRef" class="flex h-full"> <div v-if="contact.data" ref="parentRef" class="flex h-full">
<Resizer <Resizer
v-if="contact.data" v-if="contact.data"
:parent="$refs.parentRef" :parent="$refs.parentRef"
@ -168,10 +168,12 @@
</template> </template>
</Tabs> </Tabs>
</div> </div>
<ErrorPage v-else :errorTitle="errorTitle" :errorMessage="errorMessage" />
<AddressModal v-model="showAddressModal" v-model:address="_address" /> <AddressModal v-model="showAddressModal" v-model:address="_address" />
</template> </template>
<script setup> <script setup>
import ErrorPage from '@/components/ErrorPage.vue'
import Resizer from '@/components/Resizer.vue' import Resizer from '@/components/Resizer.vue'
import Icon from '@/components/Icon.vue' import Icon from '@/components/Icon.vue'
import SidePanelLayout from '@/components/SidePanelLayout.vue' import SidePanelLayout from '@/components/SidePanelLayout.vue'
@ -202,6 +204,7 @@ import {
} from 'frappe-ui' } from 'frappe-ui'
import { ref, computed, h } from 'vue' import { ref, computed, h } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { errorMessage as _errorMessage } from '../utils'
const { brand } = getSettings() const { brand } = getSettings()
const { $dialog, makeCall } = globalStore() const { $dialog, makeCall } = globalStore()
@ -225,6 +228,9 @@ const showAddressModal = ref(false)
const _contact = ref({}) const _contact = ref({})
const _address = ref({}) const _address = ref({})
const errorTitle = ref('')
const errorMessage = ref('')
const contact = createResource({ const contact = createResource({
url: 'crm.api.contact.get_contact', url: 'crm.api.contact.get_contact',
cache: ['contact', props.contactId], cache: ['contact', props.contactId],
@ -237,6 +243,18 @@ const contact = createResource({
mobile_no: data.mobile_no, mobile_no: data.mobile_no,
} }
}, },
onSuccess: () => {
errorTitle.value = ''
errorMessage.value = ''
},
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Contacts' })
}
},
}) })
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {

View File

@ -89,7 +89,7 @@
@click=" @click="
deal.data.email deal.data.email
? openEmailBox() ? openEmailBox()
: errorMessage(__('No email set')) : _errorMessage(__('No email set'))
" "
/> />
</Button> </Button>
@ -103,7 +103,7 @@
@click=" @click="
deal.data.website deal.data.website
? openWebsite(deal.data.website) ? openWebsite(deal.data.website)
: errorMessage(__('No website set')) : _errorMessage(__('No website set'))
" "
/> />
</Button> </Button>
@ -267,6 +267,7 @@
</div> </div>
</Resizer> </Resizer>
</div> </div>
<ErrorPage v-else :errorTitle="errorTitle" :errorMessage="errorMessage" />
<OrganizationModal <OrganizationModal
v-model="showOrganizationModal" v-model="showOrganizationModal"
v-model:organization="_organization" v-model:organization="_organization"
@ -297,6 +298,7 @@
/> />
</template> </template>
<script setup> <script setup>
import ErrorPage from '@/components/ErrorPage.vue'
import Icon from '@/components/Icon.vue' import Icon from '@/components/Icon.vue'
import Resizer from '@/components/Resizer.vue' import Resizer from '@/components/Resizer.vue'
import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue' import LoadingIndicator from '@/components/Icons/LoadingIndicator.vue'
@ -330,7 +332,7 @@ import {
createToast, createToast,
setupAssignees, setupAssignees,
setupCustomizations, setupCustomizations,
errorMessage, errorMessage as _errorMessage,
copyToClipboard, copyToClipboard,
} from '@/utils' } from '@/utils'
import { getView } from '@/utils/view' import { getView } from '@/utils/view'
@ -372,11 +374,17 @@ const props = defineProps({
}, },
}) })
const errorTitle = ref('')
const errorMessage = ref('')
const deal = createResource({ const deal = createResource({
url: 'crm.fcrm.doctype.crm_deal.api.get_deal', url: 'crm.fcrm.doctype.crm_deal.api.get_deal',
params: { name: props.dealId }, params: { name: props.dealId },
cache: ['deal', props.dealId], cache: ['deal', props.dealId],
onSuccess: (data) => { onSuccess: (data) => {
errorTitle.value = ''
errorMessage.value = ''
if (data.organization) { if (data.organization) {
organization.update({ organization.update({
params: { doctype: 'CRM Organization', name: data.organization }, params: { doctype: 'CRM Organization', name: data.organization },
@ -401,6 +409,14 @@ const deal = createResource({
call, call,
}) })
}, },
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Deals' })
}
},
}) })
const organization = createResource({ const organization = createResource({
@ -698,12 +714,12 @@ function triggerCall() {
let mobile_no = primaryContact.mobile_no || null let mobile_no = primaryContact.mobile_no || null
if (!primaryContact) { if (!primaryContact) {
errorMessage(__('No primary contact set')) _errorMessage(__('No primary contact set'))
return return
} }
if (!mobile_no) { if (!mobile_no) {
errorMessage(__('No mobile number set')) _errorMessage(__('No mobile number set'))
return return
} }

View File

@ -124,7 +124,7 @@
() => () =>
lead.data.mobile_no lead.data.mobile_no
? makeCall(lead.data.mobile_no) ? makeCall(lead.data.mobile_no)
: errorMessage(__('No phone number set')) : _errorMessage(__('No phone number set'))
" "
> >
<PhoneIcon class="h-4 w-4" /> <PhoneIcon class="h-4 w-4" />
@ -139,7 +139,7 @@
@click=" @click="
lead.data.email lead.data.email
? openEmailBox() ? openEmailBox()
: errorMessage(__('No email set')) : _errorMessage(__('No email set'))
" "
/> />
</Button> </Button>
@ -153,7 +153,7 @@
@click=" @click="
lead.data.website lead.data.website
? openWebsite(lead.data.website) ? openWebsite(lead.data.website)
: errorMessage(__('No website set')) : _errorMessage(__('No website set'))
" "
/> />
</Button> </Button>
@ -191,6 +191,7 @@
</div> </div>
</Resizer> </Resizer>
</div> </div>
<ErrorPage v-else :errorTitle="errorTitle" :errorMessage="errorMessage" />
<Dialog <Dialog
v-model="showConvertToDealModal" v-model="showConvertToDealModal"
:options="{ :options="{
@ -309,6 +310,7 @@
/> />
</template> </template>
<script setup> <script setup>
import ErrorPage from '@/components/ErrorPage.vue'
import Icon from '@/components/Icon.vue' import Icon from '@/components/Icon.vue'
import Resizer from '@/components/Resizer.vue' import Resizer from '@/components/Resizer.vue'
import ActivityIcon from '@/components/Icons/ActivityIcon.vue' import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
@ -342,7 +344,7 @@ import {
createToast, createToast,
setupAssignees, setupAssignees,
setupCustomizations, setupCustomizations,
errorMessage, errorMessage as _errorMessage,
copyToClipboard, copyToClipboard,
} from '@/utils' } from '@/utils'
import { getView } from '@/utils/view' import { getView } from '@/utils/view'
@ -392,11 +394,16 @@ const props = defineProps({
}, },
}) })
const errorTitle = ref('')
const errorMessage = ref('')
const lead = createResource({ const lead = createResource({
url: 'crm.fcrm.doctype.crm_lead.api.get_lead', url: 'crm.fcrm.doctype.crm_lead.api.get_lead',
params: { name: props.leadId }, params: { name: props.leadId },
cache: ['lead', props.leadId], cache: ['lead', props.leadId],
onSuccess: (data) => { onSuccess: (data) => {
errorTitle.value = ''
errorMessage.value = ''
setupAssignees(lead) setupAssignees(lead)
setupCustomizations(lead, { setupCustomizations(lead, {
doc: data, doc: data,
@ -410,6 +417,14 @@ const lead = createResource({
call, call,
}) })
}, },
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Leads' })
}
},
}) })
onMounted(() => { onMounted(() => {

View File

@ -8,7 +8,7 @@
</Breadcrumbs> </Breadcrumbs>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div ref="parentRef" class="flex h-full"> <div v-if="organization.doc" ref="parentRef" class="flex h-full">
<Resizer <Resizer
v-if="organization.doc" v-if="organization.doc"
:parent="$refs.parentRef" :parent="$refs.parentRef"
@ -160,6 +160,7 @@
</template> </template>
</Tabs> </Tabs>
</div> </div>
<ErrorPage v-else :errorTitle="errorTitle" :errorMessage="errorMessage" />
<QuickEntryModal <QuickEntryModal
v-if="showQuickEntryModal" v-if="showQuickEntryModal"
v-model="showQuickEntryModal" v-model="showQuickEntryModal"
@ -169,6 +170,7 @@
</template> </template>
<script setup> <script setup>
import ErrorPage from '@/components/ErrorPage.vue'
import Resizer from '@/components/Resizer.vue' import Resizer from '@/components/Resizer.vue'
import SidePanelLayout from '@/components/SidePanelLayout.vue' import SidePanelLayout from '@/components/SidePanelLayout.vue'
import Icon from '@/components/Icon.vue' import Icon from '@/components/Icon.vue'
@ -221,12 +223,27 @@ const showQuickEntryModal = ref(false)
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const errorTitle = ref('')
const errorMessage = ref('')
const organization = createDocumentResource({ const organization = createDocumentResource({
doctype: 'CRM Organization', doctype: 'CRM Organization',
name: props.organizationId, name: props.organizationId,
cache: ['organization', props.organizationId], cache: ['organization', props.organizationId],
fields: ['*'], fields: ['*'],
auto: true, auto: true,
onSuccess: () => {
errorTitle.value = ''
errorMessage.value = ''
},
onError: (err) => {
if (err.messages?.[0]) {
errorTitle.value = __('Not permitted')
errorMessage.value = __(err.messages?.[0])
} else {
router.push({ name: 'Organizations' })
}
},
}) })
async function updateField(fieldname, value) { async function updateField(fieldname, value) {