feat(qc): 质量评审模块增加库区管理功能

1. 为钢卷评审实体类新增改判前后逻辑库区相关字段
2. 新增数据库迁移脚本添加库区字段到qc_quality_review_coil表
3. 移除硬编码的技术部库区逻辑,改为支持手动指定目标库区
4. 在前端页面新增库区展示列和改判后库区选择器
5. 自动带出钢卷当前库区信息
This commit is contained in:
王文昊
2026-07-06 10:14:27 +08:00
parent cf4e5b78fc
commit ed69b65e96
8 changed files with 154 additions and 38 deletions

View File

@@ -0,0 +1,10 @@
-- ------------------------------------------------------------
-- 质量评审 — 钢卷明细增加逻辑库区字段
-- V16__qc_quality_review_warehouse.sql
-- ------------------------------------------------------------
ALTER TABLE `qc_quality_review_coil`
ADD COLUMN `before_warehouse_id` bigint DEFAULT NULL COMMENT '改判前逻辑库区ID' AFTER `before_quality`,
ADD COLUMN `before_warehouse_name` varchar(100) DEFAULT NULL COMMENT '改判前逻辑库区名称' AFTER `before_warehouse_id`,
ADD COLUMN `target_warehouse_id` bigint DEFAULT NULL COMMENT '改判后目标库区ID' AFTER `regrade_quality`,
ADD COLUMN `target_warehouse_name` varchar(100) DEFAULT NULL COMMENT '改判后目标库区名称' AFTER `target_warehouse_id`;

View File

