生产模块完成任务接口加幂等:重复点击“完成”不应重复生成回执

优化页面体验
任务允许重复但要合并
This commit is contained in:
朱昊天
2026-06-01 13:56:39 +08:00
parent 303092e637
commit 9f4e1c39ad
5 changed files with 117 additions and 26 deletions

View File

@@ -24,9 +24,13 @@ public class GearProductionTaskListVo {
private String remark;
private String createBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@@ -38,4 +42,3 @@ public class GearProductionTaskListVo {
private BigDecimal unfinishedQty;
}

View File

@@ -18,7 +18,9 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
public class GearProductionTaskServiceImpl implements IGearProductionTaskService {
@@ -76,22 +78,39 @@ public class GearProductionTaskServiceImpl implements IGearProductionTaskService
task.setUpdateTime(DateUtils.getNowDate());
taskMapper.insertTask(task);
List<GearProductionTaskProduct> products = new ArrayList<>();
Map<Long, GearProductionTaskProduct> productMap = new LinkedHashMap<>();
if (bo.getProducts() != null) {
bo.getProducts().forEach(p -> {
if (p == null || p.getProductId() == null) return;
GearProductionTaskProduct row = new GearProductionTaskProduct();
row.setLineId(IdUtil.getSnowflakeNextId());
row.setTaskId(taskId);
row.setProductId(p.getProductId());
row.setPlanQty(p.getPlanQty() == null ? BigDecimal.ZERO : p.getPlanQty());
row.setFinishedQty(p.getFinishedQty() == null ? BigDecimal.ZERO : p.getFinishedQty());
row.setBadQty(p.getBadQty() == null ? BigDecimal.ZERO : p.getBadQty());
row.setUnit(p.getUnit());
row.setRemark(p.getRemark());
products.add(row);
GearProductionTaskProduct hit = productMap.get(p.getProductId());
if (hit == null) {
GearProductionTaskProduct row = new GearProductionTaskProduct();
row.setLineId(IdUtil.getSnowflakeNextId());
row.setTaskId(taskId);
row.setProductId(p.getProductId());
row.setPlanQty(p.getPlanQty() == null ? BigDecimal.ZERO : p.getPlanQty());
row.setFinishedQty(p.getFinishedQty() == null ? BigDecimal.ZERO : p.getFinishedQty());
row.setBadQty(p.getBadQty() == null ? BigDecimal.ZERO : p.getBadQty());
row.setUnit(p.getUnit());
row.setRemark(p.getRemark());
productMap.put(p.getProductId(), row);
return;
}
BigDecimal planQty = p.getPlanQty() == null ? BigDecimal.ZERO : p.getPlanQty();
BigDecimal finishedQty = p.getFinishedQty() == null ? BigDecimal.ZERO : p.getFinishedQty();
BigDecimal badQty = p.getBadQty() == null ? BigDecimal.ZERO : p.getBadQty();
hit.setPlanQty((hit.getPlanQty() == null ? BigDecimal.ZERO : hit.getPlanQty()).add(planQty));
hit.setFinishedQty((hit.getFinishedQty() == null ? BigDecimal.ZERO : hit.getFinishedQty()).add(finishedQty));
hit.setBadQty((hit.getBadQty() == null ? BigDecimal.ZERO : hit.getBadQty()).add(badQty));
if ((hit.getUnit() == null || hit.getUnit().trim().isEmpty()) && p.getUnit() != null && !p.getUnit().trim().isEmpty()) {
hit.setUnit(p.getUnit());
}
if ((hit.getRemark() == null || hit.getRemark().trim().isEmpty()) && p.getRemark() != null && !p.getRemark().trim().isEmpty()) {
hit.setRemark(p.getRemark());
}
});
}
List<GearProductionTaskProduct> products = new ArrayList<>(productMap.values());
if (!products.isEmpty()) {
productMapper.insertBatch(products);
}
@@ -109,9 +128,13 @@ public class GearProductionTaskServiceImpl implements IGearProductionTaskService
if (task == null) {
return false;
}
if ("2".equals(String.valueOf(task.getStatus()))) {
return true;
}
int updated = taskMapper.updateTaskStatus(taskId, "2", task.getUpdateBy(), DateUtils.getNowDate());
if (updated <= 0) {
return false;
GearProductionTask after = taskMapper.selectTaskById(taskId);
return after != null && "2".equals(String.valueOf(after.getStatus()));
}
regenerateReceiptMaterials(taskId);
return true;
@@ -134,7 +157,8 @@ public class GearProductionTaskServiceImpl implements IGearProductionTaskService
BigDecimal planQty = r.getPlanQty() == null ? BigDecimal.ZERO : r.getPlanQty();
row.setPlanQty(planQty);
row.setUsedQty(planQty);
row.setUnit(r.getUnit());
String unit = r.getUnit();
row.setUnit(unit == null || unit.trim().isEmpty() ? "" : unit);
row.setRemark(null);
rows.add(row);
});

