fix: align disposal flow with administrative role
This commit is contained in:
parent
02b637064c
commit
843babd381
@ -290,19 +290,36 @@ test('并发审批 CAS 失败时返回 409,不追加动作也不改资产', as
|
||||
}
|
||||
})
|
||||
|
||||
test('行政不能审批自己发起的报废申请', async () => {
|
||||
test('行政可以审批自己发起的报废申请', async () => {
|
||||
const originalTransaction = prisma.$transaction
|
||||
let updateCount = 0
|
||||
const originalLogCreate = prisma.operationLog.create
|
||||
let updatedData: Record<string, any> | undefined
|
||||
let actionData: Record<string, any> | undefined
|
||||
|
||||
;(prisma.$transaction as any) = async (callback: any) => callback({
|
||||
disposalRecord: {
|
||||
findUnique: async () => ({ id: 94, applied_by: 6, items: [] }),
|
||||
updateMany: async () => {
|
||||
updateCount += 1
|
||||
findUnique: async () => ({
|
||||
id: 94,
|
||||
equipment_id: 10,
|
||||
equipment_code: rootEquipment.equipment_code,
|
||||
applied_by: 6,
|
||||
status: 'pending',
|
||||
items: []
|
||||
}),
|
||||
updateMany: async (args: any) => {
|
||||
updatedData = args.data
|
||||
return { count: 1 }
|
||||
}
|
||||
}
|
||||
},
|
||||
disposalAction: {
|
||||
create: async (args: any) => {
|
||||
actionData = args.data
|
||||
return { id: 1 }
|
||||
}
|
||||
},
|
||||
user: { findMany: async () => [] }
|
||||
})
|
||||
;(prisma.operationLog.create as any) = async () => ({ id: 1 })
|
||||
|
||||
try {
|
||||
const { res, state } = createResponse('administrative')
|
||||
@ -311,11 +328,15 @@ test('行政不能审批自己发起的报废申请', async () => {
|
||||
res as any
|
||||
)
|
||||
|
||||
assert.equal(state.statusCode, 403)
|
||||
assert.match(state.body.message, /不能审批自己发起/)
|
||||
assert.equal(updateCount, 0)
|
||||
assert.equal(state.statusCode, 200)
|
||||
assert.equal(state.body.message, '审批通过')
|
||||
assert.equal(updatedData?.status, 'approved')
|
||||
assert.equal(updatedData?.approved_by, 6)
|
||||
assert.equal(actionData?.action_type, 'approved')
|
||||
assert.equal(actionData?.operator_id, 6)
|
||||
} finally {
|
||||
;(prisma.$transaction as any) = originalTransaction
|
||||
;(prisma.operationLog.create as any) = originalLogCreate
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -563,10 +563,6 @@ export const approveDisposal = async (req: Request, res: Response) => {
|
||||
include: { items: { include: { equipment: true } } }
|
||||
})
|
||||
if (!disposal) throw Object.assign(new Error('报废记录不存在'), { status: 404 })
|
||||
const currentUser = getCurrentUser(res)
|
||||
if (currentUser.role === 'administrative' && disposal.applied_by === userId) {
|
||||
throw Object.assign(new Error('行政不能审批自己发起的报废申请,请由其他行政或管理员处理'), { status: 403 })
|
||||
}
|
||||
const changedItem = disposal.items.find((item) =>
|
||||
item.equipment.status !== '待报废' ||
|
||||
item.equipment.parent_id !== item.parent_equipment_id
|
||||
|
||||
@ -10,27 +10,29 @@ test('管理员拥有完整权限集合', () => {
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_VIEW_OWN))
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_PRINT_LABELS))
|
||||
})
|
||||
test('行政可以登记、发起报废和审批,但不能借还、调拨或挂载', () => {
|
||||
test('行政可以登记并负责报废全流程,但不能借还、调拨或挂载', () => {
|
||||
const permissions = getPermissionsForRole('administrative')
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_CREATE))
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_CORRECT))
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_PRINT_LABELS))
|
||||
assert.ok(permissions.includes(PERMISSIONS.DISPOSALS_APPLY))
|
||||
assert.ok(permissions.includes(PERMISSIONS.DISPOSALS_APPROVE))
|
||||
assert.ok(permissions.includes(PERMISSIONS.DISPOSALS_COMPLETE))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.ASSETS_BORROW))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.ASSETS_TRANSFER))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.ASSETS_MOUNT))
|
||||
})
|
||||
|
||||
test('IT可以直接维护资产资料、流转、发起报废和盘点,但不能审批或管理用户', () => {
|
||||
test('IT可以直接维护资产资料、借还、调拨和盘点,但不能处理报废或管理用户', () => {
|
||||
const permissions = getPermissionsForRole('it')
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_CORRECT))
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_BORROW))
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_TRANSFER))
|
||||
assert.ok(permissions.includes(PERMISSIONS.ASSETS_PRINT_LABELS))
|
||||
assert.ok(permissions.includes(PERMISSIONS.DISPOSALS_APPLY))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.DISPOSALS_APPLY))
|
||||
assert.ok(permissions.includes(PERMISSIONS.INVENTORY_MANAGE))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.DISPOSALS_APPROVE))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.DISPOSALS_COMPLETE))
|
||||
assert.ok(!permissions.includes(PERMISSIONS.USERS_MANAGE))
|
||||
})
|
||||
|
||||
|
||||
@ -52,7 +52,6 @@ const ROLE_PERMISSIONS: Record<UserRole, readonly Permission[]> = {
|
||||
PERMISSIONS.ASSETS_MAINTENANCE,
|
||||
PERMISSIONS.ASSETS_MOUNT,
|
||||
PERMISSIONS.ASSETS_PRINT_LABELS,
|
||||
PERMISSIONS.DISPOSALS_APPLY,
|
||||
PERMISSIONS.INVENTORY_MANAGE,
|
||||
PERMISSIONS.NOTIFICATIONS_VIEW,
|
||||
PERMISSIONS.REPORTS_VIEW
|
||||
|
||||
@ -108,7 +108,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/workflows/DisposalList.vue'),
|
||||
meta: {
|
||||
title: '报废流程',
|
||||
anyPermissions: [PERMISSIONS.DISPOSALS_APPLY, PERMISSIONS.DISPOSALS_APPROVE, PERMISSIONS.DISPOSALS_COMPLETE]
|
||||
anyPermissions: [
|
||||
PERMISSIONS.DISPOSALS_APPLY,
|
||||
PERMISSIONS.DISPOSALS_APPROVE,
|
||||
PERMISSIONS.DISPOSALS_COMPLETE,
|
||||
PERMISSIONS.NOTIFICATIONS_VIEW
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -117,7 +122,12 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/workflows/DisposalDetail.vue'),
|
||||
meta: {
|
||||
title: '报废详情',
|
||||
anyPermissions: [PERMISSIONS.DISPOSALS_APPLY, PERMISSIONS.DISPOSALS_APPROVE, PERMISSIONS.DISPOSALS_COMPLETE]
|
||||
anyPermissions: [
|
||||
PERMISSIONS.DISPOSALS_APPLY,
|
||||
PERMISSIONS.DISPOSALS_APPROVE,
|
||||
PERMISSIONS.DISPOSALS_COMPLETE,
|
||||
PERMISSIONS.NOTIFICATIONS_VIEW
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -12,15 +12,14 @@ const ACTIONS_BY_ROLE: Record<UserRole, DashboardRoleAction[]> = {
|
||||
{ id: 'asset-create', label: '资产入库', description: '登记单台资产和基础资料', to: '/equipment/add' },
|
||||
{ id: 'asset-import', label: '批量导入', description: '在资产管理中导入入库表格', to: '/equipment' },
|
||||
{ id: 'inventory', label: '盘点任务', description: '创建任务并跟进盘点结果', to: '/inventory' },
|
||||
{ id: 'disposal-approval', label: '报废审批', description: '处理 IT 发起的报废申请', to: '/workflows/disposals?scope=pending' }
|
||||
{ id: 'disposal-approval', label: '报废处理', description: '审批并完成报废;申请请从资产列表发起', to: '/workflows/disposals?scope=pending' }
|
||||
],
|
||||
it: [
|
||||
{ id: 'borrow', label: '资产借出', description: '扫码选择使用者并完成出库', to: '/borrow/scan' },
|
||||
{ id: 'return', label: '资产归还', description: '扫码归还资产及随借配件', to: '/return/scan' },
|
||||
{ id: 'transfer', label: '调拨与闲置', description: '在资产管理中选择资产后调拨', to: '/equipment' },
|
||||
{ id: 'inventory', label: '发起盘点', description: '按人员或组织派发手机盘点任务', to: '/inventory' },
|
||||
{ id: 'maintenance', label: '维修管理', description: '登记、完成或取消维修', to: '/maintenance' },
|
||||
{ id: 'disposal-apply', label: '发起报废', description: '在资产详情中选择整机及配件', to: '/equipment' }
|
||||
{ id: 'maintenance', label: '维修管理', description: '登记、完成或取消维修', to: '/maintenance' }
|
||||
],
|
||||
admin: [
|
||||
{ id: 'system', label: '系统设置', description: '管理用户、角色、同步和系统配置', to: '/settings' },
|
||||
|
||||
@ -638,7 +638,8 @@ const canApplyDisposal = computed(() => authStore.can(PERMISSIONS.DISPOSALS_APPL
|
||||
const canViewDisposals = computed(() => authStore.canAny([
|
||||
PERMISSIONS.DISPOSALS_APPLY,
|
||||
PERMISSIONS.DISPOSALS_APPROVE,
|
||||
PERMISSIONS.DISPOSALS_COMPLETE
|
||||
PERMISSIONS.DISPOSALS_COMPLETE,
|
||||
PERMISSIONS.NOTIFICATIONS_VIEW
|
||||
]))
|
||||
const canPrintLabels = computed(() => authStore.can(PERMISSIONS.ASSETS_PRINT_LABELS))
|
||||
const canOverrideCorrectionRestrictions = computed(() => authStore.can(PERMISSIONS.SYSTEM_SETTINGS))
|
||||
|
||||
@ -337,10 +337,11 @@ const authStore = useAuthStore()
|
||||
const canApprove = computed(() => authStore.can(PERMISSIONS.DISPOSALS_APPROVE))
|
||||
const canComplete = computed(() => authStore.can(PERMISSIONS.DISPOSALS_COMPLETE))
|
||||
const canApply = computed(() => authStore.can(PERMISSIONS.DISPOSALS_APPLY))
|
||||
const canViewHistory = computed(() => authStore.can(PERMISSIONS.NOTIFICATIONS_VIEW))
|
||||
const availableScopes = computed<Array<{ value: DisposalScope; label: string }>>(() => {
|
||||
const scopes: Array<{ value: DisposalScope; label: string }> = []
|
||||
if (canApprove.value) scopes.push({ value: 'pending', label: '待我审批' })
|
||||
if (canApply.value) scopes.push({ value: 'mine', label: '我发起的' })
|
||||
if (canApply.value || canViewHistory.value) scopes.push({ value: 'mine', label: '我发起的' })
|
||||
if (canApprove.value || canComplete.value) scopes.push({ value: 'processed', label: '已处理' })
|
||||
if (authStore.can(PERMISSIONS.SYSTEM_SETTINGS)) scopes.push({ value: 'all', label: '全部' })
|
||||
return scopes
|
||||
@ -414,10 +415,7 @@ const openDetail = (item: DisposalRecord) => router.push(`/workflows/disposals/$
|
||||
const hasMobileSecondaryActions = (item: DisposalRecord) =>
|
||||
(item.status === 'pending' && canApproveItem(item)) || (item.status === 'approved' && canComplete.value)
|
||||
|
||||
const canApproveItem = (item: DisposalRecord) => !(
|
||||
!canApprove.value ||
|
||||
(authStore.user?.role === 'administrative' && Number(item.applied_by) === Number(authStore.user?.id))
|
||||
)
|
||||
const canApproveItem = (_item: DisposalRecord) => canApprove.value
|
||||
|
||||
const handleMobileOpenDetail = () => {
|
||||
const item = mobileActionItem.value
|
||||
|
||||
@ -159,10 +159,7 @@ const completeForm = ref({ actual_value: 0, completion_notes: '' })
|
||||
|
||||
const canApprove = computed(() => authStore.can(PERMISSIONS.DISPOSALS_APPROVE))
|
||||
const canComplete = computed(() => authStore.can(PERMISSIONS.DISPOSALS_COMPLETE))
|
||||
const canApproveRecord = computed(() => !(
|
||||
!canApprove.value ||
|
||||
(authStore.user?.role === 'administrative' && Number(record.value?.applied_by) === Number(authStore.user?.id))
|
||||
))
|
||||
const canApproveRecord = computed(() => canApprove.value)
|
||||
const showActions = computed(() =>
|
||||
(record.value?.status === 'pending' && canApproveRecord.value) ||
|
||||
(record.value?.status === 'approved' && canComplete.value)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user