@@ -55,9 +55,21 @@ public class QcQualityReviewCoil extends BaseEntity {
/** 改判前质量等级 */ /** 改判前质量等级 */
private String beforeQuality; private String beforeQuality;
/** 改判前逻辑库区ID */
private Long beforeWarehouseId;
/** 改判前逻辑库区名称 */
private String beforeWarehouseName;
/** 改判后质量状态字典regrade_quality_type */ /** 改判后质量状态字典regrade_quality_type */
private String regradeQuality; private String regradeQuality;
/** 改判后目标库区ID */
private Long targetWarehouseId;
/** 改判后目标库区名称 */
private String targetWarehouseName;
/** 执行状态0=待执行 1=已执行 */ /** 执行状态0=待执行 1=已执行 */
private Long executeStatus; private Long executeStatus;

View File

@@ -55,9 +55,21 @@ public class QcQualityReviewCoilBo extends BaseEntity {
/** 改判前质量等级 */ /** 改判前质量等级 */
private String beforeQuality; private String beforeQuality;
/** 改判前逻辑库区ID */
private Long beforeWarehouseId;
/** 改判前逻辑库区名称 */
private String beforeWarehouseName;
/** 改判后质量状态 */ /** 改判后质量状态 */
private String regradeQuality; private String regradeQuality;
/** 改判后目标库区ID */
private Long targetWarehouseId;
/** 改判后目标库区名称 */
private String targetWarehouseName;
/** 执行状态 */ /** 执行状态 */
private Long executeStatus; private Long executeStatus;

View File

@@ -53,9 +53,21 @@ public class QcQualityReviewCoilVo extends BaseEntity {
/** 改判前质量等级 */ /** 改判前质量等级 */
private String beforeQuality; private String beforeQuality;
/** 改判前逻辑库区ID */
private Long beforeWarehouseId;
/** 改判前逻辑库区名称 */
private String beforeWarehouseName;
/** 改判后质量状态 */ /** 改判后质量状态 */
private String regradeQuality; private String regradeQuality;
/** 改判后目标库区ID */
private Long targetWarehouseId;
/** 改判后目标库区名称 */
private String targetWarehouseName;
/** 执行状态 */ /** 执行状态 */
private Long executeStatus; private Long executeStatus;

View File

@@ -11,7 +11,9 @@ import com.klp.common.core.page.TableDataInfo;
import com.klp.common.utils.StringUtils; import com.klp.common.utils.StringUtils;
import com.klp.common.helper.LoginHelper; import com.klp.common.helper.LoginHelper;
import com.klp.domain.WmsMaterialCoil; import com.klp.domain.WmsMaterialCoil;
import com.klp.domain.WmsWarehouse;
import com.klp.mapper.WmsMaterialCoilMapper; import com.klp.mapper.WmsMaterialCoilMapper;
import com.klp.mapper.WmsWarehouseMapper;
import com.klp.service.IWmsMaterialCoilService; import com.klp.service.IWmsMaterialCoilService;
import com.klp.mes.qc.domain.QcQualityReview; import com.klp.mes.qc.domain.QcQualityReview;
import com.klp.mes.qc.domain.QcQualityReviewCoil; import com.klp.mes.qc.domain.QcQualityReviewCoil;
@@ -43,13 +45,11 @@ import java.util.*;
@Service @Service
public class QcQualityReviewServiceImpl implements IQcQualityReviewService { public class QcQualityReviewServiceImpl implements IQcQualityReviewService {
/** 技术部逻辑库IDC/D级改判后移入此库区 */
private static final Long TECH_WAREHOUSE_ID = 2019583656787259393L;
private final QcQualityReviewMapper baseMapper; private final QcQualityReviewMapper baseMapper;
private final QcQualityReviewCoilMapper coilMapper; private final QcQualityReviewCoilMapper coilMapper;
private final QcQualityReviewLogMapper logMapper; private final QcQualityReviewLogMapper logMapper;
private final WmsMaterialCoilMapper wmsMaterialCoilMapper; private final WmsMaterialCoilMapper wmsMaterialCoilMapper;
private final WmsWarehouseMapper wmsWarehouseMapper;
private final IWmsMaterialCoilService wmsMaterialCoilService; private final IWmsMaterialCoilService wmsMaterialCoilService;
/** /**
@@ -313,12 +313,11 @@ public class QcQualityReviewServiceImpl implements IQcQualityReviewService {
coil.getCoilId(), coil.getRegradeQuality(), reason); coil.getCoilId(), coil.getRegradeQuality(), reason);
} }
// 2. 根据改判后质量等级自动更新钢卷所在逻辑库区 // 2. 使用手动指定的目标逻辑库区更新钢卷所在库区
Long targetWarehouseId = determineTargetWarehouse(coil.getRegradeQuality()); if (coil.getTargetWarehouseId() != null && coil.getCoilId() != null) {
if (targetWarehouseId != null && coil.getCoilId() != null) {
wmsMaterialCoilMapper.update(null, Wrappers.<WmsMaterialCoil>lambdaUpdate() wmsMaterialCoilMapper.update(null, Wrappers.<WmsMaterialCoil>lambdaUpdate()
.eq(WmsMaterialCoil::getCoilId, coil.getCoilId()) .eq(WmsMaterialCoil::getCoilId, coil.getCoilId())
.set(WmsMaterialCoil::getWarehouseId, targetWarehouseId)); .set(WmsMaterialCoil::getWarehouseId, coil.getTargetWarehouseId()));
} }
// 3. 更新明细执行状态 // 3. 更新明细执行状态
@@ -359,10 +358,12 @@ public class QcQualityReviewServiceImpl implements IQcQualityReviewService {
coil.setGroupSeq(seq); coil.setGroupSeq(seq);
} }
// 若未传改判前质量等级,从钢卷表获取 // 若未传改判前质量等级,从钢卷表获取
if (StringUtils.isBlank(coil.getBeforeQuality()) && coil.getCoilId() != null) { if (coil.getCoilId() != null) {
WmsMaterialCoil wmsCoil = wmsMaterialCoilMapper.selectById(coil.getCoilId()); WmsMaterialCoil wmsCoil = wmsMaterialCoilMapper.selectById(coil.getCoilId());
if (wmsCoil != null) { if (wmsCoil != null) {
if (StringUtils.isBlank(coil.getBeforeQuality())) {
coil.setBeforeQuality(wmsCoil.getQualityStatus()); coil.setBeforeQuality(wmsCoil.getQualityStatus());
}
if (StringUtils.isBlank(coil.getCurrentCoilNo())) { if (StringUtils.isBlank(coil.getCurrentCoilNo())) {
coil.setCurrentCoilNo(wmsCoil.getCurrentCoilNo()); coil.setCurrentCoilNo(wmsCoil.getCurrentCoilNo());
} }
@@ -372,6 +373,14 @@ public class QcQualityReviewServiceImpl implements IQcQualityReviewService {
if (coil.getNetWeight() == null) { if (coil.getNetWeight() == null) {
coil.setNetWeight(wmsCoil.getNetWeight()); coil.setNetWeight(wmsCoil.getNetWeight());
} }
// 自动带出当前逻辑库区
if (coil.getBeforeWarehouseId() == null && wmsCoil.getWarehouseId() != null) {
coil.setBeforeWarehouseId(wmsCoil.getWarehouseId());
WmsWarehouse wh = wmsWarehouseMapper.selectById(wmsCoil.getWarehouseId());
if (wh != null) {
coil.setBeforeWarehouseName(wh.getWarehouseName());
}
}
} }
} }
coilMapper.insert(coil); coilMapper.insert(coil);
@@ -402,23 +411,6 @@ public class QcQualityReviewServiceImpl implements IQcQualityReviewService {
return "QR-" + dateStr + "-" + String.format("%04d", count + 1); return "QR-" + dateStr + "-" + String.format("%04d", count + 1);
} }
/**
* 根据改判后质量等级确定目标逻辑库区
* O/A/B → 不移动C+/C/C- → 技术部逻辑库D+/D/D- → 技术部逻辑库
*/
private Long determineTargetWarehouse(String regradeQuality) {
if (regradeQuality == null) return null;
// O/A/B 级不移动
if ("O".equals(regradeQuality) || "A".equals(regradeQuality) || "B".equals(regradeQuality)) {
return null;
}
// C/D 级 → 技术部逻辑库
if (regradeQuality.startsWith("C") || regradeQuality.startsWith("D")) {
return TECH_WAREHOUSE_ID;
}
return null;
}
private void validEntityBeforeSave(QcQualityReview entity) { private void validEntityBeforeSave(QcQualityReview entity) {
if (StringUtils.isBlank(entity.getProductName())) { if (StringUtils.isBlank(entity.getProductName())) {
throw new RuntimeException("产品名称不能为空"); throw new RuntimeException("产品名称不能为空");

View File

@@ -16,7 +16,11 @@
<result property="netWeight" column="net_weight"/> <result property="netWeight" column="net_weight"/>
<result property="defectDesc" column="defect_desc"/> <result property="defectDesc" column="defect_desc"/>
<result property="beforeQuality" column="before_quality"/> <result property="beforeQuality" column="before_quality"/>
<result property="beforeWarehouseId" column="before_warehouse_id"/>
<result property="beforeWarehouseName" column="before_warehouse_name"/>
<result property="regradeQuality" column="regrade_quality"/> <result property="regradeQuality" column="regrade_quality"/>
<result property="targetWarehouseId" column="target_warehouse_id"/>
<result property="targetWarehouseName" column="target_warehouse_name"/>
<result property="executeStatus" column="execute_status"/> <result property="executeStatus" column="execute_status"/>
<result property="executeTime" column="execute_time"/> <result property="executeTime" column="execute_time"/>
<result property="remark" column="remark"/> <result property="remark" column="remark"/>

View File

@@ -124,12 +124,22 @@
<el-tag size="mini" type="danger">{{ scope.row.beforeQuality }}</el-tag> <el-tag size="mini" type="danger">{{ scope.row.beforeQuality }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="regradeQuality" label="改判后" width="110" align="center"> <el-table-column prop="regradeQuality" label="改判后" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.regradeQuality" size="mini" type="warning">{{ scope.row.regradeQuality }}</el-tag> <el-tag v-if="scope.row.regradeQuality" size="mini" type="warning">{{ scope.row.regradeQuality }}</el-tag>
<span v-else class="text-muted">待指定</span> <span v-else class="text-muted">待指定</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="beforeWarehouseName" label="当前库区" width="100">
<template slot-scope="scope">
<span>{{ scope.row.beforeWarehouseName || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="targetWarehouseName" label="改判后库区" width="120">
<template slot-scope="scope">
<span>{{ scope.row.targetWarehouseName || '不移动' }}</span>
</template>
</el-table-column>
</el-table> </el-table>
</el-card> </el-card>
@@ -170,9 +180,15 @@
<el-table-column prop="beforeQuality" label="当前等级" width="80" align="center"> <el-table-column prop="beforeQuality" label="当前等级" width="80" align="center">
<template slot-scope="s"><el-tag size="mini" type="danger">{{ s.row.beforeQuality }}</el-tag></template> <template slot-scope="s"><el-tag size="mini" type="danger">{{ s.row.beforeQuality }}</el-tag></template>
</el-table-column> </el-table-column>
<el-table-column prop="regradeQuality" label="改判后等级" width="110" align="center"> <el-table-column prop="regradeQuality" label="改判后等级" width="100" align="center">
<template slot-scope="s"><el-tag size="mini" type="warning">{{ s.row.regradeQuality }}</el-tag></template> <template slot-scope="s"><el-tag size="mini" type="warning">{{ s.row.regradeQuality }}</el-tag></template>
</el-table-column> </el-table-column>
<el-table-column prop="beforeWarehouseName" label="当前库区" width="100" />
<el-table-column prop="targetWarehouseName" label="改判后库区" width="120">
<template slot-scope="s">
<span>{{ s.row.targetWarehouseName || '不移动' }}</span>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
@@ -228,7 +244,7 @@
</div> </div>
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="960px" :close-on-click-modal="false" append-to-body class="review-dialog"> <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="1200px" :close-on-click-modal="false" append-to-body class="review-dialog">
<el-form ref="form" :model="editForm" :rules="rules" label-width="100px" size="small"> <el-form ref="form" :model="editForm" :rules="rules" label-width="100px" size="small">
<!-- ===== 基本信息 ===== --> <!-- ===== 基本信息 ===== -->
<div class="dialog-section"> <div class="dialog-section">
@@ -274,19 +290,37 @@
<el-table-column prop="supplierCoilNo" label="原料卷号" min-width="130" /> <el-table-column prop="supplierCoilNo" label="原料卷号" min-width="130" />
<el-table-column prop="spec" label="规格(mm)" width="100" /> <el-table-column prop="spec" label="规格(mm)" width="100" />
<el-table-column prop="netWeight" label="卷重(t)" width="80" align="right" /> <el-table-column prop="netWeight" label="卷重(t)" width="80" align="right" />
<el-table-column prop="defectDesc" label="缺陷描述" min-width="160"> <el-table-column prop="defectDesc" label="缺陷描述" min-width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-model="scope.row.defectDesc" size="mini" placeholder="输入缺陷描述" /> <el-input v-model="scope.row.defectDesc" size="mini" placeholder="输入缺陷" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="regradeQuality" label="改判后等级" min-width="130"> <el-table-column prop="beforeQuality" label="当前等级" width="80" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-select v-model="scope.row.regradeQuality" placeholder="请选择" size="mini"> <el-tag size="mini" type="danger">{{ scope.row.beforeQuality }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="regradeQuality" label="改判后等级" width="120">
<template slot-scope="scope">
<el-select v-model="scope.row.regradeQuality" placeholder="请选择" size="mini" style="width:100%;">
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="60" align="center"> <el-table-column prop="beforeWarehouseName" label="当前库区" width="100">
<template slot-scope="scope">
<span>{{ scope.row.beforeWarehouseName || '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="targetWarehouseId" label="改判后库区" width="140">
<template slot-scope="scope">
<el-select v-model="scope.row.targetWarehouseId" placeholder="请选择" size="mini" style="width:100%;" @change="onTargetWarehouseChange(scope.row)">
<el-option label="不移动" :value="null" />
<el-option v-for="w in warehouseOptions" v-if="w.warehouseType === 1" :key="w.warehouseId" :label="w.warehouseName" :value="w.warehouseId" />
</el-select>
</template>
</el-table-column>
<el-table-column label="操作" width="55" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click="editForm.coilList.splice(scope.$index, 1)"></el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="editForm.coilList.splice(scope.$index, 1)"></el-button>
</template> </template>
@@ -372,6 +406,7 @@ import { listQualityReview, getQualityReview, addQualityReview, updateQualityRev
submitQualityReview, approveQualityReview, rejectQualityReview, executeQualityReview, submitQualityReview, approveQualityReview, rejectQualityReview, executeQualityReview,
listQualityReviewCoil, listQualityReviewLog } from '@/api/mes/qc/qualityReview' listQualityReviewCoil, listQualityReviewLog } from '@/api/mes/qc/qualityReview'
import CoilSelector from '@/components/CoilSelector' import CoilSelector from '@/components/CoilSelector'
import { listWarehouse } from '@/api/wms/warehouse'
export default { export default {
name: 'QualityReview', name: 'QualityReview',
@@ -398,6 +433,7 @@ export default {
// 钢卷选择 // 钢卷选择
showCoilSelector: false, showCoilSelector: false,
warehouseOptions: [], // 库区下拉选项
// 编辑弹窗 // 编辑弹窗
dialogVisible: false, dialogVisible: false,
@@ -436,6 +472,11 @@ export default {
created() { created() {
this.getList() this.getList()
}, },
watch: {
dialogVisible(val) {
if (val) this.loadWarehouseOptions()
}
},
methods: { methods: {
// ===== 列表 ===== // ===== 列表 =====
handleQuery() { handleQuery() {
@@ -546,24 +587,51 @@ export default {
// ===== 钢卷选择(弹窗内) ===== // ===== 钢卷选择(弹窗内) =====
onDialogCoilConfirm(selected) { onDialogCoilConfirm(selected) {
if (!selected || selected.length === 0) return if (!selected || selected.length === 0) return
const existingIds = new Set(this.editForm.coilList.map(c => c.coilId))
const newSelected = selected.filter(coil => !existingIds.has(coil.coilId))
const dupCount = selected.length - newSelected.length
if (dupCount > 0) {
this.$message.warning(`已跳过 ${dupCount} 个重复钢卷`)
}
if (newSelected.length === 0) return
const startIdx = this.editForm.coilList.length || 0 const startIdx = this.editForm.coilList.length || 0
const newCoils = selected.map((coil, idx) => ({ const newCoils = newSelected.map((coil, idx) => ({
coilId: coil.coilId, coilId: coil.coilId,
currentCoilNo: coil.currentCoilNo, currentCoilNo: coil.currentCoilNo,
supplierCoilNo: coil.supplierCoilNo || coil.enterCoilNo, supplierCoilNo: coil.supplierCoilNo || coil.enterCoilNo,
spec: coil.specification, // CoilSelector返回的字段名为specification spec: coil.specification,
netWeight: coil.netWeight, netWeight: coil.netWeight,
defectDesc: '', defectDesc: '',
regradeQuality: '', // 改判后等级,由创建者填写 regradeQuality: '',
beforeWarehouseId: coil.warehouseId || null,
beforeWarehouseName: this.getWarehouseName(coil.warehouseId),
targetWarehouseId: null,
targetWarehouseName: '',
groupSeq: startIdx + idx + 1, groupSeq: startIdx + idx + 1,
beforeQuality: coil.qualityStatus || 'O' beforeQuality: coil.qualityStatus || 'O'
})) }))
this.editForm.coilList = [...(this.editForm.coilList || []), ...newCoils] this.editForm.coilList = [...(this.editForm.coilList || []), ...newCoils]
}, },
getWarehouseName(warehouseId) {
if (!warehouseId) return ''
const wh = this.warehouseOptions.find(w => w.warehouseId === warehouseId)
return wh ? wh.warehouseName : ''
},
handleRemoveCoil(index) { handleRemoveCoil(index) {
this.coilList.splice(index, 1) this.coilList.splice(index, 1)
}, },
// ===== 库区选择 =====
loadWarehouseOptions() {
listWarehouse().then(res => {
this.warehouseOptions = res.data || []
})
},
onTargetWarehouseChange(row) {
const selected = this.warehouseOptions.find(w => w.warehouseId === row.targetWarehouseId)
row.targetWarehouseName = selected ? selected.warehouseName : ''
},
// ===== 提交送审 ===== // ===== 提交送审 =====
handleSubmit() { handleSubmit() {
this.$confirm('确认提交送审?', '提示', { type: 'warning' }).then(() => { this.$confirm('确认提交送审?', '提示', { type: 'warning' }).then(() => {

View File

@@ -64,9 +64,15 @@
<el-table-column prop="beforeQuality" label="当前等级" width="80" align="center"> <el-table-column prop="beforeQuality" label="当前等级" width="80" align="center">
<template slot-scope="s"><el-tag size="mini" type="danger">{{ s.row.beforeQuality }}</el-tag></template> <template slot-scope="s"><el-tag size="mini" type="danger">{{ s.row.beforeQuality }}</el-tag></template>
</el-table-column> </el-table-column>
<el-table-column prop="regradeQuality" label="改判后等级" width="110" align="center"> <el-table-column prop="regradeQuality" label="改判后等级" width="100" align="center">
<template slot-scope="s"><el-tag size="mini" type="warning">{{ s.row.regradeQuality }}</el-tag></template> <template slot-scope="s"><el-tag size="mini" type="warning">{{ s.row.regradeQuality }}</el-tag></template>
</el-table-column> </el-table-column>
<el-table-column prop="beforeWarehouseName" label="当前库区" width="100" />
<el-table-column prop="targetWarehouseName" label="改判后库区" width="120">
<template slot-scope="s">
<span>{{ s.row.targetWarehouseName || '不移动' }}</span>
</template>
</el-table-column>
</el-table> </el-table>
</div> </div>
<div style="margin-top:12px;"> <div style="margin-top:12px;">