View File

@@ -13,7 +13,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
t.plan_start_time AS planStartTime,
t.plan_end_time AS planEndTime,
t.remark AS remark,
t.create_by AS createBy,
t.create_time AS createTime,
t.update_by AS updateBy,
t.update_time AS updateTime,
IFNULL(SUM(p.plan_qty), 0) AS planQty,
IFNULL(SUM(p.finished_qty), 0) AS finishedQty,
@@ -29,7 +31,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="beginTime != null"> AND t.plan_start_time <![CDATA[>=]]> #{beginTime}</if>
<if test="endTime != null"> AND t.plan_start_time <![CDATA[<=]]> #{endTime}</if>
</where>
GROUP BY t.task_id
GROUP BY
t.task_id,
t.task_code,
t.task_name,
t.status,
t.plan_start_time,
t.plan_end_time,
t.remark,
t.create_by,
t.create_time,
t.update_by,
t.update_time
ORDER BY t.update_time DESC, t.create_time DESC
</select>
@@ -105,6 +118,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update_time = #{updateTime}
WHERE task_id = #{taskId}
AND del_flag = '0'
AND status <![CDATA[<>]]> #{status}
</update>
</mapper>

View File

@@ -37,7 +37,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
END
) * IFNULL(r.material_num, 0)
) AS planQty,
mm.unit AS unit
CASE WHEN mm.unit IS NULL OR mm.unit = '' THEN '个' ELSE mm.unit END AS unit
FROM gear_production_task_product p
JOIN mat_product_material_relation r ON p.product_id = r.product_id AND r.del_flag = 0
LEFT JOIN mat_material mm ON r.material_id = mm.material_id

View File

@@ -98,6 +98,11 @@
<span style="margin-left: 10px;">已完 {{ formatQty(t.finishedQty) }}</span>
<span style="margin-left: 10px;">未完 {{ formatQty(t.unfinishedQty) }}</span>
</div>
<div class="task-item__meta">
<span v-if="t.planStartTime || t.planEndTime">计划 {{ formatTimeRange(t.planStartTime, t.planEndTime) }}</span>
<span v-if="t.createBy" style="margin-left: 10px;">创建 {{ t.createBy }}</span>
<span v-if="t.updateTime" style="margin-left: 10px;">更新 {{ formatTime(t.updateTime) }}</span>
</div>
<el-progress :percentage="taskProgressPercent(t)" :stroke-width="8" :show-text="false" />
</div>
</el-scrollbar>
@@ -339,6 +344,22 @@ function normalizeRole(v) {
return String(v || '').toLowerCase()
}
function formatTime(v) {
if (!v) return '-'
if (v instanceof Date) {
return formatDateTime(v).slice(0, 16)
}
const s = String(v)
if (s.length >= 16) return s.slice(0, 16)
return s
}
function formatTimeRange(start, end) {
const a = start ? formatTime(start) : '-'
const b = end ? formatTime(end) : '-'
return `${a} ~ ${b}`
}
function pad2(n) {
return String(n).padStart(2, '0')
}
@@ -560,21 +581,43 @@ function onProductPicked(row) {
}
}
function mergeProductLines(lines) {
const list = Array.isArray(lines) ? lines : []
const map = new Map()
list.forEach((p) => {
if (!p || p.productId == null) return
const key = String(p.productId)
const planQty = toNumber(p.planQty)
if (!map.has(key)) {
map.set(key, {
productId: p.productId,
planQty,
unit: p.unit || '',
remark: p.remark || ''
})
return
}
const hit = map.get(key)
hit.planQty = toNumber(hit.planQty) + planQty
if (!hit.unit && p.unit) hit.unit = p.unit
if (!hit.remark && p.remark) hit.remark = p.remark
})
return Array.from(map.values())
}
function submitAdd() {
if (!addFormRef.value) return
addFormRef.value.validate((valid) => {
if (!valid) return
const task = Object.assign({}, addForm.value)
const products = (addProducts.value || [])
.filter((p) => p && p.productId != null)
.map((p) => ({
productId: p.productId,
planQty: p.planQty || 0,
finishedQty: 0,
badQty: 0,
unit: p.unit || '',
remark: p.remark || ''
}))
const products = mergeProductLines(addProducts.value).map((p) => ({
productId: p.productId,
planQty: p.planQty || 0,
finishedQty: 0,
badQty: 0,
unit: p.unit || '',
remark: p.remark || ''
}))
addSaving.value = true
addProductionTask({ task, products })
@@ -775,6 +818,13 @@ onMounted(() => {
margin-bottom: 8px;
}
.task-item__meta {
margin-top: -2px;
margin-bottom: 8px;
font-size: 12px;
color: #c0c4cc;
}
.mb12 {
margin-bottom: 12px;
}