生产模块
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package com.gear.mes.production.controller;
|
||||
|
||||
import com.gear.common.core.controller.BaseController;
|
||||
import com.gear.common.core.domain.AjaxResult;
|
||||
import com.gear.common.core.domain.R;
|
||||
import com.gear.common.core.page.TableDataInfo;
|
||||
import com.gear.mes.production.domain.GearProductionTask;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskListVo;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskWithDetailVo;
|
||||
import com.gear.mes.production.service.IGearProductionTaskService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/mes/production/task")
|
||||
public class GearProductionTaskController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IGearProductionTaskService productionTaskService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<GearProductionTaskListVo> list(GearProductionTask query) {
|
||||
startPage();
|
||||
List<GearProductionTaskListVo> list = productionTaskService.selectTaskList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@GetMapping("/{taskId}")
|
||||
public AjaxResult getInfo(@PathVariable("taskId") Long taskId) {
|
||||
return AjaxResult.success(productionTaskService.selectTaskWithDetail(taskId));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public R<Long> add(@RequestBody GearProductionTaskWithDetailVo bo) {
|
||||
Long taskId = productionTaskService.insertTaskWithDetail(bo);
|
||||
if (taskId == null) {
|
||||
return R.fail("新增失败");
|
||||
}
|
||||
return R.ok(taskId);
|
||||
}
|
||||
|
||||
@PostMapping("/{taskId}/complete")
|
||||
public R<Void> complete(@PathVariable("taskId") Long taskId) {
|
||||
boolean ok = productionTaskService.completeTask(taskId);
|
||||
if (!ok) {
|
||||
return R.fail("完成失败");
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gear.mes.production.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.gear.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class GearProductionTask extends BaseEntity {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
private String taskCode;
|
||||
|
||||
private String taskName;
|
||||
|
||||
private String status;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date planStartTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date planEndTime;
|
||||
|
||||
private String remark;
|
||||
|
||||
private String delFlag;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date beginTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date endTime;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gear.mes.production.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class GearProductionTaskMaterial {
|
||||
private Long lineId;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
private Long materialId;
|
||||
|
||||
private String materialRole;
|
||||
|
||||
private BigDecimal planQty;
|
||||
|
||||
private BigDecimal usedQty;
|
||||
|
||||
private String unit;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.gear.mes.production.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class GearProductionTaskProduct {
|
||||
private Long lineId;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
private BigDecimal planQty;
|
||||
|
||||
private BigDecimal finishedQty;
|
||||
|
||||
private BigDecimal badQty;
|
||||
|
||||
private String unit;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.gear.mes.production.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class GearProductionTaskListVo {
|
||||
private Long taskId;
|
||||
|
||||
private String taskCode;
|
||||
|
||||
private String taskName;
|
||||
|
||||
private String status;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date planStartTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date planEndTime;
|
||||
|
||||
private String remark;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
private BigDecimal planQty;
|
||||
|
||||
private BigDecimal finishedQty;
|
||||
|
||||
private BigDecimal badQty;
|
||||
|
||||
private BigDecimal unfinishedQty;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gear.mes.production.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class GearProductionTaskMaterialVo {
|
||||
private Long lineId;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
private Long materialId;
|
||||
|
||||
private String materialRole;
|
||||
|
||||
private BigDecimal planQty;
|
||||
|
||||
private BigDecimal usedQty;
|
||||
|
||||
private String unit;
|
||||
|
||||
private String remark;
|
||||
|
||||
private String materialName;
|
||||
|
||||
private Integer materialType;
|
||||
|
||||
private String spec;
|
||||
|
||||
private String model;
|
||||
|
||||
private String factory;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.gear.mes.production.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class GearProductionTaskProductVo {
|
||||
private Long lineId;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
private Long productId;
|
||||
|
||||
private BigDecimal planQty;
|
||||
|
||||
private BigDecimal finishedQty;
|
||||
|
||||
private BigDecimal badQty;
|
||||
|
||||
private String unit;
|
||||
|
||||
private String remark;
|
||||
|
||||
private String productName;
|
||||
|
||||
private String productCode;
|
||||
|
||||
private String productType;
|
||||
|
||||
private String spec;
|
||||
|
||||
private String model;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.gear.mes.production.domain.vo;
|
||||
|
||||
import com.gear.mes.production.domain.GearProductionTask;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class GearProductionTaskWithDetailVo {
|
||||
private GearProductionTask task;
|
||||
|
||||
private List<GearProductionTaskProductVo> products;
|
||||
|
||||
private List<GearProductionTaskMaterialVo> materials;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gear.mes.production.mapper;
|
||||
|
||||
import com.gear.mes.production.domain.GearProductionTask;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskListVo;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public interface GearProductionTaskMapper {
|
||||
List<GearProductionTaskListVo> selectTaskList(GearProductionTask query);
|
||||
|
||||
GearProductionTask selectTaskById(@Param("taskId") Long taskId);
|
||||
|
||||
int insertTask(GearProductionTask task);
|
||||
|
||||
int updateTask(GearProductionTask task);
|
||||
|
||||
int updateTaskStatus(@Param("taskId") Long taskId,
|
||||
@Param("status") String status,
|
||||
@Param("updateBy") String updateBy,
|
||||
@Param("updateTime") Date updateTime);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gear.mes.production.mapper;
|
||||
|
||||
import com.gear.mes.production.domain.GearProductionTaskMaterial;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskMaterialVo;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GearProductionTaskMaterialMapper {
|
||||
List<GearProductionTaskMaterialVo> selectByTaskId(@Param("taskId") Long taskId);
|
||||
|
||||
List<GearProductionTaskMaterial> selectRequirementByTaskId(@Param("taskId") Long taskId);
|
||||
|
||||
int insertBatch(@Param("list") List<GearProductionTaskMaterial> list);
|
||||
|
||||
int deleteByTaskId(@Param("taskId") Long taskId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.gear.mes.production.mapper;
|
||||
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskProductVo;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface GearProductionTaskProductMapper {
|
||||
List<GearProductionTaskProductVo> selectByTaskId(@Param("taskId") Long taskId);
|
||||
|
||||
int insertBatch(@Param("list") List<com.gear.mes.production.domain.GearProductionTaskProduct> list);
|
||||
|
||||
int deleteByTaskId(@Param("taskId") Long taskId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gear.mes.production.service;
|
||||
|
||||
import com.gear.mes.production.domain.GearProductionTask;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskListVo;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskWithDetailVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IGearProductionTaskService {
|
||||
List<GearProductionTaskListVo> selectTaskList(GearProductionTask query);
|
||||
|
||||
GearProductionTaskWithDetailVo selectTaskWithDetail(Long taskId);
|
||||
|
||||
Long insertTaskWithDetail(GearProductionTaskWithDetailVo bo);
|
||||
|
||||
boolean completeTask(Long taskId);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.gear.mes.production.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.gear.mes.production.domain.GearProductionTask;
|
||||
import com.gear.mes.production.domain.GearProductionTaskMaterial;
|
||||
import com.gear.mes.production.domain.GearProductionTaskProduct;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskListVo;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskMaterialVo;
|
||||
import com.gear.mes.production.domain.vo.GearProductionTaskWithDetailVo;
|
||||
import com.gear.common.utils.DateUtils;
|
||||
import com.gear.mes.production.mapper.GearProductionTaskMapper;
|
||||
import com.gear.mes.production.mapper.GearProductionTaskMaterialMapper;
|
||||
import com.gear.mes.production.mapper.GearProductionTaskProductMapper;
|
||||
import com.gear.mes.production.service.IGearProductionTaskService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class GearProductionTaskServiceImpl implements IGearProductionTaskService {
|
||||
|
||||
@Autowired
|
||||
private GearProductionTaskMapper taskMapper;
|
||||
|
||||
@Autowired
|
||||
private GearProductionTaskProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private GearProductionTaskMaterialMapper materialMapper;
|
||||
|
||||
@Override
|
||||
public List<GearProductionTaskListVo> selectTaskList(GearProductionTask query) {
|
||||
return taskMapper.selectTaskList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GearProductionTaskWithDetailVo selectTaskWithDetail(Long taskId) {
|
||||
GearProductionTask task = taskMapper.selectTaskById(taskId);
|
||||
GearProductionTaskWithDetailVo vo = new GearProductionTaskWithDetailVo();
|
||||
vo.setTask(task);
|
||||
if (taskId == null) {
|
||||
vo.setProducts(null);
|
||||
vo.setMaterials(null);
|
||||
return vo;
|
||||
}
|
||||
vo.setProducts(productMapper.selectByTaskId(taskId));
|
||||
List<GearProductionTaskMaterialVo> materials = materialMapper.selectByTaskId(taskId);
|
||||
if (task != null && "2".equals(String.valueOf(task.getStatus())) && (materials == null || materials.isEmpty())) {
|
||||
regenerateReceiptMaterials(taskId);
|
||||
materials = materialMapper.selectByTaskId(taskId);
|
||||
}
|
||||
vo.setMaterials(materials);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long insertTaskWithDetail(GearProductionTaskWithDetailVo bo) {
|
||||
if (bo == null || bo.getTask() == null) {
|
||||
return null;
|
||||
}
|
||||
GearProductionTask task = bo.getTask();
|
||||
Long taskId = task.getTaskId() != null ? task.getTaskId() : IdUtil.getSnowflakeNextId();
|
||||
task.setTaskId(taskId);
|
||||
if (task.getDelFlag() == null) {
|
||||
task.setDelFlag("0");
|
||||
}
|
||||
if (task.getStatus() == null || String.valueOf(task.getStatus()).trim().isEmpty()) {
|
||||
task.setStatus("1");
|
||||
}
|
||||
task.setCreateTime(DateUtils.getNowDate());
|
||||
task.setUpdateTime(DateUtils.getNowDate());
|
||||
taskMapper.insertTask(task);
|
||||
|
||||
List<GearProductionTaskProduct> products = new ArrayList<>();
|
||||
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);
|
||||
});
|
||||
}
|
||||
if (!products.isEmpty()) {
|
||||
productMapper.insertBatch(products);
|
||||
}
|
||||
|
||||
return taskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean completeTask(Long taskId) {
|
||||
if (taskId == null) {
|
||||
return false;
|
||||
}
|
||||
GearProductionTask task = taskMapper.selectTaskById(taskId);
|
||||
if (task == null) {
|
||||
return false;
|
||||
}
|
||||
int updated = taskMapper.updateTaskStatus(taskId, "2", task.getUpdateBy(), DateUtils.getNowDate());
|
||||
if (updated <= 0) {
|
||||
return false;
|
||||
}
|
||||
regenerateReceiptMaterials(taskId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void regenerateReceiptMaterials(Long taskId) {
|
||||
materialMapper.deleteByTaskId(taskId);
|
||||
List<GearProductionTaskMaterial> required = materialMapper.selectRequirementByTaskId(taskId);
|
||||
if (required == null || required.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<GearProductionTaskMaterial> rows = new ArrayList<>();
|
||||
required.forEach(r -> {
|
||||
if (r == null || r.getMaterialId() == null) return;
|
||||
GearProductionTaskMaterial row = new GearProductionTaskMaterial();
|
||||
row.setLineId(IdUtil.getSnowflakeNextId());
|
||||
row.setTaskId(taskId);
|
||||
row.setMaterialId(r.getMaterialId());
|
||||
row.setMaterialRole(r.getMaterialRole());
|
||||
BigDecimal planQty = r.getPlanQty() == null ? BigDecimal.ZERO : r.getPlanQty();
|
||||
row.setPlanQty(planQty);
|
||||
row.setUsedQty(planQty);
|
||||
row.setUnit(r.getUnit());
|
||||
row.setRemark(null);
|
||||
rows.add(row);
|
||||
});
|
||||
if (!rows.isEmpty()) {
|
||||
materialMapper.insertBatch(rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gear.mes.production.mapper.GearProductionTaskMapper">
|
||||
|
||||
<select id="selectTaskList" parameterType="com.gear.mes.production.domain.GearProductionTask" resultType="com.gear.mes.production.domain.vo.GearProductionTaskListVo">
|
||||
SELECT
|
||||
t.task_id AS taskId,
|
||||
t.task_code AS taskCode,
|
||||
t.task_name AS taskName,
|
||||
t.status AS status,
|
||||
t.plan_start_time AS planStartTime,
|
||||
t.plan_end_time AS planEndTime,
|
||||
t.remark AS remark,
|
||||
t.create_time AS createTime,
|
||||
t.update_time AS updateTime,
|
||||
IFNULL(SUM(p.plan_qty), 0) AS planQty,
|
||||
IFNULL(SUM(p.finished_qty), 0) AS finishedQty,
|
||||
IFNULL(SUM(p.bad_qty), 0) AS badQty,
|
||||
GREATEST(IFNULL(SUM(p.plan_qty), 0) - IFNULL(SUM(p.finished_qty), 0), 0) AS unfinishedQty
|
||||
FROM gear_production_task t
|
||||
LEFT JOIN gear_production_task_product p ON t.task_id = p.task_id
|
||||
<where>
|
||||
t.del_flag = '0'
|
||||
<if test="status != null and status != ''"> AND t.status = #{status}</if>
|
||||
<if test="taskCode != null and taskCode != ''"> AND t.task_code LIKE CONCAT('%', #{taskCode}, '%')</if>
|
||||
<if test="taskName != null and taskName != ''"> AND t.task_name LIKE CONCAT('%', #{taskName}, '%')</if>
|
||||
<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
|
||||
ORDER BY t.update_time DESC, t.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectTaskById" resultType="com.gear.mes.production.domain.GearProductionTask">
|
||||
SELECT
|
||||
task_id AS taskId,
|
||||
task_code AS taskCode,
|
||||
task_name AS taskName,
|
||||
status AS status,
|
||||
plan_start_time AS planStartTime,
|
||||
plan_end_time AS planEndTime,
|
||||
remark AS remark,
|
||||
create_by AS createBy,
|
||||
create_time AS createTime,
|
||||
update_by AS updateBy,
|
||||
update_time AS updateTime
|
||||
FROM gear_production_task
|
||||
WHERE task_id = #{taskId}
|
||||
AND del_flag = '0'
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<insert id="insertTask" parameterType="com.gear.mes.production.domain.GearProductionTask">
|
||||
INSERT INTO gear_production_task (
|
||||
task_id,
|
||||
task_code,
|
||||
task_name,
|
||||
status,
|
||||
plan_start_time,
|
||||
plan_end_time,
|
||||
remark,
|
||||
del_flag,
|
||||
create_by,
|
||||
create_time,
|
||||
update_by,
|
||||
update_time
|
||||
) VALUES (
|
||||
#{taskId},
|
||||
#{taskCode},
|
||||
#{taskName},
|
||||
#{status},
|
||||
#{planStartTime},
|
||||
#{planEndTime},
|
||||
#{remark},
|
||||
#{delFlag},
|
||||
#{createBy},
|
||||
#{createTime},
|
||||
#{updateBy},
|
||||
#{updateTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateTask" parameterType="com.gear.mes.production.domain.GearProductionTask">
|
||||
UPDATE gear_production_task
|
||||
SET
|
||||
task_code = #{taskCode},
|
||||
task_name = #{taskName},
|
||||
status = #{status},
|
||||
plan_start_time = #{planStartTime},
|
||||
plan_end_time = #{planEndTime},
|
||||
remark = #{remark},
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
WHERE task_id = #{taskId}
|
||||
AND del_flag = '0'
|
||||
</update>
|
||||
|
||||
<update id="updateTaskStatus">
|
||||
UPDATE gear_production_task
|
||||
SET
|
||||
status = #{status},
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
WHERE task_id = #{taskId}
|
||||
AND del_flag = '0'
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gear.mes.production.mapper.GearProductionTaskMaterialMapper">
|
||||
|
||||
<select id="selectByTaskId" resultType="com.gear.mes.production.domain.vo.GearProductionTaskMaterialVo">
|
||||
SELECT
|
||||
m.line_id AS lineId,
|
||||
m.task_id AS taskId,
|
||||
m.material_id AS materialId,
|
||||
m.material_role AS materialRole,
|
||||
m.plan_qty AS planQty,
|
||||
m.used_qty AS usedQty,
|
||||
m.unit AS unit,
|
||||
m.remark AS remark,
|
||||
mm.material_name AS materialName,
|
||||
mm.material_type AS materialType,
|
||||
mm.spec AS spec,
|
||||
mm.model AS model,
|
||||
mm.factory AS factory
|
||||
FROM gear_production_task_material m
|
||||
LEFT JOIN mat_material mm ON m.material_id = mm.material_id
|
||||
WHERE m.task_id = #{taskId}
|
||||
ORDER BY m.material_role, m.line_id
|
||||
</select>
|
||||
|
||||
<select id="selectRequirementByTaskId" resultType="com.gear.mes.production.domain.GearProductionTaskMaterial">
|
||||
SELECT
|
||||
r.material_id AS materialId,
|
||||
CASE WHEN mm.material_type = 2 THEN 'main' ELSE 'aux' END AS materialRole,
|
||||
SUM(
|
||||
(
|
||||
CASE
|
||||
WHEN p.finished_qty IS NOT NULL AND p.finished_qty > 0 THEN p.finished_qty
|
||||
ELSE IFNULL(p.plan_qty, 0)
|
||||
END
|
||||
) * IFNULL(r.material_num, 0)
|
||||
) AS planQty,
|
||||
mm.unit 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
|
||||
WHERE p.task_id = #{taskId}
|
||||
GROUP BY r.material_id, mm.material_type, mm.unit
|
||||
ORDER BY mm.material_type DESC, MIN(IFNULL(r.sort, 0)), r.material_id
|
||||
</select>
|
||||
|
||||
<insert id="insertBatch">
|
||||
INSERT INTO gear_production_task_material (
|
||||
line_id,
|
||||
task_id,
|
||||
material_id,
|
||||
material_role,
|
||||
plan_qty,
|
||||
used_qty,
|
||||
unit,
|
||||
remark
|
||||
)
|
||||
VALUES
|
||||
<foreach collection="list" item="i" separator=",">
|
||||
(
|
||||
#{i.lineId},
|
||||
#{i.taskId},
|
||||
#{i.materialId},
|
||||
#{i.materialRole},
|
||||
#{i.planQty},
|
||||
#{i.usedQty},
|
||||
#{i.unit},
|
||||
#{i.remark}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByTaskId">
|
||||
DELETE FROM gear_production_task_material WHERE task_id = #{taskId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.gear.mes.production.mapper.GearProductionTaskProductMapper">
|
||||
|
||||
<select id="selectByTaskId" resultType="com.gear.mes.production.domain.vo.GearProductionTaskProductVo">
|
||||
SELECT
|
||||
p.line_id AS lineId,
|
||||
p.task_id AS taskId,
|
||||
p.product_id AS productId,
|
||||
p.plan_qty AS planQty,
|
||||
p.finished_qty AS finishedQty,
|
||||
p.bad_qty AS badQty,
|
||||
p.unit AS unit,
|
||||
p.remark AS remark,
|
||||
COALESCE(gp.product_name, mp.product_name) AS productName,
|
||||
COALESCE(gp.product_code, CAST(mp.product_id AS CHAR)) AS productCode,
|
||||
COALESCE(gp.type, mp.product_type) AS productType,
|
||||
mp.spec AS spec,
|
||||
mp.model AS model
|
||||
FROM gear_production_task_product p
|
||||
LEFT JOIN gear_product gp ON p.product_id = gp.product_id
|
||||
LEFT JOIN mat_product mp ON p.product_id = mp.product_id
|
||||
WHERE p.task_id = #{taskId}
|
||||
ORDER BY p.line_id
|
||||
</select>
|
||||
|
||||
<insert id="insertBatch">
|
||||
INSERT INTO gear_production_task_product (
|
||||
line_id,
|
||||
task_id,
|
||||
product_id,
|
||||
plan_qty,
|
||||
finished_qty,
|
||||
bad_qty,
|
||||
unit,
|
||||
remark
|
||||
)
|
||||
VALUES
|
||||
<foreach collection="list" item="i" separator=",">
|
||||
(
|
||||
#{i.lineId},
|
||||
#{i.taskId},
|
||||
#{i.productId},
|
||||
#{i.planQty},
|
||||
#{i.finishedQty},
|
||||
#{i.badQty},
|
||||
#{i.unit},
|
||||
#{i.remark}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<delete id="deleteByTaskId">
|
||||
DELETE FROM gear_production_task_product WHERE task_id = #{taskId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
31
gear-ui3/src/api/mes/productionTask.js
Normal file
31
gear-ui3/src/api/mes/productionTask.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listProductionTask(query) {
|
||||
return request({
|
||||
url: '/mes/production/task/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductionTask(taskId) {
|
||||
return request({
|
||||
url: '/mes/production/task/' + taskId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addProductionTask(data) {
|
||||
return request({
|
||||
url: '/mes/production/task',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function completeProductionTask(taskId) {
|
||||
return request({
|
||||
url: '/mes/production/task/' + taskId + '/complete',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
@@ -96,6 +96,19 @@ export const constantRoutes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/mes',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: 'production',
|
||||
component: () => import('@/views/mes/production/index.vue'),
|
||||
name: 'Production',
|
||||
meta: { title: '生产', icon: 'list', noCache: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{ path: '/user', component: Layout, hidden: true, redirect: 'noredirect', children: [ { path: 'profile/:activeTab?', component: () => import('@/views/system/user/profile/index'), name: 'Profile', meta: { title: '个人中心', icon: 'user' } } ] }, { path: '/mat/product', component: Layout, hidden: true, children: [ { path: 'detail/:id(\\d+)', component: () => import('@/views/mat/product/detail'), name: 'ProductDetail', meta: { title: '产品详情', activeMenu: '/mat/product' } } ] }
|
||||
]
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型" prop="materialType">
|
||||
<el-select v-model="form.materialType" placeholder="请选择物料类型" disabled>
|
||||
<el-option label="辅料" value="1" />
|
||||
<el-option label="辅料" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" prop="factory">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<el-table-column label="型号" align="center" prop="model" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="物料类型" align="center" prop="materialType">
|
||||
<template #default="scope">
|
||||
{{ scope.row.materialType === 1 ? '辅料' : '主材' }}
|
||||
{{ scope.row.materialType === 1 ? '辅料' : '原料' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="厂家" align="center" prop="factory" />
|
||||
@@ -95,7 +95,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型" prop="materialType">
|
||||
<el-select v-model="form.materialType" placeholder="请选择物料类型" disabled>
|
||||
<el-option label="主材" value="2" />
|
||||
<el-option label="原料" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="厂家" prop="factory">
|
||||
|
||||
782
gear-ui3/src/views/mes/production/index.vue
Normal file
782
gear-ui3/src/views/mes/production/index.vue
Normal file
@@ -0,0 +1,782 @@
|
||||
<template>
|
||||
<div class="production-page app-container" v-loading="loading">
|
||||
<el-card shadow="never" class="page-head">
|
||||
<div class="page-head__row">
|
||||
<div class="page-title">
|
||||
<div class="page-title__main">生产任务</div>
|
||||
<div class="page-title__sub">任务清单 / 明细 / 回执</div>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<el-button type="primary" @click="openAdd">新增任务</el-button>
|
||||
<el-button @click="loadTasks">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-head__row page-head__row--date">
|
||||
<div class="date-bar">
|
||||
<el-radio-group v-model="datePreset" size="small" @change="applyDatePreset">
|
||||
<el-radio-button label="today">今天</el-radio-button>
|
||||
<el-radio-button label="yesterday">昨天</el-radio-button>
|
||||
<el-radio-button label="last7">近7天</el-radio-button>
|
||||
<el-radio-button label="thisMonth">本月</el-radio-button>
|
||||
<el-radio-button label="custom">自定义</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-popover
|
||||
v-if="datePreset === 'custom'"
|
||||
v-model:visible="customPopoverOpen"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
width="360"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button size="small" plain class="custom-range-btn">{{ customRangeLabel }}</el-button>
|
||||
</template>
|
||||
<el-date-picker
|
||||
v-model="customRange"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
format="YYYY-MM-DD"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
unlink-panels
|
||||
teleported
|
||||
style="width: 320px;"
|
||||
@change="onCustomRangePicked"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-head__row page-head__row--filters">
|
||||
<el-form :inline="true" class="filter-form">
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filters.status" placeholder="全部" clearable style="width: 140px;">
|
||||
<el-option label="进行中" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
<el-option label="已暂停" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="filters.keyword" placeholder="任务名称/编号" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="page-stats">
|
||||
<el-tag type="info" effect="light">全部 {{ taskList.length }}</el-tag>
|
||||
<el-tag type="success" effect="light" style="margin-left: 8px;">进行中 {{ runningCount }}</el-tag>
|
||||
<el-tag type="warning" effect="light" style="margin-left: 8px;">暂停 {{ pausedCount }}</el-tag>
|
||||
<el-tag type="danger" effect="light" style="margin-left: 8px;">完成 {{ finishedCount }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="left-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>任务清单</span>
|
||||
<div>
|
||||
<el-button type="primary" link @click="openAdd">新增</el-button>
|
||||
<el-button type="primary" link @click="loadTasks">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="task-list">
|
||||
<el-empty v-if="!filteredTaskList.length" description="暂无任务" />
|
||||
<el-scrollbar v-else height="640px">
|
||||
<div
|
||||
v-for="t in filteredTaskList"
|
||||
:key="t.taskId"
|
||||
class="task-item"
|
||||
:class="{ active: t.taskId === selectedTaskId }"
|
||||
@click="selectTask(t.taskId)"
|
||||
>
|
||||
<div class="task-item__top">
|
||||
<div class="task-item__title">{{ t.taskName || t.taskCode || ('任务' + t.taskId) }}</div>
|
||||
<el-tag size="small" :type="statusTagType(t.status)" effect="light">{{ statusLabel(t.status) }}</el-tag>
|
||||
</div>
|
||||
<div class="task-item__sub">
|
||||
<span>计划 {{ formatQty(t.planQty) }}</span>
|
||||
<span style="margin-left: 10px;">已完 {{ formatQty(t.finishedQty) }}</span>
|
||||
<span style="margin-left: 10px;">未完 {{ formatQty(t.unfinishedQty) }}</span>
|
||||
</div>
|
||||
<el-progress :percentage="taskProgressPercent(t)" :stroke-width="8" :show-text="false" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="18">
|
||||
<el-card shadow="never" class="mb12">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>任务明细</span>
|
||||
<div v-if="selectedTask" class="card-header__right">
|
||||
<div class="card-meta">
|
||||
<span class="meta-main">{{ selectedTask.taskName || selectedTask.taskCode || ('任务' + selectedTask.taskId) }}</span>
|
||||
<el-tag size="small" :type="statusTagType(selectedTask.status)" effect="light" style="margin-left: 8px;">{{ statusLabel(selectedTask.status) }}</el-tag>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="String(selectedTask.status) === '1'"
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="completeLoading"
|
||||
@click="completeSelectedTask"
|
||||
>
|
||||
完成任务
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-if="!selectedTaskId" description="请选择任务" />
|
||||
<el-table v-else :data="productRows" size="small" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="产品" prop="productName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="规格" prop="spec" width="140" show-overflow-tooltip />
|
||||
<el-table-column label="型号" prop="model" width="140" show-overflow-tooltip />
|
||||
<el-table-column label="计划数量" prop="planQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.planQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已完成" prop="finishedQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.finishedQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="不良" prop="badQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.badQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" prop="unit" width="90" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>任务回执</span>
|
||||
<div v-if="selectedTask" class="card-meta">
|
||||
<span class="meta-sub">主材 / 辅材 / 产品概况</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-if="!selectedTaskId" description="请选择任务" />
|
||||
<el-tabs v-else type="border-card">
|
||||
<el-tab-pane label="主材">
|
||||
<el-table :data="mainMaterialRows" size="small" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="名称" prop="materialName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="计划" prop="planQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.planQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已用" prop="usedQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.usedQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" prop="unit" width="90" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="辅材">
|
||||
<el-table :data="auxMaterialRows" size="small" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="名称" prop="materialName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="计划" prop="planQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.planQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已用" prop="usedQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.usedQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" prop="unit" width="90" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="产品概况">
|
||||
<el-table :data="productRows" size="small" border stripe :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="产品" prop="productName" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column label="规格/型号" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ (scope.row.spec || '-') + ' / ' + (scope.row.model || '-') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划" prop="planQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.planQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已完成" prop="finishedQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.finishedQty) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="不良" prop="badQty" width="120" align="right">
|
||||
<template #default="scope">{{ formatQty(scope.row.badQty) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog v-model="addOpen" title="新增生产任务" width="1100px" top="5vh" append-to-body>
|
||||
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="100px">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="任务编号" prop="taskCode">
|
||||
<el-input v-model="addForm.taskCode" placeholder="可不填" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input v-model="addForm.taskName" placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="addForm.status" placeholder="请选择">
|
||||
<el-option label="进行中" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
<el-option label="已暂停" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="计划开始" prop="planStartTime">
|
||||
<el-date-picker v-model="addForm.planStartTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="计划结束" prop="planEndTime">
|
||||
<el-date-picker v-model="addForm.planEndTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="addForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<el-divider content-position="left">生产产品明细</el-divider>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<el-button type="primary" @click="addProductLine">新增产品行</el-button>
|
||||
</div>
|
||||
<el-table :data="addProducts" border size="small" :header-cell-style="{ background: '#f5f7fa' }">
|
||||
<el-table-column label="产品" min-width="220">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.productId" filterable clearable placeholder="请选择" style="width: 100%;" @change="onProductPicked(scope.row)">
|
||||
<el-option v-for="p in productOptions" :key="p.productId" :label="p.productName" :value="p.productId" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划数量" width="140" align="right">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.planQty" :min="0" :precision="4" controls-position="right" style="width: 120px;" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" width="140">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.unit" placeholder="单位" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.remark" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" link @click="removeProductLine(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="addOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="addSaving" @click="submitAdd">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Production">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { addProductionTask, completeProductionTask, getProductionTask, listProductionTask } from '@/api/mes/productionTask'
|
||||
import { listProductBase } from '@/api/mat/product'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const loading = ref(false)
|
||||
const taskList = ref([])
|
||||
const selectedTaskId = ref(null)
|
||||
const productRows = ref([])
|
||||
const materialRows = ref([])
|
||||
const datePreset = ref('today')
|
||||
const customRange = ref([])
|
||||
const customPopoverOpen = ref(false)
|
||||
const filters = ref({
|
||||
status: '1',
|
||||
keyword: '',
|
||||
beginTime: '',
|
||||
endTime: ''
|
||||
})
|
||||
|
||||
const addOpen = ref(false)
|
||||
const addSaving = ref(false)
|
||||
const completeLoading = ref(false)
|
||||
const addFormRef = ref()
|
||||
const addForm = ref({
|
||||
taskCode: '',
|
||||
taskName: '',
|
||||
status: '1',
|
||||
planStartTime: '',
|
||||
planEndTime: '',
|
||||
remark: ''
|
||||
})
|
||||
const addRules = {
|
||||
taskName: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const productOptions = ref([])
|
||||
const addProducts = ref([])
|
||||
|
||||
function toNumber(v) {
|
||||
const n = Number(v)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
function formatQty(v) {
|
||||
return toNumber(v).toFixed(2)
|
||||
}
|
||||
|
||||
function normalizeRole(v) {
|
||||
return String(v || '').toLowerCase()
|
||||
}
|
||||
|
||||
function pad2(n) {
|
||||
return String(n).padStart(2, '0')
|
||||
}
|
||||
|
||||
function formatDateTime(dt) {
|
||||
const d = dt instanceof Date ? dt : new Date(dt)
|
||||
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
|
||||
}
|
||||
|
||||
function startOfDay(d) {
|
||||
const x = new Date(d)
|
||||
x.setHours(0, 0, 0, 0)
|
||||
return x
|
||||
}
|
||||
|
||||
function endOfDay(d) {
|
||||
const x = new Date(d)
|
||||
x.setHours(23, 59, 59, 999)
|
||||
return x
|
||||
}
|
||||
|
||||
function addDays(d, n) {
|
||||
const x = new Date(d)
|
||||
x.setDate(x.getDate() + n)
|
||||
return x
|
||||
}
|
||||
|
||||
function startOfMonth(d) {
|
||||
const x = new Date(d)
|
||||
x.setDate(1)
|
||||
x.setHours(0, 0, 0, 0)
|
||||
return x
|
||||
}
|
||||
|
||||
function applyDatePreset() {
|
||||
const now = new Date()
|
||||
let begin = null
|
||||
let end = null
|
||||
const p = String(datePreset.value || '')
|
||||
if (p === 'today') {
|
||||
begin = startOfDay(now)
|
||||
end = endOfDay(now)
|
||||
} else if (p === 'yesterday') {
|
||||
const y = addDays(now, -1)
|
||||
begin = startOfDay(y)
|
||||
end = endOfDay(y)
|
||||
} else if (p === 'last7') {
|
||||
begin = startOfDay(addDays(now, -6))
|
||||
end = endOfDay(now)
|
||||
} else if (p === 'thisMonth') {
|
||||
begin = startOfMonth(now)
|
||||
end = endOfDay(now)
|
||||
} else if (p === 'custom') {
|
||||
if (!customRange.value || customRange.value.length !== 2) {
|
||||
const b = startOfDay(now)
|
||||
const e = endOfDay(now)
|
||||
customRange.value = [formatDateTime(b).slice(0, 10), formatDateTime(e).slice(0, 10)]
|
||||
filters.value.beginTime = formatDateTime(b)
|
||||
filters.value.endTime = formatDateTime(e)
|
||||
loadTasks()
|
||||
}
|
||||
customPopoverOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (begin && end) {
|
||||
filters.value.beginTime = formatDateTime(begin)
|
||||
filters.value.endTime = formatDateTime(end)
|
||||
loadTasks()
|
||||
}
|
||||
}
|
||||
|
||||
function applyCustomRange(v) {
|
||||
if (!v || v.length !== 2) return
|
||||
datePreset.value = 'custom'
|
||||
const begin = startOfDay(new Date(v[0]))
|
||||
const end = endOfDay(new Date(v[1]))
|
||||
filters.value.beginTime = formatDateTime(begin)
|
||||
filters.value.endTime = formatDateTime(end)
|
||||
loadTasks()
|
||||
}
|
||||
|
||||
const customRangeLabel = computed(() => {
|
||||
const v = customRange.value
|
||||
if (Array.isArray(v) && v.length === 2 && v[0] && v[1]) {
|
||||
return `${v[0]} ~ ${v[1]}`
|
||||
}
|
||||
return '选择日期范围'
|
||||
})
|
||||
|
||||
function onCustomRangePicked(v) {
|
||||
applyCustomRange(v)
|
||||
customPopoverOpen.value = false
|
||||
}
|
||||
|
||||
function statusLabel(v) {
|
||||
const s = String(v || '')
|
||||
if (s === '1') return '进行中'
|
||||
if (s === '2') return '已完成'
|
||||
if (s === '3') return '已暂停'
|
||||
return s || '-'
|
||||
}
|
||||
|
||||
function statusTagType(v) {
|
||||
const s = String(v || '')
|
||||
if (s === '1') return 'success'
|
||||
if (s === '2') return 'danger'
|
||||
if (s === '3') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function taskProgressPercent(t) {
|
||||
const plan = toNumber(t && t.planQty)
|
||||
const finished = toNumber(t && t.finishedQty)
|
||||
if (plan <= 0) return 0
|
||||
const p = Math.round((finished / plan) * 100)
|
||||
return Math.max(0, Math.min(100, p))
|
||||
}
|
||||
|
||||
function selectTask(taskId) {
|
||||
selectedTaskId.value = taskId
|
||||
loading.value = true
|
||||
return getProductionTask(taskId)
|
||||
.then((res) => {
|
||||
const data = res && res.data ? res.data : {}
|
||||
productRows.value = Array.isArray(data.products) ? data.products : []
|
||||
materialRows.value = Array.isArray(data.materials) ? data.materials : []
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const mainMaterialRows = computed(() => (materialRows.value || []).filter((r) => normalizeRole(r && r.materialRole) === 'main'))
|
||||
const auxMaterialRows = computed(() => (materialRows.value || []).filter((r) => normalizeRole(r && r.materialRole) === 'aux'))
|
||||
|
||||
const selectedTask = computed(() => (taskList.value || []).find((t) => String(t.taskId) === String(selectedTaskId.value)) || null)
|
||||
const runningCount = computed(() => (taskList.value || []).filter((t) => String(t && t.status) === '1').length)
|
||||
const finishedCount = computed(() => (taskList.value || []).filter((t) => String(t && t.status) === '2').length)
|
||||
const pausedCount = computed(() => (taskList.value || []).filter((t) => String(t && t.status) === '3').length)
|
||||
|
||||
const filteredTaskList = computed(() => {
|
||||
const list = Array.isArray(taskList.value) ? taskList.value : []
|
||||
const st = String(filters.value && filters.value.status ? filters.value.status : '')
|
||||
const kw = String(filters.value && filters.value.keyword ? filters.value.keyword : '').trim().toLowerCase()
|
||||
return list.filter((t) => {
|
||||
if (st && String(t && t.status) !== st) return false
|
||||
if (kw) {
|
||||
const a = String(t && t.taskName ? t.taskName : '').toLowerCase()
|
||||
const b = String(t && t.taskCode ? t.taskCode : '').toLowerCase()
|
||||
if (!a.includes(kw) && !b.includes(kw)) return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
function loadTasks() {
|
||||
loading.value = true
|
||||
const q = {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
status: filters.value && filters.value.status ? filters.value.status : undefined,
|
||||
beginTime: filters.value && filters.value.beginTime ? filters.value.beginTime : undefined,
|
||||
endTime: filters.value && filters.value.endTime ? filters.value.endTime : undefined
|
||||
}
|
||||
return listProductionTask(q)
|
||||
.then((res) => {
|
||||
const rows = (res && res.rows) ? res.rows : []
|
||||
taskList.value = rows
|
||||
if (!selectedTaskId.value && taskList.value.length) {
|
||||
const first = filteredTaskList.value && filteredTaskList.value.length ? filteredTaskList.value[0] : taskList.value[0]
|
||||
return selectTask(first.taskId)
|
||||
}
|
||||
if (selectedTaskId.value) {
|
||||
const hit = (taskList.value || []).some((t) => t.taskId === selectedTaskId.value)
|
||||
if (hit) return selectTask(selectedTaskId.value)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
addOpen.value = true
|
||||
addForm.value = {
|
||||
taskCode: '',
|
||||
taskName: '',
|
||||
status: '1',
|
||||
planStartTime: '',
|
||||
planEndTime: '',
|
||||
remark: ''
|
||||
}
|
||||
addProducts.value = []
|
||||
addProductLine()
|
||||
ensureProductOptions()
|
||||
}
|
||||
|
||||
function ensureProductOptions() {
|
||||
if (productOptions.value && productOptions.value.length) return
|
||||
listProductBase({ pageNum: 1, pageSize: 1000 }).then((res) => {
|
||||
productOptions.value = (res && res.rows) ? res.rows : []
|
||||
})
|
||||
}
|
||||
|
||||
function addProductLine() {
|
||||
addProducts.value.push({ productId: null, planQty: 0, unit: '', remark: '' })
|
||||
}
|
||||
|
||||
function removeProductLine(idx) {
|
||||
addProducts.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
function onProductPicked(row) {
|
||||
if (!row || !row.productId) return
|
||||
const hit = (productOptions.value || []).find((p) => String(p.productId) === String(row.productId))
|
||||
if (hit && !row.unit) {
|
||||
row.unit = ''
|
||||
}
|
||||
}
|
||||
|
||||
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 || ''
|
||||
}))
|
||||
|
||||
addSaving.value = true
|
||||
addProductionTask({ task, products })
|
||||
.then((res) => {
|
||||
const taskId = res && res.data ? res.data : null
|
||||
ElMessage.success('已新增')
|
||||
addOpen.value = false
|
||||
return loadTasks().then(() => {
|
||||
if (taskId != null) {
|
||||
selectedTaskId.value = taskId
|
||||
return selectTask(taskId)
|
||||
}
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
addSaving.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function completeSelectedTask() {
|
||||
const task = selectedTask.value
|
||||
const taskId = selectedTaskId.value
|
||||
if (!task || taskId == null) return
|
||||
completeLoading.value = true
|
||||
return ElMessageBox.confirm('完成后将根据产品配方自动生成主材/辅材回执,是否继续?', '确认完成', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => completeProductionTask(taskId))
|
||||
.then(() => {
|
||||
ElMessage.success('已完成')
|
||||
return loadTasks().then(() => selectTask(taskId))
|
||||
})
|
||||
.catch((e) => {
|
||||
const s = String(e || '')
|
||||
if (s && s !== 'cancel' && s !== 'close') {
|
||||
ElMessage.error('操作失败')
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
completeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
applyDatePreset()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.production-page {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.production-page :deep(.el-card) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.production-page :deep(.el-card__header) {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-header__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.meta-main {
|
||||
max-width: 520px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: #303133;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.meta-sub {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-head__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.page-head__row--filters {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.page-head__row--date {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.date-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.custom-range-btn {
|
||||
min-width: 180px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.page-title__main {
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.page-title__sub {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-form :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.page-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
padding: 10px 10px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.task-item:hover {
|
||||
border-color: rgba(64, 158, 255, 0.55);
|
||||
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.task-item.active {
|
||||
border-color: #409eff;
|
||||
background: rgba(64, 158, 255, 0.08);
|
||||
}
|
||||
|
||||
.task-item__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.task-item__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-item__sub {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mb12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user