perf: 综合安全加固 — RBAC严格映射+异步邮件+字段权限白名单+前端对齐+导入模板
本次提交包含本会话所有修改的最终统一提交 ## 权限系统重构 - permission_service.py: 添加入库/采购操作元素 + ensure_default_permissions - field_permissions.py: 严格1-to-1 Default Deny 字段映射(StockBuy/Semi/Product/MaterialBase) - decorators.py: _expand_operation_perms 双向粒度桥接 + prevent_double_submit - deploy_production.sql: 修复 sys_element 别名码(qty_inbound→in_quantity) ## 采购模块 - purchase.py: 权限驱动可见性 + inbound_purchase独立权限 + 价格字段过滤 - purchase_service.py: 异步邮件 + 三阶段批量模糊匹配防N+1 - purchase/index.vue: canApprove严格操作权限 + upload重复修复 ## 导出/入 - base_service.py: export_excel 流式写入防OOM + get_latest_specs 优化 - import_service.py + import_api.py: Excel批量导入(模板+预览+执行) - ImportDialog.vue: 三步骤导入弹窗 ## 异步邮件 - email_service.py: send_email_async (守护线程) - inventory_task.py: send_email→send_email_async ## 前端对齐 - product/semi/buy.vue: 列对齐in_quantity/stock_quantity/available_quantity + localStorage缓存V2 - buyOdoo.vue: 排序修复 + 导入按钮 + 移除点击展开加载 - BomManage.vue: 懒加载分组 + 导入按钮 - list.vue: 导入按钮 - Selection.vue + borrow/apply: BOM匹配修复 + 导入按钮 - outbound/create.vue: 出库类型必选 - AppMain.vue: 移除transition白屏修复 - material_base.ts, outbound.ts, bom.ts, stock.ts: 新增API函数
This commit is contained in:
@ -190,8 +190,8 @@
|
||||
<span v-else class="text-placeholder">-</span>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="col.prop === 'qty_stock'">
|
||||
<span class="stock-num">{{ scope.row.qty_stock }}</span>
|
||||
<template #default="scope" v-else-if="col.prop === 'stock_quantity'">
|
||||
<span class="stock-num">{{ scope.row.stock_quantity }}</span>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="col.prop === 'status'">
|
||||
@ -754,7 +754,9 @@ const allColumns = [
|
||||
{ prop: 'sku', label: 'SKU', minWidth: '110', sortable: true },
|
||||
{ prop: 'warehouse_loc', label: '库位', minWidth: '120', sortable: true },
|
||||
{ prop: 'serial_number', label: '序列号', minWidth: '130', sortable: true },
|
||||
{ prop: 'qty_stock', label: '库存', minWidth: '90', sortable: true },
|
||||
{ prop: 'in_quantity', label: '入库数', minWidth: '90', sortable: true },
|
||||
{ prop: 'stock_quantity', label: '库存', minWidth: '90', sortable: true },
|
||||
{ prop: 'available_quantity', label: '可用', minWidth: '90', sortable: true },
|
||||
{ prop: 'status', label: '状态', minWidth: '90', sortable: true },
|
||||
{ prop: 'quality_status', label: '质量', minWidth: '90', sortable: true },
|
||||
{ prop: 'spec_model', label: '规格', minWidth: '120', sortable: true },
|
||||
@ -793,11 +795,12 @@ const permissionMap: Record<string, string> = {
|
||||
status: 'inbound_product:status',
|
||||
quality_status: 'inbound_product:quality_status',
|
||||
in_quantity: 'inbound_product:in_quantity',
|
||||
qty_stock: 'inbound_product:qty_stock',
|
||||
stock_quantity: 'inbound_product:stock_quantity',
|
||||
qty_available: 'inbound_product:qty_available',
|
||||
stock_quantity: 'inbound_product:stock_quantity',
|
||||
available_quantity: 'inbound_product:available_quantity',
|
||||
available_quantity: 'inbound_product:available_quantity',
|
||||
warehouse_location: 'inbound_product:warehouse_location',
|
||||
warehouse_loc: 'inbound_product:warehouse_location',
|
||||
bom_code: 'inbound_product:bom_code',
|
||||
bom_version: 'inbound_product:bom_version',
|
||||
work_order_code: 'inbound_product:work_order_code',
|
||||
@ -819,7 +822,7 @@ const visibleColumnProps = ref<string[]>([])
|
||||
// ================= 第三步:按依赖顺序放置方法和监听 =================
|
||||
|
||||
// 1. 获取唯一缓存 Key
|
||||
const getStorageKey = () => `MOM_INBOUND_PROD_COLS_${userStore.username || 'DEFAULT'}`;
|
||||
const getStorageKey = () => `MOM_INBOUND_PROD_COLS_V2_${userStore.username || 'DEFAULT'}`;
|
||||
|
||||
// 2. 检查列权限(依赖 permissionMap)
|
||||
const hasColumnPermission = (prop: string) => {
|
||||
@ -830,7 +833,7 @@ const hasColumnPermission = (prop: string) => {
|
||||
return code ? userStore.hasPermission(code) : false
|
||||
}
|
||||
|
||||
// 3. 初始化列权限(依赖 allColumns / hasColumnPermission / getStorageKey)
|
||||
// 3. 初始化列权限
|
||||
const initColumnPermissions = () => {
|
||||
const allowedProps = allColumns
|
||||
.filter(col => hasColumnPermission(col.prop))
|
||||
@ -841,10 +844,10 @@ const initColumnPermissions = () => {
|
||||
try {
|
||||
const parsedCache = JSON.parse(cachedData);
|
||||
const filtered = parsedCache.filter((prop: string) => allowedProps.includes(prop));
|
||||
if (filtered.length > 0) {
|
||||
visibleColumnProps.value = filtered;
|
||||
return;
|
||||
}
|
||||
// 自动补齐 allColumns 中新加但缓存中没有的列
|
||||
const newCols = allowedProps.filter(p => !filtered.includes(p));
|
||||
visibleColumnProps.value = [...filtered, ...newCols];
|
||||
return;
|
||||
} catch (e) {
|
||||
console.error('解析列缓存失败', e);
|
||||
}
|
||||
@ -892,8 +895,8 @@ const displayData = computed(() => {
|
||||
if (aggMap.has(key)) {
|
||||
const existing = aggMap.get(key)
|
||||
// 累加库存数量(原地修改已拷贝的对象,不影响原始数据)
|
||||
existing.qty_stock = (existing.qty_stock || 0) + (item.qty_stock || 0)
|
||||
existing.qty_available = (existing.qty_available || 0) + (item.qty_available || 0)
|
||||
existing.stock_quantity = (existing.stock_quantity || 0) + (item.stock_quantity || 0)
|
||||
existing.available_quantity = (existing.available_quantity || 0) + (item.available_quantity || 0)
|
||||
existing.stock_quantity = (existing.stock_quantity || 0) + (item.stock_quantity || 0)
|
||||
existing.available_quantity = (existing.available_quantity || 0) + (item.available_quantity || 0)
|
||||
existing.in_quantity = (existing.in_quantity || 0) + (item.in_quantity || 0)
|
||||
@ -910,7 +913,7 @@ const displayData = computed(() => {
|
||||
return Array.from(aggMap.values())
|
||||
})
|
||||
|
||||
const defaultVisibleCols = ['company_name', 'material_name', 'sku', 'serial_number', 'qty_stock', 'status', 'quality_status', 'product_photo', 'sale_price', 'order_id']
|
||||
const defaultVisibleCols = ['company_name', 'material_name', 'sku', 'warehouse_loc', 'serial_number', 'in_quantity', 'stock_quantity', 'available_quantity', 'status', 'quality_status', 'product_photo', 'sale_price', 'order_id']
|
||||
|
||||
const form = reactive({
|
||||
id: undefined, base_id: undefined as number | undefined,
|
||||
@ -1244,7 +1247,7 @@ const handleUpdate = (row: any) => {
|
||||
product_photo: row.product_photo || [],
|
||||
quality_report_link: row.quality_report_link || [],
|
||||
inspection_report_link: row.inspection_report_link || [],
|
||||
in_quantity: Number(row.qty_inbound),
|
||||
in_quantity: Number(row.in_quantity),
|
||||
raw_material_cost: (row.raw_material_cost !== null && row.raw_material_cost !== undefined) ? Number(row.raw_material_cost) : undefined,
|
||||
unit_total_cost: (row.unit_total_cost !== null && row.unit_total_cost !== undefined) ? Number(row.unit_total_cost) : undefined,
|
||||
sale_price: (row.sale_price !== null && row.sale_price !== undefined) ? Number(row.sale_price) : undefined
|
||||
|
||||
Reference in New Issue
Block a user