9 Commits

Author SHA1 Message Date
b3dbdaef58 Merge remote-tracking branch 'origin/feat/erp-purchase-plan' into 0.8.X 2026-06-25 17:08:24 +08:00
d627a72aea refactor(cost views): 统一调整数值格式化精度为两位小数
修改了三个成本相关页面的数值格式化逻辑:
1.  energy.vue和assised.vue的formatNum方法,将保留小数位数从4位改为2位
2.  comprehensive.vue的多位置数值处理逻辑,统一将计算结果格式化为两位小数
2026-06-25 16:08:02 +08:00
16ba2dfa18 Merge branch '0.8.X' of http://49.232.154.205:10100/DeXun/klp-oa into 0.8.X 2026-06-25 15:44:28 +08:00
289555fd44 feat(aps): 新增APS排产管理模块完整功能
本次提交完成APS(高级计划与排程)模块的全量开发:
1.  新增CRM订单相关API接口,包含列表、详情、明细查询
2.  新增产需单相关CRUD API与页面,支持排产单管理与订单绑定
3.  新增按日期查询排产单、订单下钻详情页面
4.  为排产单实体类添加日期格式化注解,修复参数绑定问题
5.  统一封装APS模块主题样式,提供通用混入与变量
6.  实现产需单与销售订单的绑定解绑、明细自动生成功能
2026-06-25 15:44:26 +08:00
1c0b0da99e feat(wms): 退火计划选择合同提前
- 在退火计划表格中添加合同号选择功能,支持远程搜索和下拉选择
- 更新数据库表结构,在wms_furnace_plan_coil表中新增contract_id字段
- 修改后端实体类将contractNo改为contractId,并更新相关映射配置
- 调整前端页面布局,将左右两列比例从12:12调整为10:14
- 优化退火完成验证逻辑,要求所有钢卷必须绑定合同后才能完成操作
- 修复材料网格布局样式,改为固定2列显示
- 添加订单列表加载和搜索功能,支持按关键词过滤
- 更新完成退火对话框提示文案,明确合同绑定要求
2026-06-25 15:04:41 +08:00
7e9caf9bb7 feat(flow): 添加生产排程关联CRM订单功能
- 在生产排程VO中新增orderList字段存储关联的CRM订单列表
- 实现fillOrderList方法批量填充CRM订单数据
- 通过sch_sale_schedule_rel关联表建立排程与订单的关系
- 添加ICrmOrderService依赖注入以查询订单信息
- 在查询排程详情时自动加载相关联的CRM订单数据
- 配置klp-crm模块依赖以支持跨模块服务调用
2026-06-25 13:41:48 +08:00
86200d189d feat(schProdSchedule): 实现排产单主表与明细表的关联查询功能
- 在SchProdScheduleDetailVo中添加schedule字段用于关联主表信息
- 在SchProdScheduleVo中添加detailList字段用于关联明细列表
- 实现fillSchedule方法批量填充排产单明细中的主表信息
- 实现fillDetailList方法批量填充排产单主表中的明细列表
- 修改queryById、queryPageList、queryList方法以支持关联数据查询
- 添加必要的依赖注入和工具类导入
2026-06-25 13:23:30 +08:00
3277610ff7 feat(WmsMaterialCoil): 添加排除已绑定排产明细钢卷功能
- 在 WmsMaterialCoilBo 中新增 excludeScheduledDetail 字段
- 实现排产明细绑定钢卷的查询过滤逻辑
- 防止钢卷重复绑定到多个排产单
- 通过子查询优化排产明细关联查询性能
2026-06-25 13:18:50 +08:00
143853a87d feat(flow): 添加排产明细钢卷关系管理功能
- 创建排产明细钢卷关系实体类 SchDetailCoilRel
- 定义业务对象 SchDetailCoilRelBo 和视图对象 SchDetailCoilRelVo
- 实现排产明细钢卷关系服务接口 ISchDetailCoilRelService
- 开发控制器 SchDetailCoilRelController 提供 REST API
- 创建数据访问层 SchDetailCoilRelMapper 和 XML 映射文件
- 实现服务层业务逻辑 SchDetailCoilRelServiceImpl
- 集成分页查询、新增、修改、删除等基础操作
- 添加 Excel 导出功能和数据验证机制
2026-06-25 11:34:56 +08:00
36 changed files with 2835 additions and 55 deletions

View File

@@ -36,6 +36,9 @@ CREATE TABLE IF NOT EXISTS wms_furnace_plan_coil (
plan_coil_id BIGINT AUTO_INCREMENT PRIMARY KEY,
plan_id BIGINT NOT NULL COMMENT '计划ID',
coil_id BIGINT NOT NULL COMMENT '钢卷ID',
logic_warehouse_id BIGINT NULL COMMENT '逻辑库区去向(钢卷退火后目标逻辑库区)',
furnace_level TINYINT(1) NULL COMMENT '炉火层级1=一层2=二层3=三层)',
contract_id BIGINT NULL COMMENT '合同ID',
del_flag TINYINT DEFAULT 0 COMMENT '删除标记(0正常 1删除)',
create_by VARCHAR(64) NULL,
update_by VARCHAR(64) NULL,

View File

@@ -107,6 +107,12 @@ public class CrmOrderServiceImpl implements ICrmOrderService {
return TableDataInfo.build(result);
}
extracted(records);
return TableDataInfo.build(result);
}
private void extracted(List<CrmOrderVo> records) {
Set<String> userNames = records.stream()
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getUpdateBy()))
.filter(StringUtils::isNotBlank)
@@ -178,8 +184,6 @@ public class CrmOrderServiceImpl implements ICrmOrderService {
}
}
}
return TableDataInfo.build(result);
}
private QueryWrapper<CrmOrder> buildQueryWrapperPlus(CrmOrderBo bo) {
@@ -262,7 +266,9 @@ public class CrmOrderServiceImpl implements ICrmOrderService {
@Override
public List<CrmOrderVo> queryList(CrmOrderBo bo) {
QueryWrapper<CrmOrder> lqw = buildQueryWrapperPlus(bo);
return baseMapper.selectVoPagePlus(lqw);
List<CrmOrderVo> crmOrderVos = baseMapper.selectVoPagePlus(lqw);
extracted(crmOrderVos);
return crmOrderVos;
}
/**

View File

@@ -19,5 +19,9 @@
<groupId>com.klp</groupId>
<artifactId>klp-wms</artifactId>
</dependency>
<dependency>
<groupId>com.klp</groupId>
<artifactId>klp-crm</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,99 @@
package com.klp.flow.controller;
import java.util.List;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.klp.common.annotation.RepeatSubmit;
import com.klp.common.annotation.Log;
import com.klp.common.core.controller.BaseController;
import com.klp.common.core.domain.PageQuery;
import com.klp.common.core.domain.R;
import com.klp.common.core.validate.AddGroup;
import com.klp.common.core.validate.EditGroup;
import com.klp.common.enums.BusinessType;
import com.klp.common.utils.poi.ExcelUtil;
import com.klp.flow.domain.vo.SchDetailCoilRelVo;
import com.klp.flow.domain.bo.SchDetailCoilRelBo;
import com.klp.flow.service.ISchDetailCoilRelService;
import com.klp.common.core.page.TableDataInfo;
/**
* 排产明细钢卷关系
*
* @author klp
* @date 2026-06-25
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/flow/detailCoilRel")
public class SchDetailCoilRelController extends BaseController {
private final ISchDetailCoilRelService iSchDetailCoilRelService;
/**
* 查询排产明细钢卷关系列表
*/
@GetMapping("/list")
public TableDataInfo<SchDetailCoilRelVo> list(SchDetailCoilRelBo bo, PageQuery pageQuery) {
return iSchDetailCoilRelService.queryPageList(bo, pageQuery);
}
/**
* 导出排产明细钢卷关系列表
*/
@Log(title = "排产明细钢卷关系", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SchDetailCoilRelBo bo, HttpServletResponse response) {
List<SchDetailCoilRelVo> list = iSchDetailCoilRelService.queryList(bo);
ExcelUtil.exportExcel(list, "排产明细钢卷关系", SchDetailCoilRelVo.class, response);
}
/**
* 获取排产明细钢卷关系详细信息
*
* @param relId 主键
*/
@GetMapping("/{relId}")
public R<SchDetailCoilRelVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long relId) {
return R.ok(iSchDetailCoilRelService.queryById(relId));
}
/**
* 新增排产明细钢卷关系
*/
@Log(title = "排产明细钢卷关系", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SchDetailCoilRelBo bo) {
return toAjax(iSchDetailCoilRelService.insertByBo(bo));
}
/**
* 修改排产明细钢卷关系
*/
@Log(title = "排产明细钢卷关系", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SchDetailCoilRelBo bo) {
return toAjax(iSchDetailCoilRelService.updateByBo(bo));
}
/**
* 删除排产明细钢卷关系
*
* @param relIds 主键串
*/
@Log(title = "排产明细钢卷关系", businessType = BusinessType.DELETE)
@DeleteMapping("/{relIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] relIds) {
return toAjax(iSchDetailCoilRelService.deleteWithValidByIds(Arrays.asList(relIds), true));
}
}

