fix: 彻底解耦出库/借库权限联动 + 路由去硬编码roles + 补API权限保护
根因: 借库选单页面14处硬编码outbound_selection:operation, 导致两模块权限联动 修复: - borrow/apply: 14处outbound_selection:operation→op_borrow_apply:operation - stock.py: 拆分_do_get_stock_list裸逻辑, 出库/借库各绑独立权限码 - transactions: 新增/borrow/stock-list端点(@permission(op_borrow_apply)) - transaction.ts: 新增getBorrowStockList前端API函数 - outbound.py: 出库审批4端点改用独立outbound_approval权限码 - transactions.py: 借库审批3端点补@permission(op_borrow_approval) - purchase.py: 采购管理7端点补@permission(inbound_buy) - audit.py: 审计日志补@permission(system_audit) - router: 清除全部6处硬编码roles, 交由动态权限树控制
This commit is contained in:
@ -209,15 +209,9 @@ def get_all_stock():
|
||||
# ==============================================================================
|
||||
# 分页库存查询接口(服务端分页,出库/盘点/借用模块共用)
|
||||
# ==============================================================================
|
||||
@bp.route('/list', methods=['GET'])
|
||||
@jwt_required()
|
||||
def get_stock_list():
|
||||
def _do_get_stock_list():
|
||||
"""
|
||||
分页获取库存列表(stock_quantity > 0)
|
||||
参数:
|
||||
page - 页码(默认 1)
|
||||
pageSize - 每页条数(默认 20)
|
||||
keyword - 搜索关键字(模糊匹配名称/规格/SKU)
|
||||
分页获取库存列表(stock_quantity > 0) — 裸逻辑,供各模块复用
|
||||
"""
|
||||
try:
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@ -352,6 +346,14 @@ def get_stock_list():
|
||||
return jsonify({'msg': f'获取库存列表失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
@bp.route('/list', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_selection')
|
||||
def get_stock_list():
|
||||
"""出库选单专用库存列表"""
|
||||
return _do_get_stock_list()
|
||||
|
||||
|
||||
# --- 草稿箱接口 ---
|
||||
|
||||
@bp.route('/draft/list', methods=['GET'])
|
||||
|
||||
@ -234,7 +234,7 @@ def get_current_user_info():
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request', methods=['POST'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
@permission_required('outbound_approval')
|
||||
def create_outbound_request():
|
||||
"""
|
||||
创建出库审批单(申请阶段,用户只需提交宏观物料信息,无需关联具体库存记录)
|
||||
@ -323,7 +323,7 @@ def create_outbound_request():
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request/<int:request_id>/approve', methods=['PATCH'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
@permission_required('outbound_approval')
|
||||
def approve_outbound_request(request_id):
|
||||
"""
|
||||
审批出库申请
|
||||
@ -377,7 +377,7 @@ def approve_outbound_request(request_id):
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
@permission_required('outbound_approval')
|
||||
def get_outbound_request_list():
|
||||
"""
|
||||
获取出库审批单列表
|
||||
@ -424,7 +424,7 @@ def get_outbound_request_list():
|
||||
# --------------------------------------------------------
|
||||
@outbound_bp.route('/request/<int:request_id>', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('outbound_list')
|
||||
@permission_required('outbound_approval')
|
||||
def get_outbound_request_detail(request_id):
|
||||
"""获取出库审批单详情"""
|
||||
try:
|
||||
|
||||
@ -155,6 +155,7 @@ def get_records():
|
||||
# --- 提交借库申请 ---
|
||||
@trans_bp.route('/borrow/request', methods=['POST'])
|
||||
@jwt_required()
|
||||
@permission_required('op_borrow_approval')
|
||||
def submit_borrow_request():
|
||||
"""
|
||||
提交借库申请(仅存储意向,不扣库存)
|
||||
@ -216,6 +217,7 @@ def submit_borrow_request():
|
||||
# --- 审批借库申请 ---
|
||||
@trans_bp.route('/borrow/request/<int:request_id>/approve', methods=['PATCH'])
|
||||
@jwt_required()
|
||||
@permission_required('op_borrow_approval')
|
||||
def approve_borrow_request(request_id):
|
||||
"""
|
||||
审批借库申请
|
||||
@ -257,6 +259,7 @@ def approve_borrow_request(request_id):
|
||||
# --- 获取借库审批单列表 ---
|
||||
@trans_bp.route('/borrow/request', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('op_borrow_approval')
|
||||
def get_borrow_request_list():
|
||||
"""
|
||||
获取借库审批单列表
|
||||
@ -282,6 +285,16 @@ def get_borrow_request_list():
|
||||
return jsonify({'code': 500, 'msg': str(e)}), 500
|
||||
|
||||
|
||||
# --- 借库选单:库存查询(独立权限)---
|
||||
@trans_bp.route('/borrow/stock-list', methods=['GET'])
|
||||
@jwt_required()
|
||||
@permission_required('op_borrow_apply')
|
||||
def get_borrow_stock_list():
|
||||
"""借库选单专用库存列表,与出库选单共享底层逻辑"""
|
||||
from app.api.v1.inbound.stock import _do_get_stock_list
|
||||
return _do_get_stock_list()
|
||||
|
||||
|
||||
# --- 执行借库扣减(审批通过后调用)---
|
||||
@trans_bp.route('/borrow/dispatch', methods=['POST'])
|
||||
@jwt_required()
|
||||
|
||||
@ -174,6 +174,17 @@ export function approveBorrowRequest(id: number, data: { action: 'approve' | 're
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 借库选单专用库存列表(独立权限 op_borrow_apply)
|
||||
*/
|
||||
export function getBorrowStockList(params: { page?: number; pageSize?: number; keyword?: string }) {
|
||||
return request({
|
||||
url: '/v1/transactions/borrow/stock-list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行借库扣减(审批通过后调用)
|
||||
* @param data approval_id + 扫码选中的物品 + 借用人信息 + 签名
|
||||
|
||||
@ -164,8 +164,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@/views/outbound/approval/index.vue'),
|
||||
meta: {
|
||||
title: '出库审批',
|
||||
icon: 'Stamp',
|
||||
roles: ['SUPER_ADMIN', 'SUPERVISOR']
|
||||
icon: 'Stamp'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -239,8 +238,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@/views/borrow/approval/index.vue'),
|
||||
meta: {
|
||||
title: '借库审批',
|
||||
icon: 'Stamp',
|
||||
roles: ['SUPER_ADMIN', 'SUPERVISOR']
|
||||
icon: 'Stamp'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -276,9 +274,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
redirect: '/system/user-create',
|
||||
meta: {
|
||||
title: '系统管理',
|
||||
icon: 'Setting',
|
||||
// [修复] 使用大写角色名,匹配后端常量
|
||||
roles: ['SUPER_ADMIN', 'SUPERVISOR']
|
||||
icon: 'Setting'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@ -287,8 +283,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@/views/system/UserCreate.vue'),
|
||||
meta: {
|
||||
title: '账号开通',
|
||||
icon: 'User',
|
||||
roles: ['SUPER_ADMIN', 'SUPERVISOR']
|
||||
icon: 'User'
|
||||
}
|
||||
},
|
||||
// [新增] 权限分配页面,只有超级管理员可进
|
||||
@ -298,8 +293,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@/views/system/PermissionConfig.vue'),
|
||||
meta: {
|
||||
title: '权限分配',
|
||||
icon: 'Lock',
|
||||
roles: ['SUPER_ADMIN', 'SUPERVISOR']
|
||||
icon: 'Lock'
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -308,8 +302,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('@/views/system/AuditLog.vue'),
|
||||
meta: {
|
||||
title: '审计日志',
|
||||
icon: 'Document',
|
||||
roles: ['SUPER_ADMIN', 'SUPERVISOR']
|
||||
icon: 'Document'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -19,25 +19,25 @@
|
||||
</template>
|
||||
<!-- 普通模式 -->
|
||||
<template v-else>
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="warning" plain :disabled="selectedItems.length === 0" @click="isBulkMode = true">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="warning" plain :disabled="selectedItems.length === 0" @click="isBulkMode = true">
|
||||
批量操作
|
||||
</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="danger" :disabled="selectedItems.length === 0" @click="clearAll">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="danger" :disabled="selectedItems.length === 0" @click="clearAll">
|
||||
清空列表
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" :icon="Plus" @click="openManualSelect">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="primary" :icon="Plus" @click="openManualSelect">
|
||||
手动添加库存
|
||||
</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="warning" :icon="List" @click="openBomSelect">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="warning" :icon="List" @click="openBomSelect">
|
||||
按 BOM 套餐添加
|
||||
</el-button>
|
||||
</template>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="success" :icon="Printer" :disabled="selectedItems.length === 0" @click="handlePreview">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="success" :icon="Printer" :disabled="selectedItems.length === 0" @click="handlePreview">
|
||||
生成预览 & 打印
|
||||
</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" :icon="Plus" :disabled="selectedItems.length === 0" @click="openRequestDialog">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="primary" :icon="Plus" :disabled="selectedItems.length === 0" @click="openRequestDialog">
|
||||
提交借库申请
|
||||
</el-button>
|
||||
</div>
|
||||
@ -98,7 +98,7 @@
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
controls-position="right"
|
||||
:disabled="!userStore.hasPermission('outbound_selection:operation')"
|
||||
:disabled="!userStore.hasPermission('op_borrow_apply:operation')"
|
||||
@change="(val) => handleMainQuantityChange(val, row)"
|
||||
/>
|
||||
</template>
|
||||
@ -106,7 +106,7 @@
|
||||
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button v-if="!isBulkMode && userStore.hasPermission('outbound_selection:operation')" type="danger" link @click="removeRow($index)">移除</el-button>
|
||||
<el-button v-if="!isBulkMode && userStore.hasPermission('op_borrow_apply:operation')" type="danger" link @click="removeRow($index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -162,7 +162,7 @@
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
placeholder="0"
|
||||
:disabled="!userStore.hasPermission('outbound_selection:operation')"
|
||||
:disabled="!userStore.hasPermission('op_borrow_apply:operation')"
|
||||
@click.stop
|
||||
@change="(val) => handleManualQuantityChange(val, row)"
|
||||
/>
|
||||
@ -185,7 +185,7 @@
|
||||
已勾选 {{ tempSelection.length }} 项
|
||||
</span>
|
||||
<el-button @click="manualDialogVisible = false">取消</el-button>
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" @click="confirmManualAdd">确认添加</el-button>
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="primary" @click="confirmManualAdd">确认添加</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@ -202,7 +202,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="借库套数">
|
||||
<el-input-number v-model="bomSets" :min="1" label="套" style="width: 200px;" :disabled="!userStore.hasPermission('outbound_selection:operation')" />
|
||||
<el-input-number v-model="bomSets" :min="1" label="套" style="width: 200px;" :disabled="!userStore.hasPermission('op_borrow_apply:operation')" />
|
||||
<el-tag v-if="selectedBomNo && maxBuildableSets >= 0" type="success" style="margin-left: 16px;">
|
||||
当前库存最多可成套借库: {{ maxBuildableSets }} 套
|
||||
</el-tag>
|
||||
@ -248,7 +248,7 @@
|
||||
<template #footer>
|
||||
<el-button @click="bomSelectVisible = false">取消</el-button>
|
||||
<el-button
|
||||
v-if="userStore.hasPermission('outbound_selection:operation')"
|
||||
v-if="userStore.hasPermission('op_borrow_apply:operation')"
|
||||
type="primary"
|
||||
@click="confirmBomAdd"
|
||||
>
|
||||
@ -288,11 +288,11 @@
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="previewVisible = false">取消</el-button>
|
||||
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="warning" :icon="Download" :loading="exportLoading" @click="confirmExport">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="warning" :icon="Download" :loading="exportLoading" @click="confirmExport">
|
||||
导出 Excel
|
||||
</el-button>
|
||||
|
||||
<el-button v-if="userStore.hasPermission('outbound_selection:operation')" type="primary" :icon="Printer" :loading="printLoading" @click="confirmPrint">
|
||||
<el-button v-if="userStore.hasPermission('op_borrow_apply:operation')" type="primary" :icon="Printer" :loading="printLoading" @click="confirmPrint">
|
||||
确认打印 (A4)
|
||||
</el-button>
|
||||
</span>
|
||||
@ -440,9 +440,9 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Printer, Search, Plus, Download, List } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElTable, ElMessageBox } from 'element-plus'
|
||||
import { getStockList, printSelectionList } from '@/api/inbound/stock'
|
||||
import { printSelectionList } from '@/api/inbound/stock'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { submitBorrowRequest } from '@/api/transaction'
|
||||
import { submitBorrowRequest, getBorrowStockList } from '@/api/transaction'
|
||||
import { getApproversList } from '@/api/auth'
|
||||
import { getBomList, getBomDetail, getBomWithStock } from '@/api/bom'
|
||||
|
||||
@ -583,7 +583,7 @@ const getTypeTag = (type: string) => {
|
||||
const loadStockList = async () => {
|
||||
stockLoading.value = true
|
||||
try {
|
||||
const res: any = await getStockList({
|
||||
const res: any = await getBorrowStockList({
|
||||
page: stockPage.value,
|
||||
pageSize: stockPageSize.value,
|
||||
keyword: searchKeyword.value.trim(),
|
||||
@ -610,7 +610,7 @@ const loadAllStockForBom = async () => {
|
||||
let page = 1
|
||||
const pageSize = 200
|
||||
while (true) {
|
||||
const res: any = await getStockList({
|
||||
const res: any = await getBorrowStockList({
|
||||
page,
|
||||
pageSize,
|
||||
is_aggregated: true
|
||||
|
||||
Reference in New Issue
Block a user