feat: enforce field-level permissions for buy and service modules
Co-authored-by: aider (openai/DeepSeek-V3.2-Thinking) <aider@aider.chat>
This commit is contained in:
@ -145,6 +145,51 @@ def submit():
|
||||
if not data:
|
||||
return jsonify({"code": 400, "msg": "No data"}), 400
|
||||
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'inbound_buy:*' not in user_permissions:
|
||||
# 字段名到权限码的映射(与前端 permissionMap 保持一致)
|
||||
field_to_perm = {
|
||||
'id': 'inbound_buy:id',
|
||||
'base_id': 'inbound_buy:base_id',
|
||||
'global_print_id': 'inbound_buy:global_print_id',
|
||||
'sku': 'inbound_buy:sku',
|
||||
'barcode': 'inbound_buy:barcode',
|
||||
'in_date': 'inbound_buy:in_date',
|
||||
'serial_number': 'inbound_buy:serial_number',
|
||||
'batch_number': 'inbound_buy:batch_number',
|
||||
'status': 'inbound_buy:status',
|
||||
'in_quantity': 'inbound_buy:in_quantity',
|
||||
'stock_quantity': 'inbound_buy:stock_quantity',
|
||||
'available_quantity': 'inbound_buy:available_quantity',
|
||||
'inspection_status': 'inbound_buy:inspection_status',
|
||||
'warehouse_location': 'inbound_buy:warehouse_location',
|
||||
'unit_price': 'inbound_buy:unit_price',
|
||||
'tax_rate': 'inbound_buy:tax_rate',
|
||||
'total_price': 'inbound_buy:total_price',
|
||||
'currency': 'inbound_buy:currency',
|
||||
'exchange_rate': 'inbound_buy:exchange_rate',
|
||||
'supplier_name': 'inbound_buy:supplier_name',
|
||||
'buyer_name': 'inbound_buy:buyer_name',
|
||||
'buyer_email': 'inbound_buy:buyer_email',
|
||||
'original_link': 'inbound_buy:original_link',
|
||||
'detail_link': 'inbound_buy:detail_link',
|
||||
'arrival_photo': 'inbound_buy:arrival_photo',
|
||||
'inspection_report': 'inbound_buy:inspection_report',
|
||||
'material_name': 'inbound_buy:material_name',
|
||||
'spec_model': 'inbound_buy:spec_model',
|
||||
'category': 'inbound_buy:category',
|
||||
'unit': 'inbound_buy:unit',
|
||||
'material_type': 'inbound_buy:material_type',
|
||||
'company_name': 'inbound_buy:company_name',
|
||||
}
|
||||
# 复制一份,避免遍历时修改字典
|
||||
for field in list(data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
data.pop(field, None)
|
||||
|
||||
new_stock = BuyInboundService.handle_inbound(data)
|
||||
|
||||
return jsonify({
|
||||
@ -165,6 +210,50 @@ def submit():
|
||||
def update_buy(id):
|
||||
try:
|
||||
data = request.get_json()
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'inbound_buy:*' not in user_permissions:
|
||||
field_to_perm = {
|
||||
'id': 'inbound_buy:id',
|
||||
'base_id': 'inbound_buy:base_id',
|
||||
'global_print_id': 'inbound_buy:global_print_id',
|
||||
'sku': 'inbound_buy:sku',
|
||||
'barcode': 'inbound_buy:barcode',
|
||||
'in_date': 'inbound_buy:in_date',
|
||||
'serial_number': 'inbound_buy:serial_number',
|
||||
'batch_number': 'inbound_buy:batch_number',
|
||||
'status': 'inbound_buy:status',
|
||||
'in_quantity': 'inbound_buy:in_quantity',
|
||||
'stock_quantity': 'inbound_buy:stock_quantity',
|
||||
'available_quantity': 'inbound_buy:available_quantity',
|
||||
'inspection_status': 'inbound_buy:inspection_status',
|
||||
'warehouse_location': 'inbound_buy:warehouse_location',
|
||||
'unit_price': 'inbound_buy:unit_price',
|
||||
'tax_rate': 'inbound_buy:tax_rate',
|
||||
'total_price': 'inbound_buy:total_price',
|
||||
'currency': 'inbound_buy:currency',
|
||||
'exchange_rate': 'inbound_buy:exchange_rate',
|
||||
'supplier_name': 'inbound_buy:supplier_name',
|
||||
'buyer_name': 'inbound_buy:buyer_name',
|
||||
'buyer_email': 'inbound_buy:buyer_email',
|
||||
'original_link': 'inbound_buy:original_link',
|
||||
'detail_link': 'inbound_buy:detail_link',
|
||||
'arrival_photo': 'inbound_buy:arrival_photo',
|
||||
'inspection_report': 'inbound_buy:inspection_report',
|
||||
'material_name': 'inbound_buy:material_name',
|
||||
'spec_model': 'inbound_buy:spec_model',
|
||||
'category': 'inbound_buy:category',
|
||||
'unit': 'inbound_buy:unit',
|
||||
'material_type': 'inbound_buy:material_type',
|
||||
'company_name': 'inbound_buy:company_name',
|
||||
}
|
||||
# 复制一份,避免遍历时修改字典
|
||||
for field in list(data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
data.pop(field, None)
|
||||
|
||||
BuyInboundService.update_inbound(id, data)
|
||||
return jsonify({"code": 200, "msg": "更新成功"})
|
||||
except Exception as e:
|
||||
|
||||
@ -118,6 +118,31 @@ def create_service():
|
||||
if not data:
|
||||
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
||||
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'inbound_service:*' not in user_permissions:
|
||||
# 字段名到权限码的映射(与前端 permissionMap 保持一致)
|
||||
field_to_perm = {
|
||||
'id': 'inbound_service:id',
|
||||
'base_id': 'inbound_service:base_id',
|
||||
'sku': 'inbound_service:sku',
|
||||
'material_name': 'inbound_service:material_name',
|
||||
'provider_name': 'inbound_service:provider_name',
|
||||
'sale_price': 'inbound_service:sale_price',
|
||||
'description': 'inbound_service:description',
|
||||
'created_at': 'inbound_service:created_at',
|
||||
'material_type': 'inbound_service:material_type',
|
||||
'category': 'inbound_service:category',
|
||||
'spec_model': 'inbound_service:spec_model',
|
||||
'unit': 'inbound_service:unit',
|
||||
}
|
||||
# 复制一份,避免遍历时修改字典
|
||||
for field in list(data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
data.pop(field, None)
|
||||
|
||||
# 基础校验
|
||||
if not data.get('base_id'):
|
||||
return jsonify({'code': 400, 'msg': '请选择基础物料'}), 400
|
||||
@ -169,6 +194,31 @@ def update_service(service_id):
|
||||
if not data:
|
||||
return jsonify({'code': 400, 'msg': '请求数据为空'}), 400
|
||||
|
||||
# 数据清洗:移除用户没有权限的字段
|
||||
user_permissions = get_current_user_permissions()
|
||||
# 超级管理员不过滤
|
||||
if 'inbound_service:*' not in user_permissions:
|
||||
# 字段名到权限码的映射(与前端 permissionMap 保持一致)
|
||||
field_to_perm = {
|
||||
'id': 'inbound_service:id',
|
||||
'base_id': 'inbound_service:base_id',
|
||||
'sku': 'inbound_service:sku',
|
||||
'material_name': 'inbound_service:material_name',
|
||||
'provider_name': 'inbound_service:provider_name',
|
||||
'sale_price': 'inbound_service:sale_price',
|
||||
'description': 'inbound_service:description',
|
||||
'created_at': 'inbound_service:created_at',
|
||||
'material_type': 'inbound_service:material_type',
|
||||
'category': 'inbound_service:category',
|
||||
'spec_model': 'inbound_service:spec_model',
|
||||
'unit': 'inbound_service:unit',
|
||||
}
|
||||
# 复制一份,避免遍历时修改字典
|
||||
for field in list(data.keys()):
|
||||
perm_code = field_to_perm.get(field)
|
||||
if perm_code and perm_code not in user_permissions:
|
||||
data.pop(field, None)
|
||||
|
||||
# 允许更新的字段
|
||||
allowed_fields = {
|
||||
'sale_price', 'provider_name', 'description',
|
||||
|
||||
@ -272,12 +272,12 @@
|
||||
|
||||
<div class="read-only-grid">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="所属公司"><el-input v-model="form.company_name" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型"><el-input v-model="form.material_type" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别"><el-input v-model="form.category" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格型号"><el-input v-model="form.spec_model" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="所属公司" v-if="hasFormFieldPermission('company_name')"><el-input v-model="form.company_name" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="名称" v-if="hasFormFieldPermission('material_name')"><el-input v-model="form.material_name" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型" v-if="hasFormFieldPermission('material_type')"><el-input v-model="form.material_type" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别" v-if="hasFormFieldPermission('category')"><el-input v-model="form.category" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格型号" v-if="hasFormFieldPermission('spec_model')"><el-input v-model="form.spec_model" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位" v-if="hasFormFieldPermission('unit')"><el-input v-model="form.unit" readonly class="is-text-view"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
@ -611,6 +611,55 @@ const vLoadmore = {
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// 表单字段权限检查
|
||||
// ------------------------------------
|
||||
const hasFormFieldPermission = (fieldName: string) => {
|
||||
// 超级管理员直接返回true
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
// 根据字段名映射到权限码
|
||||
const map: Record<string, string> = {
|
||||
company_name: 'inbound_buy:company_name',
|
||||
material_name: 'inbound_buy:material_name',
|
||||
spec_model: 'inbound_buy:spec_model',
|
||||
category: 'inbound_buy:category',
|
||||
material_type: 'inbound_buy:material_type',
|
||||
unit: 'inbound_buy:unit',
|
||||
sku: 'inbound_buy:sku',
|
||||
barcode: 'inbound_buy:barcode',
|
||||
in_date: 'inbound_buy:in_date',
|
||||
serial_number: 'inbound_buy:serial_number',
|
||||
batch_number: 'inbound_buy:batch_number',
|
||||
status: 'inbound_buy:status',
|
||||
inspection_status: 'inbound_buy:inspection_status',
|
||||
in_quantity: 'inbound_buy:in_quantity',
|
||||
stock_quantity: 'inbound_buy:stock_quantity',
|
||||
available_quantity: 'inbound_buy:available_quantity',
|
||||
warehouse_location: 'inbound_buy:warehouse_location',
|
||||
unit_price: 'inbound_buy:unit_price',
|
||||
tax_rate: 'inbound_buy:tax_rate',
|
||||
total_price: 'inbound_buy:total_price',
|
||||
currency: 'inbound_buy:currency',
|
||||
exchange_rate: 'inbound_buy:exchange_rate',
|
||||
supplier_name: 'inbound_buy:supplier_name',
|
||||
purchaser: 'inbound_buy:purchaser',
|
||||
purchaser_email: 'inbound_buy:purchaser_email',
|
||||
source_link: 'inbound_buy:original_link',
|
||||
detail_link: 'inbound_buy:detail_link',
|
||||
arrival_photo: 'inbound_buy:arrival_photo',
|
||||
inspection_report: 'inbound_buy:inspection_report',
|
||||
print_copies: 'inbound_buy:print_copies',
|
||||
}
|
||||
const code = map[fieldName]
|
||||
if (!code) {
|
||||
// 没有映射的字段默认显示
|
||||
return true
|
||||
}
|
||||
return userStore.hasPermission(code)
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// 状态与变量
|
||||
// ------------------------------------
|
||||
|
||||
@ -134,11 +134,11 @@
|
||||
|
||||
<div class="read-only-grid" v-if="form.base_id">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型"><el-input v-model="form.material_type" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别"><el-input v-model="form.category" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格型号"><el-input v-model="form.spec_model" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="名称" v-if="hasFormFieldPermission('material_name')"><el-input v-model="form.material_name" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型" v-if="hasFormFieldPermission('material_type')"><el-input v-model="form.material_type" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别" v-if="hasFormFieldPermission('category')"><el-input v-model="form.category" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格型号" v-if="hasFormFieldPermission('spec_model')"><el-input v-model="form.spec_model" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位" v-if="hasFormFieldPermission('unit')"><el-input v-model="form.unit" disabled class="is-text-view"/></el-form-item></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,7 +150,7 @@
|
||||
<span>2. 服务详情</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<el-form-item label="售价" prop="sale_price">
|
||||
<el-form-item label="售价" prop="sale_price" v-if="hasFormFieldPermission('sale_price')">
|
||||
<el-input-number
|
||||
v-model="form.sale_price"
|
||||
placeholder="请输入售价"
|
||||
@ -160,7 +160,7 @@
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务商" prop="provider_name">
|
||||
<el-form-item label="服务商" prop="provider_name" v-if="hasFormFieldPermission('provider_name')">
|
||||
<el-autocomplete
|
||||
v-model="form.provider_name"
|
||||
:fetch-suggestions="querySearchProvider"
|
||||
@ -171,7 +171,7 @@
|
||||
@select="handleProviderSelect"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介" prop="description">
|
||||
<el-form-item label="简介" prop="description" v-if="hasFormFieldPermission('description')">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
@ -234,6 +234,32 @@ const hasColumnPermission = (prop: string) => {
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
// 表单字段权限检查
|
||||
const hasFormFieldPermission = (fieldName: string) => {
|
||||
// 超级管理员直接返回true
|
||||
if (userStore.role === 'SUPER_ADMIN' || userStore.username === 'IRIS') {
|
||||
return true
|
||||
}
|
||||
// 根据字段名映射到权限码
|
||||
const map: Record<string, string> = {
|
||||
base_id: 'inbound_service:base_id',
|
||||
material_name: 'inbound_service:material_name',
|
||||
spec_model: 'inbound_service:spec_model',
|
||||
category: 'inbound_service:category',
|
||||
material_type: 'inbound_service:material_type',
|
||||
unit: 'inbound_service:unit',
|
||||
sale_price: 'inbound_service:sale_price',
|
||||
provider_name: 'inbound_service:provider_name',
|
||||
description: 'inbound_service:description',
|
||||
}
|
||||
const code = map[fieldName]
|
||||
if (!code) {
|
||||
// 没有映射的字段默认显示
|
||||
return true
|
||||
}
|
||||
return userStore.hasPermission(code)
|
||||
}
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<ServiceItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user