View File

@@ -0,0 +1,45 @@
package com.klp.flow.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 排产明细钢卷关系对象 sch_detail_coil_rel
*
* @author klp
* @date 2026-06-25
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sch_detail_coil_rel")
public class SchDetailCoilRel extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 主键
*/
@TableId(value = "rel_id")
private Long relId;
/**
* 排产明细ID关联 sch_prod_schedule_detail.schedule_detail_id
*/
private Long scheduleDetailId;
/**
* 钢卷ID关联 wms_material_coil.coil_id
*/
private Long coilId;
/**
* 备注
*/
private String remark;
/**
* 删除标志0=正常1=已删除)
*/
@TableLogic
private Integer delFlag;
}

View File

@@ -0,0 +1,41 @@
package com.klp.flow.domain.bo;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.*;
/**
* 排产明细钢卷关系业务对象 sch_detail_coil_rel
*
* @author klp
* @date 2026-06-25
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SchDetailCoilRelBo extends BaseEntity {
/**
* 主键
*/
private Long relId;
/**
* 排产明细ID关联 sch_prod_schedule_detail.schedule_detail_id
*/
private Long scheduleDetailId;
/**
* 钢卷ID关联 wms_material_coil.coil_id
*/
private Long coilId;
/**
* 备注
*/
private String remark;
}

View File

