在 kintone 客制化中,会透 kintone JavaScript API 注册各种事件处理程式(event handler),以在特定的情况下触发客制化程式码运行。kintone JavaScript API 提供了各种事件可供使用,本文这次要探讨的主角是:在流程管理中执行操作时的事件 app.record.detail.process.proceed。
执行流程动作时,需要记录编辑权限吗?
在我们开始讨论 app.record.detail.process.proceed 的问题前,先来解答这个问题:
执行流程动作时,执行者需要该笔记录的编辑权限吗?
如果你对 kintone 相当熟悉,可能已经知道答案了——答案是:不需要。
根据官方说明 kintone 说明-流程管理的基本使用方法,指定为执行者的使用者,必须具备记录的查看权限。若没有记录的查看权限,该使用者将无法查看记录,也无法执行动作。没有记录的编辑权限则不影响。
执行者只需要有查看权限,没有编辑权限也能执行流程动作。最常见的情境是申请表单的审核,如果只要让审核者决定是否批准申请,但不要让他可以修改申请内容,就可以透过记录权限设定条件,让审核者只能阅览而不能编辑纪录。
然而,有些用户可能会遇到下面的情形:
明明有记录阅览权限,执行流程动作时却跳出错误显示「无权限」,不是说不用编辑权限也不影响吗?
会遇到这样的情形,通常是因为该应用程式有使用客制化程式码或外挂程式,会在执行流程动作时进行一些处理。最简单的排除方式是调整权限设定,让执行者具备编辑权限,如果开启了编辑权限就能够顺利执行动作,那么问题八九不离十就是本文要讨论的主角 —— app.record.detail.process.proceed 事件处理的问题。
app.record.detail.process.proceed 的特性
官方文件:在流程管理中执行操作时的事件
事件发生时机
app.record.detail.process.proceed (行动版:mobile.app.record.detail.process.proceed),以下简称为「process.proceed」,是发生在「在流程管理中执行操作之前」,也就是使用者点击操作之后,在状态正式更新以前的事件。
和「在新增记录画面上存储时的事件(app.record.create.submit)」与「在记录编辑画面上存储时的事件(app.record.edit.submit)」类似,是在执行动作后,到动作执行完成之间发生的事件。
event handler 可以不 return 任何东西
在 event handler 中,不 return 任何东西时,流程动作会正常执行,记录的状态会被更新。
取消执行操作
在 event handler 中,return false 或无效值,或是透过设定 event.error 值,在画面顶部显示错误讯息时,可以中断执行操作。此时,因为操作被取消了,记录的流程状态不会被更新。
可透过事件物件执行的操作
可以透过改写 event.record.栏位代码.value 值,并且 return event 来更新栏位的值。改写栏位值时,需要有记录与栏位的编辑权限。可以改写值的栏位类型请参考官方文件:可以用事件物件执行的操作-重写栏位的值
改写栏位值需要编辑权限,这是很合理的。但是你可能会遇到这样的问题:
「我明明没有修改栏位值啊,怎么还是跳出无权限?」
只要 return event ,就需要编辑权限
虽然官方文件是在改写栏位值的部分加注说需要记录与栏位编辑权限,但实际上,不论你是否有改写 event.record.栏位代码.value 的值,只要在 event handler 内 return event ,就会被视为编辑动作。
所以,如果在事件处理中不需要编辑栏位值时,只要不 return 任何东西,流程就会继续进行。
以下图应用程式为例:
流程设计:
我们写一支简单的客制化程式码,会在申请人执行「送审」时,将当下时间填入栏位「申请时间」,其他的动作则不做任何处理。
【客制化程式码1】
kintone.events.on(\'app.record.detail.process.proceed\', event => {
const action = event.action.value
// 执行此动作的使用者,需要有记录编辑权限
if (action === \'送审\') {
const today = new Date().toISOString()
event.record[\'申请时间\'].value = today
return event // 在条件内 return event
}
// 其他动作不 return event,不需记录编辑权限 (亦可直接省略 return)
return
})
由于在大多数的 kintone 事件当中,都需要 return event 才能正确动作,所以开发者们可能会很习惯地加上 return event ,但在上述的情形中就会遇上权限问题,所以使用 process.proceed 事件进行处理时一定要特别留意这点。
不 return event 的潜在问题
然而,不 return event 有一个潜在的问题,当一个应用程式有多支客制化程式码或外挂程式运用到 process.proceed 事件处理时,更精确来说,当一个应用程式内触发复数个 process.proceed event handler 时,可能会有事件处理之间的竞争问题。
我们继续以方才的范例应用程式来说明,假设后来新增了一个需求,要在执行「送审」动作时检查申请内容字数,字数太少就跳出提示并且取消动作。
【客制化程式码2】
kintone.events.on(\'app.record.detail.process.proceed\', event => {
if (event.action.value !== \'送审\') return
const content = event.record[\'申请内容\']?.value || \'\'
if (content.trim().length < 5) {
window.alert(\'申请内容不得少于 5 字!\')
return false
}
})
由于这个动作不需要修改栏位值,所以按照刚刚讲的原则,不 return event,看起来似乎没有问题对吧?
⋯⋯但是,请停下来思考一下,下面的这两种情形,分别会产生什么样的结果:
如果是第2种情况,两个程式码都可以如预期运作;但如果是第1种情况,就会发生问题——在程式码1当中应该要被更新的申请时间,在流程结束后还是空白的。
这是因为操作 event.record 修改栏位值时,必须要 return event 才会生效,但是程式码1先执行的情况下,虽然有 return event ,但是进入到程式码2后,最终并没有再把 event return 出去,导致程式码1中的编辑无效。
解决方案
本文的范例非常单纯,在没有其他第三方客制化内容或外挂程式的情况下,我们已知执行「送审」动作时会需要编辑权限,且会透过客制化程式码编辑栏位值,所以只要在 【客制化程式码2】 最后面补上 return event,或是确保 【客制化程式码2】 先执行即可。
然而(对,又是这个然而),kintone 使用者很可能会因为需求的改变,需要追加客制化程式或是其他外挂,我们要如何避免自己写的客制化程式码和其他的程式发生冲突呢?
检查编辑权限,再决定是否 return event
以目前 process.proceed 的性质来说,建议所有 kintone 开发者採用这个设计方式。即使开发的内容不需要编辑权限,但你无法确定用户是否会使用其他的客制化程式或外挂,需要 return event 来完成动作。
可以运用「获取执行API的用户记录的存取权限」的 REST API,先确认动作执行者是否具备该笔记录之编辑权限,如果有编辑权限就 return event ,若没有编辑权限则不 return 任何东西。所有客制化开发(含外挂)都遵循此原则时,就可以避免「该 return event 的状况却因为别的程式没有 return 而发生冲突」的问题。
以下是 【客制化程式码2】 的改良方案(完整范例):
(() => {
\'use strict\'
kintone.events.on(\'app.record.detail.process.proceed\', async event => {
const action = event.action.value
// 检查执行者是否有编辑权限
let isEditable = false
try {
// 使用「获取执行API的用户记录的存取权限」的 REST API
const res = await getUserPermission()
const right = res.rights[0]
isEditable = right.record?.editable || false
} catch (error) {
console.error(error)
}
// 送审时,检查申请内容字数
if (action === \'送审\') {
const content = event.record[\'申请内容\']?.value || \'\'
if (content.trim().length < 5) {
window.alert(\'申请内容不得少于 5 字!\')
return false
}
}
if (isEditable) {
return event // 有编辑权限时,return event
} else {
return // 无编辑权限时,不 return 任何东西
}
})
// 执行 REST API 的 function
async function getUserPermission() {
try {
const baseUrl = window.location.origin + \'/k/v1/records/acl/evaluate.json?\'
const paramString = new URLSearchParams({
app: kintone.app.getId(),
ids: [kintone.app.record.getId()]
})
const res = await fetch(baseUrl + paramString, {
method: \'GET\',
headers: {
\'X-Requested-With\': \'XMLHttpRequest\'
}
})
const data = await res.json()
if (!res.ok) {
console.error(\'API error\', data)
throw new Error(\'API error\')
}
return data
} catch (error) {
throw error
}
}
})()
结语
平心而论,process.proceed 是一个使用起来有点麻烦的事件,因为如果没有考虑到 return event 的权限问题时,就很容易雷到别人(或是被别人雷到)⋯⋯针对这个议题,我们已经提出改善的建议,日后或许有机会优化。