完善单元格合并逻辑

This commit is contained in:
2026-06-27 14:10:56 +08:00
parent 66d2b33db5
commit fa333ff557
4 changed files with 96 additions and 20 deletions

View File

@@ -142,6 +142,13 @@ public class ErpPurchasePlanController extends BaseController {
return R.ok(iErpPurchasePlanService.queryDeliveryList(planId)); return R.ok(iErpPurchasePlanService.queryDeliveryList(planId));
} }
/** 刷新到货:拿已上传卷号去 WMS 钢卷表实时复核,签收入库的自动翻为已到货(无需重传) */
@PutMapping("/{planId}/refreshArrival")
public R<Void> refreshArrival(@PathVariable Long planId) {
iErpPurchasePlanService.refreshProgress(planId);
return R.ok();
}
/** 某计划的到货上传批次(每次上传一条,可回看) */ /** 某计划的到货上传批次(每次上传一条,可回看) */
@GetMapping("/{planId}/deliveryBatches") @GetMapping("/{planId}/deliveryBatches")
public R<List<ErpPurchasePlanDeliveryBatchVo>> deliveryBatches(@PathVariable Long planId) { public R<List<ErpPurchasePlanDeliveryBatchVo>> deliveryBatches(@PathVariable Long planId) {

View File

@@ -46,6 +46,7 @@ import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -340,7 +341,31 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
if (valid.isEmpty()) { if (valid.isEmpty()) {
throw new ServiceException("未解析到有效到货数据,请检查文件内容或列名是否与模板一致"); throw new ServiceException("未解析到有效到货数据,请检查文件内容或列名是否与模板一致");
} }
// 2) kg→t 单位判定(文件级:单卷重量最大值超阈值视为 kg // 2) 牌号校验:到货表里的牌号必须都在本计划「采购要求」的牌号范围内
List<ErpPurchasePlanItem> planItems = itemMapper.selectList(Wrappers.lambdaQuery(ErpPurchasePlanItem.class)
.eq(ErpPurchasePlanItem::getPlanId, planId));
Set<String> reqGrades = planItems.stream()
.map(it -> normCoil(it.getGrade()))
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
if (!reqGrades.isEmpty()) {
Set<String> badGrades = new LinkedHashSet<>();
for (ErpPurchasePlanDeliveryImportVo r : valid) {
String g = normCoil(r.getGrade());
if (StringUtils.isBlank(g)) {
badGrades.add("(空牌号)");
} else if (!reqGrades.contains(g)) {
badGrades.add(r.getGrade().trim());
}
}
if (!badGrades.isEmpty()) {
String allow = planItems.stream().map(ErpPurchasePlanItem::getGrade)
.filter(StringUtils::isNotBlank).map(String::trim).distinct().collect(Collectors.joining(""));
throw new ServiceException("到货表中的牌号 [" + String.join("", badGrades)
+ "] 不在本计划采购要求的牌号范围内(仅允许:" + allow + "),请核对后重新上传");
}
}
// 3) kg→t 单位判定(文件级:单卷重量最大值超阈值视为 kg
BigDecimal maxCoil = valid.stream() BigDecimal maxCoil = valid.stream()
.map(ErpPurchasePlanDeliveryImportVo::getCoilWeight) .map(ErpPurchasePlanDeliveryImportVo::getCoilWeight)
.filter(w -> w != null) .filter(w -> w != null)
@@ -348,7 +373,7 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
.orElse(BigDecimal.ZERO); .orElse(BigDecimal.ZERO);
boolean kgConverted = maxCoil.compareTo(KG_THRESHOLD) > 0; boolean kgConverted = maxCoil.compareTo(KG_THRESHOLD) > 0;
// 3) 卷号去 WMS 钢卷表校验:存在且 data_type<>10 即视为已实际到货入库 // 4) 卷号去 WMS 钢卷表校验:存在且 data_type<>10 即视为已实际到货入库
List<String> uploadCoilNos = valid.stream() List<String> uploadCoilNos = valid.stream()
.map(ErpPurchasePlanDeliveryImportVo::getCoilNo) .map(ErpPurchasePlanDeliveryImportVo::getCoilNo)
.filter(StringUtils::isNotBlank) .filter(StringUtils::isNotBlank)
@@ -358,13 +383,13 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
: baseMapper.selectExistingSupplierCoilNos(uploadCoilNos).stream() : baseMapper.selectExistingSupplierCoilNos(uploadCoilNos).stream()
.map(this::normCoil).collect(Collectors.toSet()); .map(this::normCoil).collect(Collectors.toSet());
// 4) 先建批次(每次上传存档,可随时回看) // 5) 先建批次(每次上传存档,可随时回看)
ErpPurchasePlanDeliveryBatch batch = new ErpPurchasePlanDeliveryBatch(); ErpPurchasePlanDeliveryBatch batch = new ErpPurchasePlanDeliveryBatch();
batch.setPlanId(planId); batch.setPlanId(planId);
batch.setFileName(StringUtils.isNotBlank(fileName) ? fileName : "到货表"); batch.setFileName(StringUtils.isNotBlank(fileName) ? fileName : "到货表");
deliveryBatchMapper.insert(batch); deliveryBatchMapper.insert(batch);
// 5) 合并单元格向下填充 + 单位换算 + 落库 + WMS到货标记 // 6) 合并单元格向下填充 + 单位换算 + 落库 + WMS到货标记
String lastTruckNo = null; String lastTruckNo = null;
BigDecimal lastTruckWeight = null; BigDecimal lastTruckWeight = null;
Integer lastPieceCount = null; Integer lastPieceCount = null;
@@ -401,10 +426,10 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
matched++; matched++;
} }
} }
// 6) 汇总刷新进度 // 7) 汇总刷新进度
refreshProgress(planId); refreshProgress(planId);
// 7) 回填批次统计行数、WMS确认到货卷数、上传后进度快照 // 8) 回填批次统计行数、WMS确认到货卷数、上传后进度快照
ErpPurchasePlan after = baseMapper.selectById(planId); ErpPurchasePlan after = baseMapper.selectById(planId);
batch.setRowCount(count); batch.setRowCount(count);
batch.setMatchedCount(matched); batch.setMatchedCount(matched);
@@ -492,13 +517,29 @@ public class ErpPurchasePlanServiceImpl implements IErpPurchasePlanService {
if (plan == null) { if (plan == null) {
return; return;
} }
// 到货 = 累计所有上传批次中 WMS 已确认到货(arrived=1)的卷 // 到货 = 累计所有上传批次中 WMS 已确认到货的卷
// 每次刷新都拿卷号去 WMS 钢卷表实时复核:之前未到、后来签收入库的,这里会自动翻成已到货,无需重新上传。
List<ErpPurchasePlanDelivery> deliveries = deliveryMapper.selectList(Wrappers.lambdaQuery(ErpPurchasePlanDelivery.class) List<ErpPurchasePlanDelivery> deliveries = deliveryMapper.selectList(Wrappers.lambdaQuery(ErpPurchasePlanDelivery.class)
.eq(ErpPurchasePlanDelivery::getPlanId, planId)); .eq(ErpPurchasePlanDelivery::getPlanId, planId));
List<String> coilNos = deliveries.stream()
.map(ErpPurchasePlanDelivery::getCoilNo)
.filter(StringUtils::isNotBlank)
.map(String::trim)
.collect(Collectors.toList());
Set<String> wmsArrivedSet = coilNos.isEmpty() ? new HashSet<>()
: baseMapper.selectExistingSupplierCoilNos(coilNos).stream()
.map(this::normCoil).collect(Collectors.toSet());
int arrivedCount = 0; int arrivedCount = 0;
BigDecimal arrivedWeight = BigDecimal.ZERO; BigDecimal arrivedWeight = BigDecimal.ZERO;
for (ErpPurchasePlanDelivery d : deliveries) { for (ErpPurchasePlanDelivery d : deliveries) {
if (d.getArrived() != null && d.getArrived() == 1) { int nowArrived = StringUtils.isNotBlank(d.getCoilNo()) && wmsArrivedSet.contains(normCoil(d.getCoilNo())) ? 1 : 0;
// 状态有变化才回写,减少无谓更新
if (d.getArrived() == null || d.getArrived() != nowArrived) {
d.setArrived(nowArrived);
deliveryMapper.updateById(d);
}
if (nowArrived == 1) {
arrivedCount++; arrivedCount++;
arrivedWeight = arrivedWeight.add(d.getCoilWeight() == null ? BigDecimal.ZERO : d.getCoilWeight()); arrivedWeight = arrivedWeight.add(d.getCoilWeight() == null ? BigDecimal.ZERO : d.getCoilWeight());
} }

View File

@@ -124,6 +124,14 @@ export function listDeliveryPlans(query) {
}) })
} }
// 刷新到货(拿已上传卷号去 WMS 实时复核,签收的自动翻为已到货)
export function refreshArrival(planId) {
return request({
url: `/erp/purchasePlan/${planId}/refreshArrival`,
method: 'put'
})
}
// 某计划的到货上传批次(每次上传一条) // 某计划的到货上传批次(每次上传一条)
export function listDeliveryBatches(planId) { export function listDeliveryBatches(planId) {
return request({ return request({

View File

@@ -59,6 +59,8 @@
<span class="pd-d-title">{{ current.planNo }}</span> <span class="pd-d-title">{{ current.planNo }}</span>
<span class="pd-badge" :class="current.planStatus === '1' ? 'p1' : 'p0'">{{ current.planStatus === '1' ? '已到齐' : '到货中' }}</span> <span class="pd-badge" :class="current.planStatus === '1' ? 'p1' : 'p0'">{{ current.planStatus === '1' ? '已到齐' : '到货中' }}</span>
</div> </div>
<div class="pd-head-act">
<el-button size="small" icon="el-icon-refresh" :loading="refreshing" @click="doRefreshArrival">刷新到货</el-button>
<el-upload <el-upload
:headers="upload.headers" :headers="upload.headers"
:action="uploadUrl" :action="uploadUrl"
@@ -70,6 +72,7 @@
<el-button type="primary" size="small" icon="el-icon-upload2">上传到货表格</el-button> <el-button type="primary" size="small" icon="el-icon-upload2">上传到货表格</el-button>
</el-upload> </el-upload>
</div> </div>
</div>
<div class="pd-meta"> <div class="pd-meta">
<div class="pd-meta-i"><label>合同号</label><span>{{ (current.contractCodes || []).join('、') || '—' }}</span></div> <div class="pd-meta-i"><label>合同号</label><span>{{ (current.contractCodes || []).join('、') || '—' }}</span></div>
@@ -169,7 +172,8 @@ import {
getPurchasePlan, getPurchasePlan,
listDeliveryPlans, listDeliveryPlans,
listDeliveryBatches, listDeliveryBatches,
listDeliveryByBatch listDeliveryByBatch,
refreshArrival
} from '@/api/erp/purchasePlan' } from '@/api/erp/purchasePlan'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
@@ -188,6 +192,7 @@ export default {
currentBatch: {}, currentBatch: {},
batchRows: [], batchRows: [],
batchRowsLoading: false, batchRowsLoading: false,
refreshing: false,
upload: { headers: { Authorization: 'Bearer ' + getToken() } }, upload: { headers: { Authorization: 'Bearer ' + getToken() } },
progressColor: '#5b8db8' progressColor: '#5b8db8'
} }
@@ -222,7 +227,11 @@ export default {
this.current = { ...p } this.current = { ...p }
this.currentBatch = {} this.currentBatch = {}
this.batchRows = [] this.batchRows = []
this.refreshDetail() // 打开即静默复核一次 WMS 到货状态,保证看到的是最新
const planId = p.planId
refreshArrival(planId).catch(() => {}).finally(() => {
if (this.current.planId === planId) this.refreshDetail()
})
}, },
refreshDetail() { refreshDetail() {
const planId = this.current.planId const planId = this.current.planId
@@ -230,6 +239,16 @@ export default {
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } }) getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } })
this.loadBatches() this.loadBatches()
}, },
doRefreshArrival() {
if (!this.current.planId) return
this.refreshing = true
refreshArrival(this.current.planId).then(() => {
this.$modal.msgSuccess('已按钢卷表刷新到货状态')
this.refreshDetail()
if (this.currentBatch.batchId) this.viewBatch(this.currentBatch)
this.getList(true)
}).finally(() => { this.refreshing = false })
},
loadBatches() { loadBatches() {
this.batchLoading = true this.batchLoading = true
listDeliveryBatches(this.current.planId).then(res => { listDeliveryBatches(this.current.planId).then(res => {
@@ -302,6 +321,7 @@ $sub: #909399;
p { font-size: 13px; } p { font-size: 13px; }
} }
.pd-d-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid $line; } .pd-d-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid $line; }
.pd-head-act { display: flex; gap: 10px; align-items: center; ::v-deep .el-button { margin: 0; } }
.pd-d-title { font-size: 15px; font-weight: 600; color: $ink; margin-right: 8px; } .pd-d-title { font-size: 15px; font-weight: 600; color: $ink; margin-right: 8px; }
.pd-badge { .pd-badge {
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub; background: #fafafa; font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub; background: #fafafa;