@@ -8,6 +8,7 @@ import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
/**
* 排产单主业务对象 sch_prod_schedule
@@ -33,6 +34,8 @@ public class SchProdScheduleBo extends BaseEntity {
/**
* 生产日期(和合同号组成业务关联键)
*/
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date prodDate;
/**

View File

@@ -0,0 +1,49 @@
package com.klp.flow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.klp.common.annotation.ExcelDictFormat;
import com.klp.common.convert.ExcelDictConvert;
import lombok.Data;
/**
* 排产明细钢卷关系视图对象 sch_detail_coil_rel
*
* @author klp
* @date 2026-06-25
*/
@Data
@ExcelIgnoreUnannotated
public class SchDetailCoilRelVo {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long relId;
/**
* 排产明细ID关联 sch_prod_schedule_detail.schedule_detail_id
*/
@ExcelProperty(value = "排产明细ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "关=联,s=ch_prod_schedule_detail.schedule_detail_id")
private Long scheduleDetailId;
/**
* 钢卷ID关联 wms_material_coil.coil_id
*/
@ExcelProperty(value = "钢卷ID", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "关=联,w=ms_material_coil.coil_id")
private Long coilId;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -3,6 +3,7 @@ package com.klp.flow.domain.vo;
import java.math.BigDecimal;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableField;
import com.klp.common.annotation.ExcelDictFormat;
import com.klp.common.convert.ExcelDictConvert;
import lombok.Data;
@@ -69,5 +70,10 @@ public class SchProdScheduleDetailVo {
@ExcelProperty(value = "单行排产备注")
private String remark;
/**
* 关联的排产单主表信息
*/
@TableField(exist = false)
private SchProdScheduleVo schedule;
}

View File

@@ -5,10 +5,14 @@ import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableField;
import com.klp.common.annotation.ExcelDictFormat;
import com.klp.common.convert.ExcelDictConvert;
import com.klp.crm.domain.vo.CrmOrderVo;
import lombok.Data;
import java.util.List;
/**
* 排产单主视图对象 sch_prod_schedule
@@ -186,5 +190,16 @@ public class SchProdScheduleVo {
@ExcelProperty(value = "备注")
private String remark;
/**
* 关联的排产明细列表
*/
@TableField(exist = false)
private List<SchProdScheduleDetailVo> detailList;
/**
* 关联的CRM订单列表通过合同号 relContractNo 匹配 CrmOrder.contractCode
*/
@TableField(exist = false)
private List<CrmOrderVo> orderList;
}

View File

@@ -0,0 +1,15 @@
package com.klp.flow.mapper;
import com.klp.flow.domain.SchDetailCoilRel;
import com.klp.flow.domain.vo.SchDetailCoilRelVo;
import com.klp.common.core.mapper.BaseMapperPlus;
/**
* 排产明细钢卷关系Mapper接口
*
* @author klp
* @date 2026-06-25
*/
public interface SchDetailCoilRelMapper extends BaseMapperPlus<SchDetailCoilRelMapper, SchDetailCoilRel, SchDetailCoilRelVo> {
}

View File

@@ -0,0 +1,49 @@
package com.klp.flow.service;
import com.klp.flow.domain.SchDetailCoilRel;
import com.klp.flow.domain.vo.SchDetailCoilRelVo;
import com.klp.flow.domain.bo.SchDetailCoilRelBo;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 排产明细钢卷关系Service接口
*
* @author klp
* @date 2026-06-25
*/
public interface ISchDetailCoilRelService {
/**
* 查询排产明细钢卷关系
*/
SchDetailCoilRelVo queryById(Long relId);
/**
* 查询排产明细钢卷关系列表
*/
TableDataInfo<SchDetailCoilRelVo> queryPageList(SchDetailCoilRelBo bo, PageQuery pageQuery);
/**
* 查询排产明细钢卷关系列表
*/
List<SchDetailCoilRelVo> queryList(SchDetailCoilRelBo bo);
/**
* 新增排产明细钢卷关系
*/
Boolean insertByBo(SchDetailCoilRelBo bo);
/**
* 修改排产明细钢卷关系
*/
Boolean updateByBo(SchDetailCoilRelBo bo);
/**
* 校验并批量删除排产明细钢卷关系信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@@ -0,0 +1,109 @@
package com.klp.flow.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.flow.domain.bo.SchDetailCoilRelBo;
import com.klp.flow.domain.vo.SchDetailCoilRelVo;
import com.klp.flow.domain.SchDetailCoilRel;
import com.klp.flow.mapper.SchDetailCoilRelMapper;
import com.klp.flow.service.ISchDetailCoilRelService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 排产明细钢卷关系Service业务层处理
*
* @author klp
* @date 2026-06-25
*/
@RequiredArgsConstructor
@Service
public class SchDetailCoilRelServiceImpl implements ISchDetailCoilRelService {
private final SchDetailCoilRelMapper baseMapper;
/**
* 查询排产明细钢卷关系
*/
@Override
public SchDetailCoilRelVo queryById(Long relId){
return baseMapper.selectVoById(relId);
}
/**
* 查询排产明细钢卷关系列表
*/
@Override
public TableDataInfo<SchDetailCoilRelVo> queryPageList(SchDetailCoilRelBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SchDetailCoilRel> lqw = buildQueryWrapper(bo);
Page<SchDetailCoilRelVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询排产明细钢卷关系列表
*/
@Override
public List<SchDetailCoilRelVo> queryList(SchDetailCoilRelBo bo) {
LambdaQueryWrapper<SchDetailCoilRel> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<SchDetailCoilRel> buildQueryWrapper(SchDetailCoilRelBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SchDetailCoilRel> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getScheduleDetailId() != null, SchDetailCoilRel::getScheduleDetailId, bo.getScheduleDetailId());
lqw.eq(bo.getCoilId() != null, SchDetailCoilRel::getCoilId, bo.getCoilId());
return lqw;
}
/**
* 新增排产明细钢卷关系
*/
@Override
public Boolean insertByBo(SchDetailCoilRelBo bo) {
SchDetailCoilRel add = BeanUtil.toBean(bo, SchDetailCoilRel.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setRelId(add.getRelId());
}
return flag;
}
/**
* 修改排产明细钢卷关系
*/
@Override
public Boolean updateByBo(SchDetailCoilRelBo bo) {
SchDetailCoilRel update = BeanUtil.toBean(bo, SchDetailCoilRel.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SchDetailCoilRel entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除排产明细钢卷关系
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -12,12 +12,17 @@ import org.springframework.stereotype.Service;
import com.klp.flow.domain.bo.SchProdScheduleDetailBo;
import com.klp.flow.domain.vo.SchProdScheduleDetailVo;
import com.klp.flow.domain.SchProdScheduleDetail;
import com.klp.flow.domain.vo.SchProdScheduleVo;
import com.klp.flow.mapper.SchProdScheduleDetailMapper;
import com.klp.flow.mapper.SchProdScheduleMapper;
import com.klp.flow.service.ISchProdScheduleDetailService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 排产单明细Service业务层处理
@@ -30,13 +35,18 @@ import java.util.Collection;
public class SchProdScheduleDetailServiceImpl implements ISchProdScheduleDetailService {
private final SchProdScheduleDetailMapper baseMapper;
private final SchProdScheduleMapper scheduleMapper;
/**
* 查询排产单明细
*/
@Override
public SchProdScheduleDetailVo queryById(Long scheduleDetailId){
return baseMapper.selectVoById(scheduleDetailId);
SchProdScheduleDetailVo vo = baseMapper.selectVoById(scheduleDetailId);
if (vo != null) {
fillSchedule(Collections.singletonList(vo));
}
return vo;
}
/**
@@ -46,6 +56,7 @@ public class SchProdScheduleDetailServiceImpl implements ISchProdScheduleDetailS
public TableDataInfo<SchProdScheduleDetailVo> queryPageList(SchProdScheduleDetailBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SchProdScheduleDetail> lqw = buildQueryWrapper(bo);
Page<SchProdScheduleDetailVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
fillSchedule(result.getRecords());
return TableDataInfo.build(result);
}
@@ -55,7 +66,32 @@ public class SchProdScheduleDetailServiceImpl implements ISchProdScheduleDetailS
@Override
public List<SchProdScheduleDetailVo> queryList(SchProdScheduleDetailBo bo) {
LambdaQueryWrapper<SchProdScheduleDetail> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
List<SchProdScheduleDetailVo> list = baseMapper.selectVoList(lqw);
fillSchedule(list);
return list;
}
/**
* 批量填充主表信息
*/
private void fillSchedule(List<SchProdScheduleDetailVo> voList) {
if (voList == null || voList.isEmpty()) {
return;
}
Set<Long> scheduleIds = voList.stream()
.map(SchProdScheduleDetailVo::getScheduleId)
.filter(id -> id != null)
.collect(Collectors.toSet());
if (scheduleIds.isEmpty()) {
return;
}
List<SchProdScheduleVo> schedules = scheduleMapper.selectVoBatchIds(scheduleIds);
Map<Long, SchProdScheduleVo> scheduleMap = schedules != null
? schedules.stream().collect(Collectors.toMap(SchProdScheduleVo::getScheduleId, s -> s, (a, b) -> a))
: Collections.emptyMap();
for (SchProdScheduleDetailVo vo : voList) {
vo.setSchedule(scheduleMap.get(vo.getScheduleId()));
}
}
private LambdaQueryWrapper<SchProdScheduleDetail> buildQueryWrapper(SchProdScheduleDetailBo bo) {

View File

@@ -12,12 +12,24 @@ import org.springframework.stereotype.Service;
import com.klp.flow.domain.bo.SchProdScheduleBo;
import com.klp.flow.domain.vo.SchProdScheduleVo;
import com.klp.flow.domain.SchProdSchedule;
import com.klp.flow.domain.SchProdScheduleDetail;
import com.klp.flow.domain.vo.SchProdScheduleDetailVo;
import com.klp.flow.mapper.SchProdScheduleMapper;
import com.klp.flow.mapper.SchProdScheduleDetailMapper;
import com.klp.flow.mapper.SchSaleScheduleRelMapper;
import com.klp.flow.domain.SchSaleScheduleRel;
import com.klp.flow.service.ISchProdScheduleService;
import com.klp.crm.service.ICrmOrderService;
import com.klp.crm.domain.bo.CrmOrderBo;
import com.klp.crm.domain.vo.CrmOrderVo;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Collectors;
/**
* 排产单主Service业务层处理
@@ -30,13 +42,21 @@ import java.util.Collection;
public class SchProdScheduleServiceImpl implements ISchProdScheduleService {
private final SchProdScheduleMapper baseMapper;
private final SchProdScheduleDetailMapper detailMapper;
private final SchSaleScheduleRelMapper relMapper;
private final ICrmOrderService crmOrderService;
/**
* 查询排产单主
*/
@Override
public SchProdScheduleVo queryById(Long scheduleId){
return baseMapper.selectVoById(scheduleId);
SchProdScheduleVo vo = baseMapper.selectVoById(scheduleId);
if (vo != null) {
fillDetailList(Collections.singletonList(vo));
fillOrderList(Collections.singletonList(vo));
}
return vo;
}
/**
@@ -46,6 +66,8 @@ public class SchProdScheduleServiceImpl implements ISchProdScheduleService {
public TableDataInfo<SchProdScheduleVo> queryPageList(SchProdScheduleBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SchProdSchedule> lqw = buildQueryWrapper(bo);
Page<SchProdScheduleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
fillDetailList(result.getRecords());
fillOrderList(result.getRecords());
return TableDataInfo.build(result);
}
@@ -55,7 +77,95 @@ public class SchProdScheduleServiceImpl implements ISchProdScheduleService {
@Override
public List<SchProdScheduleVo> queryList(SchProdScheduleBo bo) {
LambdaQueryWrapper<SchProdSchedule> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
List<SchProdScheduleVo> list = baseMapper.selectVoList(lqw);
fillDetailList(list);
fillOrderList(list);
return list;
}
/**
* 批量填充明细列表
*/
private void fillDetailList(List<SchProdScheduleVo> voList) {
if (voList == null || voList.isEmpty()) {
return;
}
List<Long> scheduleIds = voList.stream()
.map(SchProdScheduleVo::getScheduleId)
.filter(id -> id != null)
.distinct()
.collect(Collectors.toList());
if (scheduleIds.isEmpty()) {
return;
}
LambdaQueryWrapper<SchProdScheduleDetail> detailQw = Wrappers.lambdaQuery();
detailQw.in(SchProdScheduleDetail::getScheduleId, scheduleIds);
List<SchProdScheduleDetailVo> allDetails = detailMapper.selectVoList(detailQw);
if (allDetails == null || allDetails.isEmpty()) {
return;
}
Map<Long, List<SchProdScheduleDetailVo>> detailMap = allDetails.stream()
.collect(Collectors.groupingBy(SchProdScheduleDetailVo::getScheduleId));
for (SchProdScheduleVo vo : voList) {
vo.setDetailList(detailMap.getOrDefault(vo.getScheduleId(), Collections.emptyList()));
}
}
/**
* 批量填充CRM订单列表通过 sch_sale_schedule_rel 关联表)
*/
private void fillOrderList(List<SchProdScheduleVo> voList) {
if (voList == null || voList.isEmpty()) {
return;
}
// 1. 收集所有 scheduleId
List<Long> scheduleIds = voList.stream()
.map(SchProdScheduleVo::getScheduleId)
.filter(id -> id != null)
.distinct()
.collect(Collectors.toList());
if (scheduleIds.isEmpty()) {
return;
}
// 2. 查关联表,获取 scheduleId → orderId 映射
LambdaQueryWrapper<SchSaleScheduleRel> relQw = Wrappers.lambdaQuery();
relQw.in(SchSaleScheduleRel::getScheduleId, scheduleIds);
List<SchSaleScheduleRel> rels = relMapper.selectList(relQw);
if (rels == null || rels.isEmpty()) {
return;
}
// 收集所有 orderId去重
Set<Long> orderIdSet = rels.stream()
.map(SchSaleScheduleRel::getOrderId)
.filter(id -> id != null)
.collect(Collectors.toSet());
// scheduleId → 关联的 orderId 列表
Map<Long, List<Long>> scheduleOrderIdsMap = rels.stream()
.collect(Collectors.groupingBy(
SchSaleScheduleRel::getScheduleId,
Collectors.mapping(SchSaleScheduleRel::getOrderId, Collectors.toList())
));
if (orderIdSet.isEmpty()) {
return;
}
// 3. 批量查 CrmOrder
CrmOrderBo orderBo = new CrmOrderBo();
orderBo.setOrderIds(new java.util.ArrayList<>(orderIdSet));
List<CrmOrderVo> allOrders = crmOrderService.queryList(orderBo);
Map<Long, CrmOrderVo> orderMap = allOrders != null
? allOrders.stream().collect(Collectors.toMap(CrmOrderVo::getOrderId, o -> o, (a, b) -> a))
: Collections.emptyMap();
// 4. 回填到 VO
for (SchProdScheduleVo vo : voList) {
List<Long> orderIds = scheduleOrderIdsMap.get(vo.getScheduleId());
if (orderIds != null) {
List<CrmOrderVo> orders = orderIds.stream()
.map(orderMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
vo.setOrderList(orders);
}
}
}
private LambdaQueryWrapper<SchProdSchedule> buildQueryWrapper(SchProdScheduleBo bo) {

View File

@@ -0,0 +1,20 @@
<?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.klp.flow.mapper.SchDetailCoilRelMapper">
<resultMap type="com.klp.flow.domain.SchDetailCoilRel" id="SchDetailCoilRelResult">
<result property="relId" column="rel_id"/>
<result property="scheduleDetailId" column="schedule_detail_id"/>
<result property="coilId" column="coil_id"/>
<result property="remark" column="remark"/>
<result property="createTime" column="create_time"/>
<result property="createBy" column="create_by"/>
<result property="updateTime" column="update_time"/>
<result property="updateBy" column="update_by"/>
<result property="delFlag" column="del_flag"/>
</resultMap>
</mapper>

View File

@@ -0,0 +1,27 @@
import request from '@/utils/request'
// 查询 CRM 订单列表(只读)
export function listCrmOrder(query) {
return request({
url: '/crm/order/list',
method: 'get',
params: query
})
}
// 查询 CRM 订单详情
export function getCrmOrder(orderId) {
return request({
url: '/crm/order/' + orderId,
method: 'get'
})
}
// 查询 CRM 订单明细列表
export function listCrmOrderItem(query) {
return request({
url: '/crm/orderItem/list',
method: 'get',
params: query
})
}

View File

@@ -0,0 +1,120 @@
import request from '@/utils/request'
// ====== 产需单SchProdScheduleCRUD ======
// 查询产需单列表
export function listRequirement(query) {
return request({
url: '/flow/prodSchedule/list',
method: 'get',
params: query
})
}
// 查询产需单详情
export function getRequirement(scheduleId) {
return request({
url: '/flow/prodSchedule/' + scheduleId,
method: 'get'
})
}
// 新增产需单
export function addRequirement(data) {
return request({
url: '/flow/prodSchedule',
method: 'post',
data
})
}
// 修改产需单
export function updateRequirement(data) {
return request({
url: '/flow/prodSchedule',
method: 'put',
data
})
}
// 删除产需单
export function delRequirement(scheduleIds) {
return request({
url: '/flow/prodSchedule/' + scheduleIds,
method: 'delete'
})
}
// ====== 产需单明细SchProdScheduleDetail ======
// 查询产需单明细列表
export function listRequirementDetail(query) {
return request({
url: '/flow/prodScheduleDetail/list',
method: 'get',
params: query
})
}
// 新增产需单明细
export function addRequirementDetail(data) {
return request({
url: '/flow/prodScheduleDetail',
method: 'post',
data
})
}
// 修改产需单明细
export function updateRequirementDetail(data) {
return request({
url: '/flow/prodScheduleDetail',
method: 'put',
data
})
}
// 删除产需单明细
export function delRequirementDetail(detailIds) {
return request({
url: '/flow/prodScheduleDetail/' + detailIds,
method: 'delete'
})
}
// ====== 销售订单-产需单关联SchSaleScheduleRel ======
// 查询关联列表
export function listRel(query) {
return request({
url: '/flow/saleScheduleRel/list',
method: 'get',
params: query
})
}
// 新增关联(绑定订单)
export function addRel(data) {
return request({
url: '/flow/saleScheduleRel',
method: 'post',
data
})
}
// 修改关联
export function updateRel(data) {
return request({
url: '/flow/saleScheduleRel',
method: 'put',
data
})
}
// 删除关联(解绑订单)
export function delRel(relIds) {
return request({
url: '/flow/saleScheduleRel/' + relIds,
method: 'delete'
})
}

View File

@@ -0,0 +1,35 @@
import request from '@/utils/request'
// 按日期查询产需单列表
export function listScheduleByDate(prodDate) {
return request({
url: '/flow/prodSchedule/list',
method: 'get',
params: { prodDate }
})
}
// 查询产需单明细(用于排产单聚合)
export function listScheduleDetail(scheduleId) {
return request({
url: '/flow/prodScheduleDetail/list',
method: 'get',
params: { scheduleId }
})
}
// 查询 CRM 订单信息(用于下钻)
export function getCrmOrderInfo(orderId) {
return request({
url: '/crm/order/' + orderId,
method: 'get'
})
}
// 查询 CRM 订单明细(用于下钻展示)
export function getCrmOrderItem(orderDetailId) {
return request({
url: '/crm/orderItem/' + orderDetailId,
method: 'get'
})
}

View File

@@ -735,7 +735,7 @@ export default {
if (!d.detailDate) return
if (!map[d.detailDate]) map[d.detailDate] = { detailDate: d.detailDate }
const sfx = d.shift && d.shift !== '0' ? '_' + d.shift : ''
map[d.detailDate]['q' + d.itemId + sfx] = d.quantity
map[d.detailDate]['q' + d.itemId + sfx] = d.quantity != null && d.quantity !== '' ? Number(d.quantity).toFixed(2) : d.quantity
})
this.gridRows = Object.values(map).sort((a,b) => a.detailDate.localeCompare(b.detailDate))
},
@@ -780,7 +780,7 @@ export default {
})
})
},
evalF(f) { const s = f.replace(/[^0-9+\-*/.()\s]/g,''); if(!s) return null; try { const r = new Function('return ('+s+')')(); return isFinite(r)?Math.round(r*10000)/10000:null } catch(e){ return null } },
evalF(f) { const s = f.replace(/[^0-9+\-*/.()\s]/g,''); if(!s) return null; try { const r = new Function('return ('+s+')')(); return isFinite(r)?(Math.round(r*100)/100).toFixed(2):null } catch(e){ return null } },
sortGrid() { this.gridRows.sort((a,b)=>{if(!a.detailDate)return 1;if(!b.detailDate)return -1;return a.detailDate.localeCompare(b.detailDate)}) },
async saveGrid() {
const rid = this.activeReport.reportId; if (!rid) return; this.saving = true
@@ -860,7 +860,7 @@ export default {
try {
const val = await handler(col.queryCondition, row, col, this.activeReport, shift)
if (val != null) {
const round3 = n => Math.round(n * 1000) / 1000
const round3 = n => (Math.round(n * 100) / 100).toFixed(2)
if (Array.isArray(val)) {
this.$set(row, 'q' + col.itemId + '_1', round3(val[0]))
this.$set(row, 'q' + col.itemId + '_2', round3(val[1]))
@@ -946,7 +946,7 @@ export default {
if (!col.queryCondition) return
const handler = queryHandlers[col.category] || queryHandlers['default']
if (!handler) { this.$modal.msgWarning(`类别 "${col.category}" 未注册查询处理器`); return }
const round3 = n => Math.round(n * 1000) / 1000
const round3 = n => (Math.round(n * 100) / 100).toFixed(2)
const tasks = []
for (const row of this.gridRows) {
if (!row.detailDate) continue

View File

@@ -130,7 +130,7 @@ export default {
else return row.lineType || '-'
}
},
formatNum(val) { if (val === null || val === undefined || val === '') return ''; const n = Number(val); if (isNaN(n)) return val; return parseFloat(n.toFixed(4)) },
formatNum(val) { if (val === null || val === undefined || val === '') return ''; const n = Number(val); if (isNaN(n)) return val; return parseFloat(n.toFixed(2)) },
async loadItems() { if (!this.allItems.length) { const r = await listItem({ pageNum:1, pageSize:999 }); this.allItems = r.rows || [] } },
async enter(row) { const r = await getProdReport(row.reportId); if (r.data) this.activeReport = r.data; else this.activeReport = row; this.loadGrid() }
}

View File

@@ -130,7 +130,7 @@ export default {
else return row.lineType || '-'
}
},
formatNum(val) { if (val === null || val === undefined || val === '') return ''; const n = Number(val); if (isNaN(n)) return val; return parseFloat(n.toFixed(4)) },
formatNum(val) { if (val === null || val === undefined || val === '') return ''; const n = Number(val); if (isNaN(n)) return val; return parseFloat(n.toFixed(2)) },
async loadItems() { if (!this.allItems.length) { const r = await listItem({ pageNum:1, pageSize:999 }); this.allItems = r.rows || [] } },
async enter(row) { const r = await getProdReport(row.reportId); if (r.data) this.activeReport = r.data; else this.activeReport = row; this.loadGrid() }
}

View File

@@ -52,7 +52,7 @@
@pagination="getList" />
<el-row :gutter="20" class="mt16">
<el-col :span="12">
<el-col :span="10">
<div class="custom-panel">
<div class="panel-header">
<span class="panel-title">领料列表</span>
@@ -102,7 +102,7 @@
</div>
</div>
</el-col>
<el-col :span="12">
<el-col :span="14">
<div class="custom-panel">
<div class="panel-header">
<span class="panel-title">退火计划</span>
@@ -136,8 +136,33 @@
</el-table-column>
<el-table-column label="入场卷号" align="center" prop="enterCoilNo" />
<el-table-column label="当前卷号" align="center" prop="coil.currentCoilNo" />
<el-table-column label="创建时间" align="center" prop="action" width="200">
<el-table-column label="合同号" align="center" prop="contractId" width="200">
<template slot-scope="scope">
<el-select
v-model="scope.row.contractId"
placeholder="搜索"
filterable
remote
:remote-method="(q) => remoteSearchOrders(q)"
:loading="orderLoading"
clearable
size="small"
@change="handleContractIdChange(scope.row)"
>
<el-option
v-for="item in orderList"
:key="item.orderId"
:label="`${item.contractCode} - ${item.companyName}`"
:value="item.orderId"
>
<span style="font-weight:bold">{{ item.contractCode }}</span>
<span style="color:#8492a6;font-size:13px;margin-left:8px">{{ item.companyName }}</span>
<span style="color:#c0c4cc;font-size:12px;float:right">{{ item.salesman }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="action">
<template slot-scope="scope">
<el-date-picker style="width: 185px" v-model="scope.row.createTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择创建时间" @change="handlePLanCoilChange(scope.row)" />
@@ -159,7 +184,7 @@
</el-row>
<el-dialog title="完成退火" :visible.sync="completeOpen" width="800px" append-to-body>
<div class="complete-tip">请为每条钢卷分配逻辑库区去向和关联合同未分配将无法完成</div>
<div class="complete-tip">请确认每条钢卷逻辑库区去向,所有钢卷须已在明细中绑定合同。</div>
<el-table :data="completeCoils" v-loading="completeLoading" height="360px">
<el-table-column label="入场钢卷号" prop="enterCoilNo" align="center" />
<el-table-column label="钢卷去向" align="center" width="270">
@@ -170,11 +195,9 @@
</el-select>
</template>
</el-table-column>
<el-table-column label="关联合同" align="center" width="270">
<el-table-column label="合同" align="center" width="140">
<template slot-scope="scope">
<div style="width: 100%; display: flex; align-items: center;">
<ContractSelect v-model="scope.row.contractId" placeholder="请选择合同" />
</div>
<span>{{ scope.row.contractCode || '-' }}</span>
</template>
</el-table-column>
</el-table>
@@ -238,6 +261,7 @@
import { listAnnealPlan, updateAnnealPlanCoil, getAnnealPlan, addAnnealPlan, updateAnnealPlan, delAnnealPlan, changeAnnealPlanStatus, inFurnace, completeAnnealPlan, listAnnealPlanCoils, bindAnnealPlanCoils, unbindAnnealPlanCoil } from "@/api/wms/annealPlan";
import { listAnnealFurnace } from "@/api/wms/annealFurnace";
import { listMaterialCoil } from "@/api/wms/coil";
import { listOrder } from "@/api/crm/order";
import { listCoilContractRel } from '@/api/wms/coilContractRel';
import { listWarehouse } from '@/api/wms/warehouse'
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
@@ -279,6 +303,8 @@ export default {
},
currentPlan: {},
coilList: [],
orderList: [],
orderLoading: false,
materialLoading: false,
materialTotal: 0,
materialList: [],
@@ -303,6 +329,7 @@ export default {
floatLayerConfig: {
columns: [
{ label: '入场钢卷号', prop: 'enterCoilNo' },
{ label: '合同号', prop: 'contractId' },
{ label: '当前钢卷号', prop: 'coil.currentCoilNo' },
{ label: '厂家卷号', prop: 'coil.supplierCoilNo' },
{ label: '逻辑库位', prop: 'coil.warehouseName' },
@@ -332,6 +359,7 @@ export default {
this.loadFurnaces();
this.getMaterialCoils();
this.loadWarehouses();
this.loadOrderList();
},
methods: {
getList() {
@@ -363,6 +391,33 @@ export default {
this.loadPlanCoils();
});
},
handleContractIdChange(row) {
updateAnnealPlanCoil(row).then(() => {
this.$message.success('合同号已更新');
});
},
loadOrderList() {
this.orderLoading = true;
listOrder({ pageNum: 1, pageSize: 100 }).then(response => {
this.orderList = response.rows || [];
this.orderLoading = false;
}).catch(() => {
this.orderLoading = false;
});
},
remoteSearchOrders(query) {
if (query) {
this.orderLoading = true;
listOrder({ pageNum: 1, pageSize: 20, keyword: query }).then(response => {
this.orderList = response.rows || [];
this.orderLoading = false;
}).catch(() => {
this.orderLoading = false;
});
} else {
this.loadOrderList();
}
},
getMaterialCoils() {
this.materialLoading = true;
listMaterialCoil(this.materialQueryParams).then(response => {
@@ -397,17 +452,10 @@ export default {
...item,
coilId: item.coilId,
enterCoilNo: item.enterCoilNo,
warehouseId: item.logicWarehouseId || null
warehouseId: item.logicWarehouseId || null,
contractId: item.contractId || null,
contractCode: (this.orderList.find(o => o.orderId === item.contractId) || {}).contractCode || null,
}));
// 查询每个钢卷绑定的合同号作为默认值
for (const coil of this.completeCoils) {
listCoilContractRel({ coilId: coil.coilId }).then(res => {
const rows = res.rows || []
if (rows.length > 0 && rows[0].contractId) {
this.$set(coil, 'contractId', rows[0].contractId)
}
})
}
this.completeLoading = false;
}).catch(() => {
this.completeLoading = false;
@@ -585,9 +633,14 @@ export default {
warehouseId: item.warehouseId,
contractId: item.contractId,
}));
const missing = locations.filter(item => !item.warehouseId || !item.contractId);
if (missing.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位和关联合同');
const missingWarehouse = locations.filter(item => !item.warehouseId);
if (missingWarehouse.length > 0) {
this.$message.warning('请先为所有钢卷分配实际库位');
return;
}
const missingContract = locations.filter(item => !item.contractId);
if (missingContract.length > 0) {
this.$message.warning('请先在明细中为所有钢卷绑定合同后再完成退火');
return;
}
this.completeLoading = true;
@@ -703,8 +756,7 @@ export default {
/* ========== 修复在这里 ========== */
.material-grid {
display: grid;
/* 核心修复:去掉固定 4 列,改用自动填充,实现真正自适应 */
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-template-columns: repeat(2, 1fr);
gap: 12px;
min-height: 120px;
/* 必须加,让 grid 不受父级弹性压缩影响 */
@@ -712,15 +764,6 @@ export default {
box-sizing: border-box;
}
/* 媒体查询只需要控制最小宽度即可,不用写死列数 */
@media (max-width: 768px) {
.material-grid {
grid-template-columns: 1fr;
}
}
/* =============================== */
.material-card {
border: 1px solid #e9ecf2;
border-radius: 8px;
@@ -806,3 +849,6 @@ export default {
padding-bottom: 40px;
}
</style>
<style>
html { overflow-y: scroll; }
</style>

View File

@@ -0,0 +1,445 @@
<template>
<div class="app-container" style="height: calc(100vh - 84px); display: flex;">
<!-- 左侧订单列表 -->
<div class="left-panel" v-loading="orderLoading" style="width: 30%; border-right: 1px solid #e4e7ed; overflow-y: auto;">
<!-- 筛选区 -->
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
<!-- 第一行搜索 + 按钮 + 记录数 -->
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
<el-input v-model="queryParams.keyword" placeholder="请输入关键字" clearable
@keyup.enter.native="handleSearch" style="width: 160px;" />
<el-button class="aps-btn-red" icon="el-icon-search" size="mini" @click="handleSearch">筛选</el-button>
<el-button icon="el-icon-sort" size="mini" @click="toggleMoreFilter"
:class="showMoreFilter ? 'aps-btn-red' : 'aps-btn-silver'"></el-button>
</div>
<div style="font-size: 12px; color: #909399;">
<span class="aps-total-count">{{ total }}</span> 条记录
</div>
</div>
<!-- 第二行日期范围 -->
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 4px;">
<div style="display: flex; align-items: center; gap: 4px; font-size: 13px; color: #606266;">
<span style="white-space: nowrap;">签订日期</span>
<el-date-picker clearable v-model="queryParams.signDateStart" type="date" value-format="yyyy-MM-dd"
placeholder="签订开始" style="width: 150px;" @change="handleSearch" />
<span>~</span>
<el-date-picker clearable v-model="queryParams.signDateEnd" type="date" value-format="yyyy-MM-dd"
placeholder="签订结束" style="width: 150px;" @change="handleSearch" />
</div>
<div style="display: flex; align-items: center; gap: 4px; font-size: 13px; color: #606266;">
<span style="white-space: nowrap;">交货日期</span>
<el-date-picker clearable v-model="queryParams.deliveryDateStart" type="date" value-format="yyyy-MM-dd"
placeholder="交货开始" style="width: 150px;" @change="handleSearch" />
<span>~</span>
<el-date-picker clearable v-model="queryParams.deliveryDateEnd" type="date" value-format="yyyy-MM-dd"
placeholder="交货结束" style="width: 150px;" @change="handleSearch" />
</div>
</div>
<!-- 第三行更多筛选条件 -->
<div v-show="showMoreFilter" class="more-filter"
style="margin-top: 8px; padding-top: 10px; border-top: 1px dashed #e4e7ed;">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
<el-form-item label="订单编号" prop="orderCode">
<el-input v-model="queryParams.orderCode" placeholder="请输入订单编号" clearable
@keyup.enter.native="handleSearch" />
</el-form-item>
<el-form-item label="销售员" prop="salesman">
<el-select v-model="queryParams.salesman" placeholder="请选择销售员" clearable style="width: 140px;">
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="订单状态" prop="orderStatus">
<el-select v-model="queryParams.orderStatus" placeholder="请选择订单状态" clearable style="width: 140px;">
<el-option v-for="(value, key) in ORDER_STATUS" :key="value" :label="key" :value="value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button class="aps-btn-silver" icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
<!-- 列表区域 -->
<div class="custom-list">
<div class="list-body">
<div
v-for="item in orderList"
:key="item.orderId"
class="list-item"
:class="{ 'list-item-active': currentOrder && currentOrder.orderId === item.orderId }"
@click="handleOrderClick(item)"
>
<!-- 第一行订单编号 + 客户公司 -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="font-weight: bold;">{{ item.contractName }}</div>
</div>
<div style="font-size: 12px; color: #606266;">{{ item.companyName }}</div>
</div>
<!-- 第二行销售员 + 合同号 -->
<div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
<span>销售员: {{ item.salesman }}</span>
<span style="margin-left: 20px;">合同号: {{ item.contractCode }}</span>
</div>
<!-- 第三行签订时间 + 交货日期 -->
<div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
<span>签订时间: {{ item.signTime }}</span>
<span style="margin-left: 20px;">交货日期: {{ item.deliveryDate }}</span>
</div>
<!-- 第四行签订地点 + 状态 -->
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 12px; color: #909399;">
签订地点: {{ item.signLocation || '-' }}
</div>
<el-tag
:type="statusTagType(item.orderStatus)"
size="small"
>{{ statusLabel(item.orderStatus) }}</el-tag>
</div>
</div>
<div v-if="orderList.length === 0 && !orderLoading" style="padding: 40px; text-align: center; color: #909399;">
暂无订单数据
</div>
</div>
</div>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
style="padding: 10px; margin-bottom: 10px !important;"
/>
</div>
<!-- 右侧内容区域 -->
<div class="right-panel" v-if="currentOrder && currentOrder.orderId" style="flex: 1; display: flex; flex-direction: column;">
<div class="detail-panel">
<!-- 订单基本信息含甲乙双方卡片 -->
<div class="detail-card">
<div class="detail-card-header">
<span>订单基本信息</span>
</div>
<div class="detail-card-body">
<div class="form-grid-2">
<div class="form-field"><label>销售员</label><div class="field-value">{{ currentOrder.salesman }}</div></div>
<div class="form-field"><label>合同号</label><div class="field-value">{{ currentOrder.contractCode }}</div></div>
<div class="form-field"><label>签订时间</label><div class="field-value">{{ currentOrder.signTime }}</div></div>
<div class="form-field"><label>交货日期</label><div class="field-value">{{ currentOrder.deliveryDate }}</div></div>
<div class="form-field"><label>订单总金额</label><div class="field-value">{{ currentOrder.orderAmount }}</div></div>
<div class="form-field"><label>签订地点</label><div class="field-value">{{ currentOrder.signLocation }}</div></div>
<div class="form-field" style="grid-column:1/3;"><label>备注</label><div class="field-value">{{ currentOrder.remark }}</div></div>
</div>
<div class="section-divider"></div>
<div class="form-grid-2">
<div class="form-field"><label>供方名称甲方</label><div class="field-value">{{ currentOrder.supplier }}</div></div>
<div class="form-field"><label>供方地址</label><div class="field-value">{{ currentOrder.supplierAddress }}</div></div>
<div class="form-field"><label>供方电话</label><div class="field-value">{{ currentOrder.supplierPhone }}</div></div>
<div class="form-field"><label>供方开户行</label><div class="field-value">{{ currentOrder.supplierBank }}</div></div>
<div class="form-field"><label>供方账号</label><div class="field-value">{{ currentOrder.supplierAccount }}</div></div>
<div class="form-field"><label>供方税号</label><div class="field-value">{{ currentOrder.supplierTaxNo }}</div></div>
<div class="form-field" style="border-top:1px solid #e8e8e8;padding-top:8px;margin-top:4px;grid-column:1/3;"></div>
<div class="form-field"><label>需方名称乙方</label><div class="field-value">{{ currentOrder.customer }}</div></div>
<div class="form-field"><label>需方地址</label><div class="field-value">{{ currentOrder.customerAddress }}</div></div>
<div class="form-field"><label>需方电话</label><div class="field-value">{{ currentOrder.customerPhone }}</div></div>
<div class="form-field"><label>需方开户行</label><div class="field-value">{{ currentOrder.customerBank }}</div></div>
<div class="form-field"><label>需方账号</label><div class="field-value">{{ currentOrder.customerAccount }}</div></div>
<div class="form-field"><label>需方税号</label><div class="field-value">{{ currentOrder.customerTaxNo }}</div></div>
</div>
</div>
</div>
<!-- 订单明细卡片 -->
<div class="detail-card">
<div class="detail-card-header">
<span>订单明细{{ productList.length }} </span>
</div>
<div class="detail-card-body" style="padding:0;">
<div v-if="productList.length > 0" class="aps-product-table-wrap">
<el-table :data="productList" border size="small" class="aps-product-table">
<el-table-column label="规格" prop="spec" min-width="120" />
<el-table-column label="材质" prop="material" width="100" align="center" />
<el-table-column label="数量(吨)" prop="quantity" width="90" align="right" />
<el-table-column label="含税单价" prop="taxPrice" width="100" align="right" />
<el-table-column label="含税总额" prop="taxTotal" width="100" align="right" />
<el-table-column label="无税单价" prop="noTaxPrice" width="100" align="right" />
<el-table-column label="无税总额" prop="noTaxTotal" width="100" align="right" />
<el-table-column label="税额" prop="taxAmount" width="90" align="right" />
<el-table-column label="备注" prop="remark" min-width="120" />
</el-table>
<div class="aps-product-summary">
<span>产品名称{{ productName }}</span>
<span>总数量{{ totalQuantity }} </span>
<span>含税总额{{ totalTaxTotal }}</span>
</div>
</div>
<el-empty v-else description="暂无产品明细" />
</div>
</div>
</div>
</div>
<div v-else style="flex: 1; display: flex; flex-direction: column;">
<el-empty description="选择订单后查看内容" />
</div>
</div>
</template>
<script>
import { listCrmOrder } from '@/api/aps/order'
import { parseProductContent } from '@/utils/productContent'
import { ORDER_STATUS } from '@/views/crm/js/enum'
import FileList from '@/components/FileList'
export default {
name: 'ApsOrder',
components: { FileList },
dicts: ['wip_pack_saleman'],
data() {
return {
ORDER_STATUS,
showMoreFilter: false,
orderLoading: false,
orderList: [],
total: 0,
currentOrder: null,
productList: [],
productName: '',
totalQuantity: 0,
totalTaxTotal: 0,
queryParams: {
keyword: '',
orderCode: '',
salesman: '',
orderStatus: undefined,
signDateStart: undefined,
signDateEnd: undefined,
deliveryDateStart: undefined,
deliveryDateEnd: undefined,
pageNum: 1,
pageSize: 10
}
}
},
created() {
this.getList()
},
methods: {
toggleMoreFilter() {
this.showMoreFilter = !this.showMoreFilter
},
handleSearch() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.queryParams.orderCode = ''
this.queryParams.salesman = ''
this.queryParams.orderStatus = undefined
this.queryParams.signDateStart = undefined
this.queryParams.signDateEnd = undefined
this.queryParams.deliveryDateStart = undefined
this.queryParams.deliveryDateEnd = undefined
this.handleSearch()
},
getList() {
this.orderLoading = true
listCrmOrder(this.queryParams).then(res => {
this.orderList = res.rows || []
this.total = res.total || 0
}).catch(() => {
this.orderList = []
this.total = 0
}).finally(() => {
this.orderLoading = false
})
},
handleOrderClick(order) {
this.currentOrder = order
this.parseProductContent(order)
},
parseProductContent(order) {
if (!order || !order.productContent) {
this.productList = []
this.productName = ''
this.totalQuantity = 0
this.totalTaxTotal = 0
return
}
const parsed = parseProductContent(order.productContent)
this.productList = parsed.products || []
this.productName = parsed.productName || ''
this.totalQuantity = parsed.totalQuantity || 0
this.totalTaxTotal = parsed.totalTaxTotal || 0
},
statusTagType(status) {
const map = { 0: 'info', 1: 'warning', 2: 'primary', 3: 'success', 4: 'success' }
return map[status] || 'info'
},
statusLabel(status) {
const labels = { 0: '待生产', 1: '生产中', 2: '部分发货', 3: '已发货', 4: '已签收' }
return labels[status] || '未知'
}
}
}
</script>
<style scoped lang="scss">
@import './scss/aps-theme.scss';
.app-container {
overflow: hidden;
padding: 0;
}
.left-panel {
height: calc(100vh - 84px);
box-sizing: border-box;
overflow-y: auto;
}
.right-panel {
height: calc(100vh - 84px);
overflow: hidden;
min-height: 0;
}
// ====== 左侧列表(参照 ContractList.vue ======
.custom-list {
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
}
.list-item {
padding: 10px;
border-bottom: 2px solid #dddddd;
cursor: pointer;
}
.list-item:hover {
background-color: $aps-silver-1;
}
.list-item-active {
background-color: $aps-red-1;
border-left: 3px solid $aps-red-2;
}
.aps-total-count {
color: $aps-red-2;
font-weight: bold;
}
.aps-btn-red {
@include aps-btn-red;
}
.aps-btn-silver {
@include aps-btn-silver;
}
// ====== 右侧详情面板(参照 HTML 设计稿) ======
.detail-panel {
flex: 1;
overflow-y: scroll;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
background: $aps-bg;
min-height: 0;
}
.detail-card {
background: $aps-white;
border: 1px solid $aps-border;
border-radius: $aps-radius;
box-shadow: $aps-shadow-sm;
}
.detail-card-header {
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
color: white;
padding: 8px 14px;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-card-body {
padding: 14px;
}
.form-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px 16px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 3px;
}
.form-field label {
font-size: 11px;
color: $aps-text-muted;
font-weight: 500;
}
.form-field .field-value {
font-size: 13px;
color: $aps-text;
padding: 4px 0;
border-bottom: 1px solid $aps-silver-mid;
}
// ====== 产品明细 ======
.aps-detail-title {
font-size: 15px;
font-weight: 600;
color: $aps-silver-5;
border-left: 3px solid $aps-red-2;
padding-left: 8px;
}
.aps-product-table-wrap {
border: 1px solid $aps-silver-3;
border-radius: 4px;
overflow: hidden;
}
.aps-product-table {
width: 100%;
::v-deep th {
background: $aps-silver-1 !important;
color: $aps-silver-5 !important;
font-weight: 600 !important;
}
}
.aps-product-summary {
display: flex;
gap: 24px;
padding: 8px 16px;
background: $aps-silver-1;
border-top: 1px solid $aps-silver-3;
font-size: 13px;
color: $aps-silver-5;
font-weight: 500;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
<template>
<div class="aps-sch-page">
<!-- 顶部工具栏 -->
<div class="aps-sch-toolbar">
<span class="aps-sch-label">生产日期</span>
<el-date-picker
v-model="queryDate"
type="date"
placeholder="选择生产日期"
value-format="yyyy-MM-dd"
size="small"
style="width:160px"
@change="handleDateChange"
/>
<el-button size="small" class="aps-btn-red" icon="el-icon-search" @click="handleQuery">查询</el-button>
<div class="aps-sch-summary" v-if="summaryText">
<span>{{ summaryText }}</span>
</div>
</div>
<!-- 排产明细卡片 -->
<div class="detail-card aps-sch-card">
<div class="detail-card-header">
<span>排产明细</span>
<span v-if="detailList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
{{ scheduleList.length }} 个产需单{{ detailList.length }} 条明细
</span>
</div>
<div class="detail-card-body" style="padding:0;" v-loading="schLoading">
<el-table
v-if="detailList.length > 0"
:data="detailList"
border
size="small"
class="aps-table"
@row-click="handleRowClick"
>
<el-table-column label="排产单号" prop="scheduleNo" min-width="140" fixed="left">
<template slot-scope="{ row }">
<span class="sch-link-text">{{ row.scheduleNo }}</span>
</template>
</el-table-column>
<el-table-column label="规格" prop="spec" min-width="120" />
<el-table-column label="材质" prop="material" width="90" align="center" />
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" />
<el-table-column label="品名" prop="productType" min-width="100" />
<el-table-column label="订货单位" prop="customerName" min-width="140" />
<el-table-column label="业务员" prop="businessUser" width="80" align="center" />
<el-table-column label="交货期(天)" prop="deliveryCycle" width="90" align="center" />
<el-table-column label="备注" prop="remark" min-width="140" />
</el-table>
<div v-else-if="!schLoading" style="padding:40px;text-align:center;color:#909399;">
{{ hasQueried ? '该日期暂无排产数据' : '请选择日期查询排产数据' }}
</div>
</div>
</div>
<!-- 下钻弹窗 -->
<el-dialog
title="来源订单信息"
:visible.sync="drillDialogVisible"
width="600px"
append-to-body
>
<div v-if="drillOrder" class="detail-card" style="border:none;box-shadow:none;">
<div class="detail-card-body">
<div class="form-grid-2">
<div class="form-field"><label>订单编号</label><div class="field-value">{{ drillOrder.orderCode }}</div></div>
<div class="form-field"><label>销售员</label><div class="field-value">{{ drillOrder.salesman }}</div></div>
<div class="form-field"><label>客户公司</label><div class="field-value">{{ drillOrder.companyName }}</div></div>
<div class="form-field"><label>联系人</label><div class="field-value">{{ drillOrder.contactPerson }}</div></div>
<div class="form-field"><label>联系电话</label><div class="field-value">{{ drillOrder.contactWay }}</div></div>
<div class="form-field"><label>交货日期</label><div class="field-value">{{ drillOrder.deliveryDate }}</div></div>
<div class="form-field"><label>合同号</label><div class="field-value">{{ drillOrder.contractCode }}</div></div>
<div class="form-field" style="grid-column:1/3;"><label>备注</label><div class="field-value">{{ drillOrder.remark }}</div></div>
</div>
</div>
</div>
<div v-else>
<el-empty description="未找到订单信息" />
</div>
</el-dialog>
</div>
</template>
<script>
import { listRequirement } from '@/api/aps/requirement'
import { getCrmOrderInfo } from '@/api/aps/schedule'
export default {
name: 'ApsSchedule',
data() {
const today = new Date()
const y = today.getFullYear()
const m = String(today.getMonth() + 1).padStart(2, '0')
const d = String(today.getDate()).padStart(2, '0')
return {
queryDate: `${y}-${m}-${d}`,
schLoading: false,
hasQueried: false,
scheduleList: [],
detailList: [],
summaryText: '',
drillDialogVisible: false,
drillOrder: null
}
},
created() {
this.handleQuery()
},
methods: {
handleDateChange() {
this.handleQuery()
},
handleQuery() {
if (!this.queryDate) {
this.$message.warning('请选择生产日期')
return
}
this.schLoading = true
this.hasQueried = true
this.detailList = []
this.scheduleList = []
// 后端 list 接口已通过 fillDetailList 填充 detailList
listRequirement({ prodDate: this.queryDate, pageNum: 1, pageSize: 999 }).then(res => {
const list = res.rows || []
this.scheduleList = list
// 扁平化所有 detailList
const merged = []
list.forEach(sch => {
const details = sch.detailList || []
details.forEach(d => {
merged.push({
...d,
scheduleNo: sch.scheduleNo,
customerName: sch.customerName,
businessUser: sch.businessUser,
deliveryCycle: sch.deliveryCycle,
_scheduleId: sch.scheduleId
})
})
})
this.detailList = merged
const totalWeight = merged.reduce((sum, d) => sum + (parseFloat(d.scheduleWeight) || 0), 0)
this.summaryText = `${list.length} 个产需单,${merged.length} 条明细,排产总吨数 ${totalWeight.toFixed(3)}`
}).catch(() => {
this.detailList = []
this.summaryText = ''
}).finally(() => {
this.schLoading = false
})
},
handleRowClick(row) {
// 通过 _scheduleId 找到产需单,然后找到关联的订单
const sch = this.scheduleList.find(s => s.scheduleId === row._scheduleId)
if (!sch || !sch.orderList || sch.orderList.length === 0) {
this.$message.warning('未找到关联订单')
return
}
// 取第一个关联订单展示
const order = sch.orderList[0]
getCrmOrderInfo(order.orderId).then(res => {
this.drillOrder = res.data
this.drillDialogVisible = true
}).catch(() => {
this.$message.warning('未找到来源订单')
})
}
}
}
</script>
<style scoped lang="scss">
@import './scss/aps-theme.scss';
.aps-sch-page {
height: 100%;
padding: 8px;
box-sizing: border-box;
background: $aps-bg;
display: flex;
flex-direction: column;
gap: 12px;
}
// 工具栏
.aps-sch-toolbar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
background: $aps-white;
border: 1px solid $aps-border;
border-radius: $aps-radius;
box-shadow: $aps-shadow-sm;
flex-shrink: 0;
}
.aps-sch-label {
font-size: 13px;
font-weight: 600;
color: $aps-text;
white-space: nowrap;
}
.aps-sch-summary {
margin-left: auto;
font-size: 12px;
color: $aps-text-muted;
background: $aps-silver-1;
padding: 4px 12px;
border-radius: $aps-radius;
border: 1px solid $aps-border;
}
// 排产卡片
.aps-sch-card {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
.detail-card-body {
flex: 1;
overflow: auto;
min-height: 0;
}
}
// 表格
.aps-table {
width: 100%;
::v-deep th {
background: $aps-silver-1 !important;
color: $aps-text !important;
font-weight: 600 !important;
}
::v-deep .el-table__body tr:hover > td {
background-color: $aps-red-1 !important;
cursor: pointer;
}
::v-deep td {
padding: 6px 8px;
}
}
.sch-link-text {
color: $aps-red-2;
font-weight: 500;
&:hover {
color: $aps-red-3;
text-decoration: underline;
}
}
// 复用卡片/网格变量
.aps-btn-red {
@include aps-btn-red;
}
.detail-card {
background: $aps-white;
border: 1px solid $aps-border;
border-radius: $aps-radius;
box-shadow: $aps-shadow-sm;
overflow: hidden;
}
.detail-card-header {
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
color: white;
padding: 8px 14px;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-card-body {
padding: 14px;
}
.form-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px 16px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 3px;
}
.form-field label {
font-size: 11px;
color: $aps-text-muted;
font-weight: 500;
}
.form-field .field-value {
font-size: 13px;
color: $aps-text;
padding: 4px 0;
border-bottom: 1px solid $aps-silver-mid;
}
</style>

View File

@@ -0,0 +1,143 @@
// ============================================
// APS 排产管理模块 — 银灰色 + 红色主题
// 每个页面通过 @import 引入并提供 scoped 命名空间
// ============================================
// ——— 颜色变量 ———
$aps-silver-1: #f5f5f5; // 最浅银灰(背景)
$aps-silver-2: #e8e8e8; // 浅银灰(卡片/列表行)
$aps-silver-3: #d9d9d9; // 中银灰(边框)
$aps-silver-4: #8c8c8c; // 深银灰(次要文字/图标)
$aps-silver-5: #595959; // 最深银灰(主标题/强调文字)
$aps-red-1: #fff1f0; // 极浅红(背景高亮)
$aps-red-2: #ff4d4f; // 标准红(主按钮/标签)
$aps-red-3: #cf1322; // 深红hover/边框强调)
$aps-red-4: #f5222d; // 辅助红
// ——— 混入:列表项 ———
@mixin aps-list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
margin-bottom: 6px;
border: 1px solid $aps-silver-3;
background-color: #fff;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 4px;
&:hover {
border-color: $aps-red-2;
background-color: $aps-red-1;
}
&.active {
border-left: 3px solid $aps-red-2;
border-color: $aps-red-2;
background-color: $aps-red-1;
box-shadow: 0 0 0 1px rgba($aps-red-2, 0.08) inset;
}
}
// ——— 混入:银色卡片 ———
@mixin aps-card {
background: #fff;
border: 1px solid $aps-silver-3;
border-radius: 6px;
padding: 16px;
}
// ——— 混入:银色按钮变体 ———
@mixin aps-btn-silver {
color: $aps-silver-5;
background: $aps-silver-1;
border-color: $aps-silver-3;
&:hover {
color: $aps-red-2;
border-color: $aps-red-2;
background: $aps-red-1;
}
}
// ——— 混入:红色按钮变体 ———
@mixin aps-btn-red {
color: #fff;
background: $aps-red-2;
border-color: $aps-red-2;
&:hover {
background: $aps-red-3;
border-color: $aps-red-3;
}
}
// ——— 混入:状态标签 ———
@mixin aps-status-tag($bg, $color) {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 12px;
background: $bg;
color: $color;
}
// ——— 详情卡片通用变量(参照 HTML 设计稿) ———
$aps-bg: #f4f5f7;
$aps-white: #ffffff;
$aps-border: #c8cdd2;
$aps-text: #2c3e50;
$aps-text-muted: #7f8c8d;
$aps-silver-mid: #d5d8dc;
$aps-shadow-sm: 0 1px 4px rgba(0,0,0,0.08);
$aps-radius: 4px;
// ——— 混入:详情卡片 ———
@mixin aps-detail-card {
background: $aps-white;
border: 1px solid $aps-border;
border-radius: $aps-radius;
box-shadow: $aps-shadow-sm;
overflow: hidden;
}
// ——— 混入:卡片头部渐变 ———
@mixin aps-card-header {
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
color: white;
padding: 8px 14px;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
// ——— 混入表单网格2列 ———
@mixin aps-form-grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px 16px;
}
// ——— 混入:表单字段 ———
@mixin aps-form-field {
display: flex;
flex-direction: column;
gap: 3px;
label {
font-size: 11px;
color: $aps-text-muted;
font-weight: 500;
}
.field-value {
font-size: 13px;
color: $aps-text;
padding: 4px 0;
border-bottom: 1px solid $aps-silver-mid;
}
}

View File

@@ -47,9 +47,9 @@ public class WmsFurnacePlanCoil extends BaseEntity {
private Integer furnaceLevel;
/**
* 合同
* 合同ID
*/
private String contractNo;
private Long contractId;
/**
* 删除标志0=正常1=已删除)

View File

@@ -43,9 +43,9 @@ public class WmsFurnacePlanCoilBo extends BaseEntity {
private Integer furnaceLevel;
/**
* 合同
* 合同ID
*/
private String contractNo;
private Long contractId;
/**
* 钢卷ID列表逗号分隔

View File

@@ -432,5 +432,11 @@ public class WmsMaterialCoilBo extends BaseEntity {
*/
@TableField(exist = false)
private Boolean excludeAccepted;
/**
* 是否排除已绑定排产明细的钢卷true=列表不返回已绑定排产明细的钢卷)
*/
@TableField(exist = false)
private Boolean excludeScheduledDetail;
}

View File

@@ -34,8 +34,8 @@ public class WmsFurnacePlanCoilVo {
@ExcelProperty(value = "炉火层级")
private Integer furnaceLevel;
@ExcelProperty(value = "合同")
private String contractNo;
@ExcelProperty(value = "合同ID")
private Long contractId;
@ExcelProperty(value = "入场钢卷号")
private String enterCoilNo;

View File

@@ -64,7 +64,7 @@ public class WmsFurnacePlanCoilServiceImpl implements IWmsFurnacePlanCoilService
lqw.eq(bo.getCoilId() != null, WmsFurnacePlanCoil::getCoilId, bo.getCoilId());
lqw.eq(bo.getLogicWarehouseId() != null, WmsFurnacePlanCoil::getLogicWarehouseId, bo.getLogicWarehouseId());
lqw.eq(bo.getFurnaceLevel() != null, WmsFurnacePlanCoil::getFurnaceLevel, bo.getFurnaceLevel());
lqw.eq(bo.getContractNo() != null, WmsFurnacePlanCoil::getContractNo, bo.getContractNo());
lqw.eq(bo.getContractId() != null, WmsFurnacePlanCoil::getContractId, bo.getContractId());
return lqw;
}

View File

@@ -392,6 +392,11 @@ public class WmsFurnacePlanServiceImpl implements IWmsFurnacePlanService {
if (targetLocation == null) {
throw new ServiceException("钢卷" + coil.getEnterCoilNo() + "未分配库位");
}
// 校验合同ID必须所有钢卷都已绑定合同
Long contractId = contractIdMap.get(coil.getCoilId());
if (contractId == null) {
throw new ServiceException("钢卷" + coil.getEnterCoilNo() + "未绑定合同,请先在明细中设置合同");
}
WmsMaterialCoil oldCoil = materialCoilMapper.selectById(coil.getCoilId());
if (oldCoil == null) {
@@ -414,7 +419,7 @@ public class WmsFurnacePlanServiceImpl implements IWmsFurnacePlanService {
updateBo.setStatus(0);
updateBo.setExportBy(null);
updateBo.setExportTime(null);
updateBo.setContractId(contractIdMap.get(coil.getCoilId()));
updateBo.setContractId(contractId);
materialCoilService.updateByBo(updateBo, "annealing");

View File

@@ -1128,6 +1128,10 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
if (Boolean.TRUE.equals(bo.getExcludeAccepted())) {
qw.apply("NOT EXISTS (SELECT 1 FROM ts_accept_coil_rel acr WHERE acr.del_flag = 0 AND acr.coil_id = mc.coil_id)");
}
// 排除已绑定排产明细的钢卷(防止重复绑定排产单)
if (Boolean.TRUE.equals(bo.getExcludeScheduledDetail())) {
qw.apply("NOT EXISTS (SELECT 1 FROM sch_prod_schedule_detail_coil sdc WHERE sdc.del_flag = 0 AND sdc.coil_id = mc.coil_id)");
}
// 组合 item_id 条件:改为使用 EXISTS 子查询,替代预查询 + IN
boolean hasSelectType = StringUtils.isNotBlank(bo.getSelectType());

View File

@@ -10,7 +10,7 @@
<result property="coilId" column="coil_id"/>
<result property="logicWarehouseId" column="logic_warehouse_id"/>
<result property="furnaceLevel" column="furnace_level"/>
<result property="contractNo" column="contract_no"/>
<result property="contractId" column="contract_id"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="updateBy" column="update_by"/>

View File

@@ -3976,6 +3976,7 @@ CREATE TABLE `wms_furnace_plan_coil` (
`coil_id` bigint NOT NULL COMMENT '钢卷ID',
`logic_warehouse_id` bigint NULL DEFAULT NULL COMMENT '逻辑库区去向(钢卷退火后目标逻辑库区)',
`furnace_level` tinyint(1) NULL DEFAULT NULL COMMENT '炉火层级1=一层2=二层3=三层)',
`contract_id` bigint NULL DEFAULT NULL COMMENT '合同ID',
`del_flag` tinyint NULL DEFAULT 0 COMMENT '删除标记(0正常 1删除)',
`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,