gongchunyi
2026-04-27 0be02df3d287f802c76e5738916301a877dfaa0e
feat: 生产报工与报工台账功能更改完成
已添加8个文件
已修改15个文件
1298 ■■■■ 文件已修改
doc/20260427_alter_production_product_main_add_report_duration.sql 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260427_alter_report_duration_minutes_to_decimal.sql 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260427_create_table_production_product_report_daily.sql 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductWorkOrderController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionReportDailySummaryDto.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionReportStateDto.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductOrderMapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductWorkOrderMapper.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionProductMainMapper.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionProductReportDailyMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductReportDaily.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionProductMainService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 561 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductOrderMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductWorkOrderMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductReportDailyMapper.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysUserMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260427_alter_production_product_main_add_report_duration.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
alter table production_product_main
    add column report_start_time datetime null comment '报工开始时间' after status,
    add column report_end_time datetime null comment '报工结束时间' after report_start_time,
    add column report_duration_minutes decimal(16,2) null comment '实际报工时长(分钟)' after report_end_time;
doc/20260427_alter_report_duration_minutes_to_decimal.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
alter table production_product_main
    modify column report_duration_minutes decimal(16,2) null comment '实际报工时长(分钟)';
alter table production_product_report_daily
    modify column duration_minutes decimal(16,2) not null comment '当日时长(分钟)';
doc/20260427_create_table_production_product_report_daily.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
drop table if exists production_product_report_daily;
create table production_product_report_daily
(
    id                            bigint auto_increment primary key,
    product_main_id               bigint   not null comment '报工主表ID',
    work_order_id                 bigint   not null comment '工单ID',
    product_process_route_item_id bigint   not null comment '工艺路线项目ID',
    user_id                       bigint   not null comment '报工人ID',
    report_date                   date     not null comment '报工日期(按天统计)',
    start_time                    datetime not null comment '当日开始时间',
    end_time                      datetime not null comment '当日结束时间',
    duration_minutes              decimal(16,2) not null comment '当日时长(分钟)',
    create_time                   datetime null comment '创建时间',
    create_user                   int      null comment '创建用户',
    update_time                   datetime null comment '修改时间',
    update_user                   int      null comment '修改用户',
    tenant_id                     bigint   not null comment '租户ID',
    dept_id                       bigint   null comment '部门ID',
    key idx_product_main_id (product_main_id),
    key idx_work_order_user_date (work_order_id, user_id, report_date),
    key idx_user_date (user_id, report_date)
) comment '生产报工-每日时长明细';
src/main/java/com/ruoyi/production/controller/ProductWorkOrderController.java
@@ -36,7 +36,7 @@
    @PostMapping ("/updateProductWorkOrder")
    public R updateProductWorkOrder(@RequestBody ProductWorkOrderDto productWorkOrderDto) {
        return R.ok(productWorkOrderservice.updateProductWorkOrder(productWorkOrderDto));
    }
    }
    /**
     * pda根据二维码的工单id查询数据
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java
@@ -3,18 +3,20 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.dto.ProductProcessRouteItemDto;
import com.ruoyi.production.dto.ProductionProductMainDto;
import com.ruoyi.production.dto.SalesLedgerProductionAccountingDto;
import com.ruoyi.production.dto.ProductionReportDailySummaryDto;
import com.ruoyi.production.dto.ProductionReportStateDto;
import com.ruoyi.production.pojo.ProductionProductMain;
import com.ruoyi.production.service.ProductionProductMainService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.time.LocalDate;
import java.util.List;
@RequestMapping("productionProductMain")
@@ -31,9 +33,48 @@
     * @param productionProductMainDto
     * @return
     */
    @ApiOperation("报工台账汇总分页(当前登录人)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "current", value = "页码", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "workOrderNo", value = "工单编号(模糊)", dataType = "string", paramType = "query"),
            @ApiImplicitParam(name = "workOrderStatus", value = "工单状态", dataType = "string", paramType = "query")
    })
    @GetMapping("listPage")
    public R page(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
    public R<?> page(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.listPageProductionProductMainDto(page, productionProductMainDto));
    }
    /**
     * æŠ¥å·¥æ˜Žç»†æŸ¥è¯¢(每条报工记录)
     */
    @ApiOperation("报工明细分页(每条报工记录, å½“前登录人)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "current", value = "页码", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "workOrderId", value = "工单ID(建议必传)", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "startDate", value = "开始日期(按结束时间过滤, yyyy-MM-dd)", dataType = "string", paramType = "query"),
            @ApiImplicitParam(name = "endDate", value = "结束日期(按结束时间过滤, yyyy-MM-dd)", dataType = "string", paramType = "query")
    })
    @GetMapping("listPageDetail")
    public R<?> pageDetail(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.listPageProductionProductMainDetailDto(page, productionProductMainDto));
    }
    /**
     * æŠ¥å·¥æ˜Žç»†æ±‡æ€»(每个人员每天)
     */
    @ApiOperation("报工每日汇总分页(每人每天, å½“前登录人)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "current", value = "页码", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "size", value = "每页数量", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "workOrderId", value = "工单ID(建议必传)", dataType = "long", paramType = "query"),
            @ApiImplicitParam(name = "startDate", value = "开始日期(report_date, yyyy-MM-dd)", dataType = "string", paramType = "query"),
            @ApiImplicitParam(name = "endDate", value = "结束日期(report_date, yyyy-MM-dd)", dataType = "string", paramType = "query")
    })
    @GetMapping("listPageDaily")
    public R<?> pageDaily(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.listPageProductionProductMainDailyDto(page, productionProductMainDto));
    }
    /**
@@ -42,13 +83,41 @@
     * @return
     */
    @PostMapping("addProductMain")
    public R addProductMain(@RequestBody ProductionProductMainDto productionProductMainDto) {
    public R<?> addProductMain(@RequestBody ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.addProductMain(productionProductMainDto));
    }
    /**
     * æ‰«ç åŽæŸ¥è¯¢å½“前用户是否存在进行中的报工
     */
    @ApiOperation("查询进行中的报工(当前登录人)")
    @GetMapping("getRunning")
    public R<ProductionProductMain> getRunning(Long workOrderId, Long productProcessRouteItemId) {
        ProductionProductMain productionProductMain = productionProductMainService.getRunning(workOrderId, productProcessRouteItemId);
        return R.ok(productionProductMain);
    }
    /**
     * æ¯æ—¥æŠ¥å·¥æ—¶é•¿æ±‡æ€»(当前登录人)
     */
    @ApiOperation("每日报工时长汇总(当前登录人)")
    @GetMapping("dailyDuration")
    public R<List<ProductionReportDailySummaryDto>> dailyDuration(Long workOrderId, Long productProcessRouteItemId, LocalDate startDate, LocalDate endDate) {
        return R.ok(productionProductMainService.dailyDuration(workOrderId, productProcessRouteItemId, startDate, endDate));
    }
    /**
     * æŸ¥è¯¢æŠ¥å·¥çŠ¶æ€: 1-开始报工 2-结束报工
     */
    @ApiOperation("查询报工状态(当前登录人)")
    @GetMapping("reportState")
    public R<ProductionReportStateDto> reportState(Long workOrderId, Long productProcessRouteItemId) {
        return R.ok(productionProductMainService.reportState(workOrderId, productProcessRouteItemId));
    }
    @ApiOperation("删除报工")
    @DeleteMapping("/delete")
    public R delete(@RequestBody ProductionProductMainDto productionProductMainDto) {
    public R<?> delete(@RequestBody ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.removeProductMain(productionProductMainDto.getId()));
    }
src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.production.pojo.ProductWorkOrder;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -10,6 +11,7 @@
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(value = "ProductWorkOrderDto", description = "产品工单分页/报工视角返回 DTO")
public class ProductWorkOrderDto extends ProductWorkOrder {
    //产品名称
@@ -54,4 +56,15 @@
    @TableField(exist = false)
    private Long currentUserId;
    /**
     * ä»Šæ—¥æŠ¥å·¥çŠ¶æ€(仅 type=2 æ—¶å›žå¡«):1-未开始 2-已开始(进行中) 3-已结束
     */
    @ApiModelProperty("今日报工状态(1-未开始 2-已开始 3-已结束)")
    @TableField(exist = false)
    private Integer todayReportState;
    @ApiModelProperty("报工时间总和(分钟)")
    @TableField(exist = false)
    private BigDecimal totalReportDurationMinutes;
}
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
@@ -4,16 +4,19 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.production.pojo.ProductionProductMain;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = true)
@ExcelIgnoreUnannotated
@ApiModel(value = "ProductionProductMainDto", description = "生产报工台账/明细 DTO")
public class ProductionProductMainDto extends ProductionProductMain {
    @ApiModelProperty(value = "工单编号")
    @Excel(name = "工单编号")
@@ -50,8 +53,19 @@
    @Excel(name = "销售合同号")
    private String salesContractNo;
    @ApiModelProperty(value = "生产订单号")
    @Excel(name = "生产订单号")
    private String productOrderNpsNo;
    @ApiModelProperty(value = "开始日期(明细查询用)")
    private LocalDate startDate;
    @ApiModelProperty(value = "结束日期(明细查询用)")
    private LocalDate endDate;
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "日期(明细/汇总中使用)")
    private LocalDate schedulingDate;
    private String schedulingUserName;
    private String customerName;
@@ -61,5 +75,30 @@
    private BigDecimal workHours;
    private BigDecimal wages;
    @ApiModelProperty(value = "报工动作 1-开始 2-结束")
    private Integer actionType;
    @ApiModelProperty(value = "实际报工时长(分钟)")
    @Excel(name = "实际报工时长(分钟)")
    private BigDecimal reportDurationMinutes;
    @ApiModelProperty(value = "项目总工时(小时)")
    private BigDecimal projectTotalHours;
    @ApiModelProperty(value = "工序标准工时(小时)")
    private BigDecimal processStandardHours;
    @ApiModelProperty(value = "实际报工工时(小时)")
    private BigDecimal actualReportHours;
    @ApiModelProperty(value = "每日人员工时(小时)")
    private BigDecimal dailyPersonHours;
    @ApiModelProperty(value = "产出总数量")
    private BigDecimal outputTotalQuantity;
    @ApiModelProperty(value = "报废总数量")
    private BigDecimal scrapTotalQuantity;
}
src/main/java/com/ruoyi/production/dto/ProductionReportDailySummaryDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.production.dto;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
@Data
@ApiModel(value = "ProductionReportDailySummaryDto", description = "每日报工时长汇总返回")
public class ProductionReportDailySummaryDto {
    @ApiModelProperty("日期")
    private LocalDate reportDate;
    @ApiModelProperty("时长(分钟)")
    private BigDecimal durationMinutes;
}
src/main/java/com/ruoyi/production/dto/ProductionReportStateDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.production.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel("ProductionReportStateDto")
public class ProductionReportStateDto {
    @ApiModelProperty("状态: 1-开始报工 2-结束报工")
    private Integer state;
    @ApiModelProperty("进行中的报工ID")
    private Long runningId;
    @ApiModelProperty("开始时间")
    private LocalDateTime startTime;
}
src/main/java/com/ruoyi/production/mapper/ProductOrderMapper.java
@@ -3,7 +3,6 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.production.dto.ProductBomDto;
import com.ruoyi.production.dto.ProductOrderDto;
import com.ruoyi.production.dto.ProductStructureDto;
import com.ruoyi.production.pojo.ProcessRoute;
@@ -11,12 +10,13 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
@Mapper
public interface ProductOrderMapper extends BaseMapper<ProductOrder> {
    IPage<ProductOrderDto> pageProductOrder(Page page, @Param("c") ProductOrderDto productOrder);
    IPage<ProductOrderDto> pageProductOrder(Page<ProductOrderDto> page, @Param("c") ProductOrderDto productOrder);
    List<ProcessRoute> listProcessRoute(@Param("productModelId") Long productModelId);
@@ -27,4 +27,10 @@
    Integer countCompleted(@Param("startDate") String startDate, @Param("endDate") String endDate);
    Integer countPending(@Param("startDate") String startDate, @Param("endDate") String endDate);
    /**
     * åŽŸå­å¢žåŠ å®Œæˆæ•°é‡ï¼Œé˜²æ­¢å¹¶å‘è¶…å‡ºè®¢å•æ•°é‡
     * @return å½±å“è¡Œæ•°(1 æˆåŠŸï¼Œ0 å¤±è´¥)
     */
    int addCompleteQtyIfNotExceed(@Param("id") Long id, @Param("delta") BigDecimal delta);
}
src/main/java/com/ruoyi/production/mapper/ProductWorkOrderMapper.java
@@ -1,6 +1,5 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -9,8 +8,8 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@Mapper
public interface ProductWorkOrderMapper extends BaseMapper<ProductWorkOrder> {
@@ -22,4 +21,10 @@
    List<ProductWorkOrderDto> selectWorkOrderStartStats(@Param("startDate") String startDate, @Param("endDate") String endDate);
    ProductWorkOrder selectMax(@Param("datePrefix") String datePrefix);
    /**
     * åŽŸå­å¢žåŠ å®Œæˆæ•°é‡ï¼Œé˜²æ­¢å¹¶å‘è¶…å‡ºè®¡åˆ’æ•°é‡
     * @return å½±å“è¡Œæ•°(1 æˆåŠŸï¼Œ0 å¤±è´¥)
     */
    int addCompleteQtyIfNotExceed(@Param("id") Long id, @Param("delta") BigDecimal delta);
}
src/main/java/com/ruoyi/production/mapper/ProductionProductMainMapper.java
@@ -16,7 +16,11 @@
@Mapper
public interface ProductionProductMainMapper extends BaseMapper<ProductionProductMain> {
    IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page page, @Param("c") ProductionProductMainDto productionProductMainDto);
    IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page<ProductionProductMainDto> page, @Param("c") ProductionProductMainDto productionProductMainDto);
    IPage<ProductionProductMainDto> listPageProductionProductMainDetailDto(Page<ProductionProductMainDto> page, @Param("c") ProductionProductMainDto productionProductMainDto);
    IPage<ProductionProductMainDto> listPageProductionProductMainDailyDto(Page<ProductionProductMainDto> page, @Param("c") ProductionProductMainDto productionProductMainDto);
    /**
     * æ ¹æ®å·¥å•ID批量删除生产主表数据
@@ -30,7 +34,7 @@
     */
    ProductOrder getOrderByMainId(@Param("productMainId") Long productMainId);
    IPage<ProductionProductMainDto> listProductionDetails(@Param("ew") SalesLedgerProductionAccountingDto salesLedgerProductionAccountingDto, Page page);
    IPage<ProductionProductMainDto> listProductionDetails(@Param("ew") SalesLedgerProductionAccountingDto salesLedgerProductionAccountingDto, Page<ProductionProductMainDto> page);
    ArrayList<Long> listMain(List<Long> idList);
}
src/main/java/com/ruoyi/production/mapper/ProductionProductReportDailyMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.production.dto.ProductionReportDailySummaryDto;
import com.ruoyi.production.pojo.ProductionProductReportDaily;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
@Mapper
public interface ProductionProductReportDailyMapper extends BaseMapper<ProductionProductReportDaily> {
    List<ProductionReportDailySummaryDto> listDailySummary(
            @Param("workOrderId") Long workOrderId,
            @Param("productProcessRouteItemId") Long productProcessRouteItemId,
            @Param("userId") Long userId,
            @Param("startDate") LocalDate startDate,
            @Param("endDate") LocalDate endDate
    );
}
src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
@@ -7,6 +7,7 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@@ -35,6 +36,15 @@
    @ApiModelProperty(value = "报工状态")
    private Integer status;
    @ApiModelProperty(value = "报工开始时间")
    private LocalDateTime reportStartTime;
    @ApiModelProperty(value = "报工结束时间")
    private LocalDateTime reportEndTime;
    @ApiModelProperty(value = "实际报工时长(分钟)")
    private BigDecimal reportDurationMinutes;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
src/main/java/com/ruoyi/production/pojo/ProductionProductReportDaily.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package com.ruoyi.production.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("production_product_report_daily")
public class ProductionProductReportDaily implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("报工主表ID")
    private Long productMainId;
    @ApiModelProperty("工单ID")
    private Long workOrderId;
    @ApiModelProperty("工艺路线项目ID")
    private Long productProcessRouteItemId;
    @ApiModelProperty("报工人ID")
    private Long userId;
    @ApiModelProperty("报工日期(按天)")
    private LocalDate reportDate;
    @ApiModelProperty("当日开始时间(片段)")
    private LocalDateTime startTime;
    @ApiModelProperty("当日结束时间(片段)")
    private LocalDateTime endTime;
    @ApiModelProperty("当日时长(分钟)")
    private BigDecimal durationMinutes;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/production/service/ProductionProductMainService.java
@@ -4,16 +4,39 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.production.dto.ProductionProductMainDto;
import com.ruoyi.production.dto.ProductionReportDailySummaryDto;
import com.ruoyi.production.dto.ProductionReportStateDto;
import com.ruoyi.production.pojo.ProductionProductMain;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public interface ProductionProductMainService extends IService<ProductionProductMain> {
    IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page page, ProductionProductMainDto productionProductMainDto);
    IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto);
    IPage<ProductionProductMainDto> listPageProductionProductMainDetailDto(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto);
    IPage<ProductionProductMainDto> listPageProductionProductMainDailyDto(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto);
    Boolean addProductMain(ProductionProductMainDto productionProductMainDto);
    /**
     * æŸ¥è¯¢å½“前登录人进行中的报工(同工单同工序)
     */
    ProductionProductMain getRunning(Long workOrderId, Long productProcessRouteItemId);
    /**
     * æ¯æ—¥æŠ¥å·¥æ—¶é•¿æ±‡æ€»(当前登录人)
     */
    List<ProductionReportDailySummaryDto> dailyDuration(Long workOrderId, Long productProcessRouteItemId, LocalDate startDate, LocalDate endDate);
    /**
     * æŸ¥è¯¢æŠ¥å·¥çŠ¶æ€(当前登录人)
     */
    ProductionReportStateDto reportState(Long workOrderId, Long productProcessRouteItemId);
    Boolean removeProductMain(Long id);
    ArrayList<Long> listMain(List<Long> idList);
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java
@@ -13,9 +13,13 @@
import com.ruoyi.common.utils.MatrixToImageWriter;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.production.dto.ProductWorkOrderDto;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.mapper.ProductionProductReportDailyMapper;
import com.ruoyi.production.mapper.ProductWorkOrderFileMapper;
import com.ruoyi.production.mapper.ProductWorkOrderMapper;
import com.ruoyi.production.mapper.ProductWorkOrderRapporteurMapper;
import com.ruoyi.production.pojo.ProductionProductMain;
import com.ruoyi.production.pojo.ProductionProductReportDaily;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.production.pojo.ProductWorkOrderFile;
import com.ruoyi.production.pojo.ProductWorkOrderRapporteur;
@@ -30,7 +34,9 @@
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -39,6 +45,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@Service
@Transactional(rollbackFor = Exception.class)
@@ -52,14 +59,21 @@
    private ProductWorkOrderRapporteurMapper productWorkOrderRapporteurMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private ProductionProductMainMapper productionProductMainMapper;
    @Autowired
    private ProductionProductReportDailyMapper productionProductReportDailyMapper;
    @Value("${file.temp-dir}")
    private String tempDir;
    @Override
    public IPage<ProductWorkOrderDto> listPage(Page<ProductWorkOrderDto> page, ProductWorkOrderDto productWorkOrder) {
        if (productWorkOrder != null && Integer.valueOf(2).equals(productWorkOrder.getType())) {
            productWorkOrder.setCurrentUserId(SecurityUtils.getUserId());
        boolean reportView = productWorkOrder != null && Integer.valueOf(2).equals(productWorkOrder.getType());
        Long currentUserId = null;
        if (reportView) {
            currentUserId = SecurityUtils.getUserId();
            productWorkOrder.setCurrentUserId(currentUserId);
        }
        IPage<ProductWorkOrderDto> pageData = productWorkOrdermapper.pageProductWorkOrder(page, productWorkOrder);
        List<ProductWorkOrderDto> records = pageData.getRecords();
@@ -92,6 +106,81 @@
            List<Long> userIds = rapporteurMap.get(item.getId());
            item.setReportWorkersId(userIds == null ? new Long[0] : userIds.toArray(new Long[0]));
        });
        // type=2 æ—¶ï¼šå›žå¡«â€œå½“前报工人今日状态”
        if (reportView && currentUserId != null) {
            // 1) è¿›è¡Œä¸­(status=0)的报工
            List<ProductionProductMain> runningList = productionProductMainMapper.selectList(
                    Wrappers.<ProductionProductMain>lambdaQuery()
                            .in(ProductionProductMain::getWorkOrderId, workOrderIds)
                            .eq(ProductionProductMain::getUserId, currentUserId)
                            .eq(ProductionProductMain::getStatus, 0)
            );
            final java.util.Set<Long> runningWorkOrders = CollectionUtils.isNotEmpty(runningList)
                    ? runningList.stream().map(ProductionProductMain::getWorkOrderId).filter(Objects::nonNull).collect(Collectors.toSet())
                    : java.util.Collections.emptySet();
            // 2) ä»Šæ—¥å·²ç»“束(今日有 daily æ˜Žç»†ï¼‰
            LocalDate today = LocalDate.now();
            List<ProductionProductReportDaily> todayDailyList = productionProductReportDailyMapper.selectList(
                    Wrappers.<ProductionProductReportDaily>lambdaQuery()
                            .in(ProductionProductReportDaily::getWorkOrderId, workOrderIds)
                            .eq(ProductionProductReportDaily::getUserId, currentUserId)
                            .eq(ProductionProductReportDaily::getReportDate, today)
            );
            final java.util.Set<Long> endedTodayWorkOrders = CollectionUtils.isNotEmpty(todayDailyList)
                    ? todayDailyList.stream().map(ProductionProductReportDaily::getWorkOrderId).filter(Objects::nonNull).collect(Collectors.toSet())
                    : java.util.Collections.emptySet();
            records.forEach(item -> {
                Long woId = item.getId();
                if (woId == null) {
                    item.setTodayReportState(1);
                    return;
                }
                if (runningWorkOrders.contains(woId)) {
                    item.setTodayReportState(2);
                    return;
                }
                if (endedTodayWorkOrders.contains(woId)) {
                    item.setTodayReportState(3);
                    return;
                }
                item.setTodayReportState(1);
            });
        }
        // å›žå¡«æŠ¥å·¥æ—¶é—´æ€»å’Œ(分钟): type=2 æŒ‰å½“前登录人汇总,其他按工单全员汇总
        QueryWrapper<ProductionProductReportDaily> totalDurationQw = new QueryWrapper<>();
        totalDurationQw.select("work_order_id as workOrderId", "sum(duration_minutes) as totalMinutes")
                .in("work_order_id", workOrderIds)
                .groupBy("work_order_id");
        if (reportView && currentUserId != null) {
            totalDurationQw.eq("user_id", currentUserId);
        }
        List<Map<String, Object>> durationRows = productionProductReportDailyMapper.selectMaps(totalDurationQw);
        Map<Long, BigDecimal> durationMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(durationRows)) {
            for (Map<String, Object> row : durationRows) {
                Object workOrderObj = row.get("workOrderId");
                if (workOrderObj == null) {
                    workOrderObj = row.get("work_order_id");
                }
                Object totalObj = row.get("totalMinutes");
                if (totalObj == null) {
                    totalObj = row.get("total_minutes");
                }
                if (workOrderObj == null || totalObj == null) {
                    continue;
                }
                Long woId = workOrderObj instanceof Number ? ((Number) workOrderObj).longValue() : Long.valueOf(workOrderObj.toString());
                BigDecimal totalMinutes = totalObj instanceof BigDecimal
                        ? (BigDecimal) totalObj
                        : new BigDecimal(totalObj.toString());
                durationMap.put(woId, totalMinutes);
            }
        }
        records.forEach(item -> item.setTotalReportDurationMinutes(durationMap.getOrDefault(item.getId(), BigDecimal.ZERO)));
        return pageData;
    }
@@ -118,9 +207,10 @@
            return rows;
        }
        List<Long> existUserIds = sysUserMapper.selectUserByIds(candidateUserIds).stream()
        List<Long> existUserIds = sysUserMapper.selectUsersByIds(candidateUserIds).stream()
                .map(SysUser::getUserId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (existUserIds.size() != candidateUserIds.size()) {
            List<Long> invalidUserIds = candidateUserIds.stream()
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -15,10 +15,15 @@
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.production.dto.ProductStructureDto;
import com.ruoyi.production.dto.ProductionProductMainDto;
import com.ruoyi.production.dto.ProductionReportDailySummaryDto;
import com.ruoyi.production.dto.ProductionReportStateDto;
import com.ruoyi.production.enums.ProductProcessEnum;
import com.ruoyi.production.mapper.ProductionProductReportDailyMapper;
import com.ruoyi.production.pojo.ProductionProductReportDaily;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductionProductMainService;
@@ -26,13 +31,14 @@
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.quality.mapper.*;
import com.ruoyi.quality.pojo.*;
import com.ruoyi.quality.service.IQualityInspectService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -46,8 +52,8 @@
@Transactional(rollbackFor = Exception.class)
public class ProductionProductMainServiceImpl extends ServiceImpl<ProductionProductMainMapper, ProductionProductMain> implements ProductionProductMainService {
    private IQualityInspectService qualityInspectService;
    private ProductionProductMainMapper productionProductMainMapper;
    private ProductionProductReportDailyMapper productionProductReportDailyMapper;
    private ProductWorkOrderMapper productWorkOrderMapper;
@@ -56,25 +62,16 @@
    private SysUserMapper userMapper;
    private ProductionProductOutputMapper productionProductOutputMapper;
    private ProductModelMapper productModelMapper;
    private ProductMapper productMapper;
    private ProductProcessMapper productProcessMapper;
    private QualityInspectMapper qualityInspectMapper;
    private QualityUnqualifiedMapper qualityUnqualifiedMapper;
    private ProductProcessMapper productProcessMapper;
    private ProductProcessRouteMapper productProcessRouteMapper;
    private ProductMapper productMapper;
    private QualityInspectParamMapper qualityInspectParamMapper;
    private QualityTestStandardParamMapper qualityTestStandardParamMapper;
    private QualityTestStandardMapper qualityTestStandardMapper;
    private QualityInspectParamMapper qualityInspectParamMapper;
    private ProductStructureMapper productStructureMapper;
    private ProductionProductInputMapper productionProductInputMapper;
@@ -86,27 +83,162 @@
    @Override
    public IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page page, ProductionProductMainDto productionProductMainDto) {
        return productionProductMainMapper.listPageProductionProductMainDto(page, productionProductMainDto);
    public IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        if (productionProductMainDto == null) {
            productionProductMainDto = new ProductionProductMainDto();
        }
//        productionProductMainDto.setUserId(SecurityUtils.getUserId());
        IPage<ProductionProductMainDto> result = productionProductMainMapper.listPageProductionProductMainDto(page, productionProductMainDto);
        fillHourDefaults(result.getRecords());
        return result;
    }
    @Override
    public IPage<ProductionProductMainDto> listPageProductionProductMainDetailDto(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        if (productionProductMainDto == null) {
            productionProductMainDto = new ProductionProductMainDto();
        }
        IPage<ProductionProductMainDto> result = productionProductMainMapper.listPageProductionProductMainDetailDto(page, productionProductMainDto);
        fillHourDefaults(result.getRecords());
        return result;
    }
    @Override
    public IPage<ProductionProductMainDto> listPageProductionProductMainDailyDto(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        if (productionProductMainDto == null) {
            productionProductMainDto = new ProductionProductMainDto();
        }
        IPage<ProductionProductMainDto> result = productionProductMainMapper.listPageProductionProductMainDailyDto(page, productionProductMainDto);
        fillHourDefaults(result.getRecords());
        return result;
    }
    private void fillHourDefaults(List<ProductionProductMainDto> records) {
        if (records == null || records.isEmpty()) {
            return;
        }
        records.forEach(item -> {
            if (item.getProjectTotalHours() == null) {
                item.setProjectTotalHours(BigDecimal.ZERO);
            }
            if (item.getProcessStandardHours() == null) {
                item.setProcessStandardHours(BigDecimal.ZERO);
            }
            if (item.getActualReportHours() == null) {
                item.setActualReportHours(BigDecimal.ZERO);
            }
            if (item.getDailyPersonHours() == null) {
                item.setDailyPersonHours(BigDecimal.ZERO);
            }
        });
    }
    @Override
    public Boolean addProductMain(ProductionProductMainDto dto) {
        SysUser user = userMapper.selectUserById(dto.getUserId());
        ProductionProductMain productionProductMain = new ProductionProductMain();
        //当前工艺路线对应的工序详情
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(dto.getProductProcessRouteItemId());
        if (productProcessRouteItem == null) {
            throw new RuntimeException("工艺路线项不存在");
        if (dto.getActionType() == null) {
            if (dto.getId() != null) {
                if (dto.getQuantity() == null || dto.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                    throw new ServiceException("结束报工失败: æœ¬æ¬¡ç”Ÿäº§æ•°é‡å¿…须大于0");
                }
                return finishReport(dto);
            }
            Long workOrderId = dto.getWorkOrderId();
            Long itemId = dto.getProductProcessRouteItemId();
            if (workOrderId == null || itemId == null) {
                throw new ServiceException("工单ID和工艺路线项目ID不能为空");
            }
            ProductionProductMain running = getRunning(workOrderId, itemId);
            if (running != null) {
                if (dto.getQuantity() == null || dto.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                    throw new ServiceException("结束报工失败: æœ¬æ¬¡ç”Ÿäº§æ•°é‡å¿…须大于0");
                }
                dto.setId(running.getId());
                return finishReport(dto);
            }
            return startReport(dto);
        }
        //当前具体工序
        ProductProcess productProcess = productProcessMapper.selectById(productProcessRouteItem.getProcessId());
        //工艺路线中当前工序对应的产出规格型号
        ProductModel productModel = productModelMapper.selectById(productProcessRouteItem.getProductModelId());
        //查询该生产订单对应的bom
        ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectById(productProcessRouteItem.getProductRouteId());
        /*新增报工主表*/
        //查询最大报工编号
        if (dto.getActionType() == 1) {
            return startReport(dto);
        }
        if (dto.getActionType() == 2) {
            return finishReport(dto);
        }
        throw new ServiceException("无效报工动作: " + dto.getActionType());
    }
    @Override
    public ProductionProductMain getRunning(Long workOrderId, Long productProcessRouteItemId) {
        if (workOrderId == null || productProcessRouteItemId == null) {
            throw new ServiceException("工单ID和工艺路线项目ID不能为空");
        }
        Long currentUserId = SecurityUtils.getUserId();
        return productionProductMainMapper.selectOne(
                Wrappers.<ProductionProductMain>lambdaQuery()
                        .eq(ProductionProductMain::getWorkOrderId, workOrderId)
                        .eq(ProductionProductMain::getProductProcessRouteItemId, productProcessRouteItemId)
                        .eq(ProductionProductMain::getUserId, currentUserId)
                        .eq(ProductionProductMain::getStatus, 0)
                        .orderByDesc(ProductionProductMain::getReportStartTime)
                        .last("limit 1")
        );
    }
    @Override
    public List<ProductionReportDailySummaryDto> dailyDuration(Long workOrderId, Long productProcessRouteItemId, LocalDate startDate, LocalDate endDate) {
        Long userId = SecurityUtils.getUserId();
        return productionProductReportDailyMapper.listDailySummary(
                workOrderId,
                productProcessRouteItemId,
                userId,
                startDate,
                endDate
        );
    }
    @Override
    public ProductionReportStateDto reportState(Long workOrderId, Long productProcessRouteItemId) {
        ProductionProductMain running = getRunning(workOrderId, productProcessRouteItemId);
        ProductionReportStateDto dto = new ProductionReportStateDto();
        if (running == null) {
            dto.setState(1);
            return dto;
        }
        dto.setState(2);
        dto.setRunningId(running.getId());
        dto.setStartTime(running.getReportStartTime());
        return dto;
    }
    private Boolean startReport(ProductionProductMainDto dto) {
        if (dto.getWorkOrderId() == null || dto.getProductProcessRouteItemId() == null) {
            throw new ServiceException("开始报工失败: å·¥å•ID和工艺路线项目ID不能为空");
        }
        if (dto.getUserId() == null) {
            dto.setUserId(SecurityUtils.getUserId());
        }
        if (dto.getUserId() == null) {
            throw new ServiceException("开始报工失败: æ— æ³•获取当前登录人");
        }
        QueryWrapper<ProductionProductMain> runningWrapper = new QueryWrapper<>();
        runningWrapper.eq("work_order_id", dto.getWorkOrderId())
                .eq("product_process_route_item_id", dto.getProductProcessRouteItemId())
                .eq("user_id", dto.getUserId())
                .eq("status", 0);
        Long runningCount = productionProductMainMapper.selectCount(runningWrapper);
        if (runningCount != null && runningCount > 0) {
            // å·²æœ‰è¿›è¡Œä¸­çš„æŠ¥å·¥æ—¶ï¼Œä¸å†æ–°å»ºï¼Œç»§ç»­æ²¿ç”¨åˆ°ç»“束报工
            return true;
        }
        SysUser user = userMapper.selectUserById(dto.getUserId());
        if (user == null) {
            throw new ServiceException("报工人不存在");
        }
        ProductionProductMain productionProductMain = new ProductionProductMain();
        String datePrefix = "BG" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
        QueryWrapper<ProductionProductMain> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("MAX(product_no) as maxNo")
@@ -134,110 +266,139 @@
        String productNo = String.format("%s%03d", datePrefix, sequenceNumber);
        productionProductMain.setProductNo(productNo);
        productionProductMain.setUserId(dto.getUserId());
        productionProductMain.setUserName(dto.getUserName());
        productionProductMain.setUserName(user.getNickName());
        productionProductMain.setProductProcessRouteItemId(dto.getProductProcessRouteItemId());
        productionProductMain.setWorkOrderId(dto.getWorkOrderId());
        productionProductMain.setStatus(0);
        productionProductMain.setReportStartTime(LocalDateTime.now());
        productionProductMainMapper.insert(productionProductMain);
        /*新增报工投入表*/
        List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(productProcessRoute.getBomId(), productProcess.getId());
        if (productStructureDtos.size() == 0) {
            //如果该工序没有产品结构的投入品,那这个投入品和产出品是同一个
            ProductStructureDto productStructureDto = new ProductStructureDto();
            productStructureDto.setProductModelId(productProcessRouteItem.getProductModelId());
            productStructureDto.setUnitQuantity(BigDecimal.ONE);
            productStructureDtos.add(productStructureDto);
        }
        for (ProductStructureDto productStructureDto : productStructureDtos) {
        return true;
    }
            ProductionProductInput productionProductInput = new ProductionProductInput();
            productionProductInput.setProductModelId(productStructureDto.getProductModelId());
            productionProductInput.setQuantity(productStructureDto.getUnitQuantity().multiply(dto.getQuantity()));
            productionProductInput.setProductMainId(productionProductMain.getId());
            productionProductInputMapper.insert(productionProductInput);
            stockUtils.substractStock(productStructureDto.getProductModelId(), productionProductInput.getQuantity(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId());
    private Boolean finishReport(ProductionProductMainDto dto) {
        if (dto.getId() == null) {
            throw new ServiceException("结束报工失败: æŠ¥å·¥ID不能为空");
        }
        if (dto.getQuantity() == null || dto.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("结束报工失败: æœ¬æ¬¡ç”Ÿäº§æ•°é‡å¿…须大于0");
        }
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(dto.getId());
        if (productionProductMain == null) {
            throw new ServiceException("结束报工失败: æŠ¥å·¥è®°å½•不存在");
        }
        if (productionProductMain.getStatus() != null && productionProductMain.getStatus() == 1) {
            throw new ServiceException("该报工已结束,请勿重复提交");
        }
        if (productionProductMain.getReportStartTime() == null) {
            throw new ServiceException("该报工缺少开始时间,无法结束");
        }
        LocalDateTime endTime = LocalDateTime.now();
        long durationSeconds = Duration.between(productionProductMain.getReportStartTime(), endTime).getSeconds();
        BigDecimal durationMinutes = secondsToMinutesExact(durationSeconds);
        int finishRows = productionProductMainMapper.update(
                null,
                Wrappers.<ProductionProductMain>lambdaUpdate()
                        .set(ProductionProductMain::getReportEndTime, endTime)
                        .set(ProductionProductMain::getReportDurationMinutes, durationMinutes)
                        .set(ProductionProductMain::getStatus, 1)
                        .eq(ProductionProductMain::getId, productionProductMain.getId())
                        .eq(ProductionProductMain::getStatus, 0)
        );
        if (finishRows <= 0) {
            throw new ServiceException("该报工已结束,请勿重复提交");
        }
        productionProductMain.setReportEndTime(endTime);
        productionProductMain.setReportDurationMinutes(durationMinutes);
        productionProductMain.setStatus(1);
        // å†™å…¥â€œæ¯æ—¥æ—¶é•¿æ˜Žç»†â€ï¼Œè·¨å¤©è‡ªåŠ¨æ‹†åˆ†
        saveDailyDurations(productionProductMain, productionProductMain.getReportStartTime(), endTime);
        dto.setWorkOrderId(productionProductMain.getWorkOrderId());
        dto.setProductProcessRouteItemId(productionProductMain.getProductProcessRouteItemId());
        if (dto.getUserId() == null) {
            dto.setUserId(productionProductMain.getUserId());
        }
        if (dto.getUserName() == null) {
            dto.setUserName(productionProductMain.getUserName());
        }
        if (dto.getScrapQty() == null) {
            dto.setScrapQty(BigDecimal.ZERO);
        }
        SysUser user = userMapper.selectUserById(dto.getUserId());
        if (user == null) {
            throw new ServiceException("报工人不存在");
        }
        //  ä½¿ç”¨å·¥å•关联的生产订单产品型号
        ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(dto.getWorkOrderId());
        if (productWorkOrder == null) {
            throw new ServiceException("结束报工失败: å·¥å•不存在");
        }
        ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
        if (productOrder == null) {
            throw new ServiceException("结束报工失败: å…³è”生产订单不存在");
        }
        Long outputProductModelId = productOrder.getProductModelId();
        if (outputProductModelId == null) {
            throw new ServiceException("结束报工失败: ç”Ÿäº§è®¢å•未配置产品型号");
        }
        /*新增报工投入表(无BOM场景: æŠ•å…¥=产出型号, æ•°é‡æŒ‰æœ¬æ¬¡æŠ¥å·¥æ•°é‡)*/
        ProductionProductInput productionProductInput = new ProductionProductInput();
        productionProductInput.setProductModelId(outputProductModelId);
        productionProductInput.setQuantity(dto.getQuantity());
        productionProductInput.setProductMainId(productionProductMain.getId());
        productionProductInputMapper.insert(productionProductInput);
        stockUtils.substractStock(outputProductModelId, dto.getQuantity(), StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId());
        /*新增报工产出表*/
        ProductionProductOutput productionProductOutput = new ProductionProductOutput();
        productionProductOutput.setProductMainId(productionProductMain.getId());
        productionProductOutput.setProductModelId(productProcessRouteItem.getProductModelId());
        productionProductOutput.setProductModelId(outputProductModelId);
        productionProductOutput.setQuantity(dto.getQuantity() != null ? dto.getQuantity() : BigDecimal.ZERO);
        productionProductOutput.setScrapQty(dto.getScrapQty() != null ? dto.getScrapQty() : BigDecimal.ZERO);
        productionProductOutputMapper.insert(productionProductOutput);
        //合格数量=报工数量-报废数量
        BigDecimal productQty = productionProductOutput.getQuantity().subtract(productionProductOutput.getScrapQty());
        // æ˜¯å¦éœ€è¦è´¨æ£€ï¼šæŒ‰ product_process.is_quality åˆ¤æ–­ï¼ˆ1-需要,0-不需要)
        boolean needQuality = isNeedQualityByWorkOrder(productWorkOrder);
        //只有合格数量>0才能增加相应数据
        if (productQty.compareTo(BigDecimal.ZERO) > 0) {
            /*新增质检*/
            List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
            if (productProcessRouteItem.getIsQuality()) {
                //对应的过程检或者出厂检
                int inspectType = 1;
                String process = productProcess.getName();//工序
                if (productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
                    //最后一道工序生成出厂检
                    inspectType = 2;
                    process = null;
                }
                Product product = productMapper.selectById(productModel.getProductId());
                QualityInspect qualityInspect = new QualityInspect();
                qualityInspect.setProductId(product.getId());
                qualityInspect.setProductName(product.getProductName());
                qualityInspect.setModel(productModel.getModel());
                qualityInspect.setUnit(productModel.getUnit());
                qualityInspect.setQuantity(productQty);
                qualityInspect.setProcess(process);
                qualityInspect.setInspectState(0);
                qualityInspect.setInspectType(inspectType);
                qualityInspect.setProductMainId(productionProductMain.getId());
                qualityInspect.setProductModelId(productModel.getId());
                qualityInspectMapper.insert(qualityInspect);
                List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
                if (qualityTestStandard.size() > 0) {
                    qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
                    qualityInspectMapper.updateById(qualityInspect);
                    qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                                    .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))//默认获取最新的
                            .forEach(qualityTestStandardParam -> {
                                QualityInspectParam param = new QualityInspectParam();
                                BeanUtils.copyProperties(qualityTestStandardParam, param);
                                param.setId(null);
                                param.setInspectId(qualityInspect.getId());
                                qualityInspectParamMapper.insert(param);
                            });
                }
            }else {
                //直接入库
                stockUtils.addStock(productProcessRouteItem.getProductModelId(), productQty, StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productionProductMain.getId());
            // éœ€è¦è´¨æ£€æ—¶æ‰æ–°å¢žè¿‡ç¨‹æ£€/出厂检
            if (needQuality) {
                createQualityInspect(productionProductMain.getId(), outputProductModelId, productQty, 1, "生产报工");
            }
            stockUtils.addStock(outputProductModelId, productQty, StockInQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_IN.getCode(), productionProductMain.getId());
            /*更新工单和生产订单*/
            ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(dto.getWorkOrderId());
            productWorkOrder.setCompleteQuantity(productWorkOrder.getCompleteQuantity().add(productQty));
            if (ObjectUtils.isNull(productWorkOrder.getActualStartTime())) {
                productWorkOrder.setActualStartTime(LocalDateTime.now());//实际开始时间
            int woRows = productWorkOrderMapper.addCompleteQtyIfNotExceed(dto.getWorkOrderId(), productQty);
            if (woRows <= 0) {
                ProductWorkOrder current = productWorkOrderMapper.selectById(dto.getWorkOrderId());
                throw new ServiceException("本次生产数量不能大于剩余数量,剩余数量: "
                        + (current == null ? "0" : current.getPlanQuantity().subtract(current.getCompleteQuantity())));
            }
            if (productWorkOrder.getCompleteQuantity().compareTo(productWorkOrder.getPlanQuantity()) == 0) {
                productWorkOrder.setActualEndTime(LocalDateTime.now());//实际结束时间
            // æ— å·¥è‰ºè·¯çº¿åœºæ™¯ï¼šæŠ¥å·¥å³è®¡å…¥ç”Ÿäº§è®¢å•完成数量
            int poRows = productOrderMapper.addCompleteQtyIfNotExceed(productOrder.getId(), productQty);
            if (poRows <= 0) {
                ProductOrder currentOrder = productOrderMapper.selectById(productOrder.getId());
                throw new ServiceException("本次生产数量不能大于订单剩余数量,剩余数量: "
                        + (currentOrder == null ? "0" : currentOrder.getQuantity().subtract(currentOrder.getCompleteQuantity())));
            }
            productWorkOrderMapper.updateById(productWorkOrder);
            //生产订单
            ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
            if (ObjectUtils.isNull(productOrder.getStartTime())) {
                productOrder.setStartTime(LocalDateTime.now());//开始时间
            ProductOrder latestOrder = productOrderMapper.selectById(productOrder.getId());
            if (needQuality
                    && latestOrder != null
                    && latestOrder.getCompleteQuantity() != null
                    && latestOrder.getQuantity() != null
                    && latestOrder.getCompleteQuantity().compareTo(latestOrder.getQuantity()) >= 0) {
                // è®¢å•完成时新增出厂检
                createQualityInspect(productionProductMain.getId(), outputProductModelId, productQty, 2, null);
            }
            if (productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
                //如果是最后一道工序报工之后生产订单完成数量+
                productOrder.setCompleteQuantity(productOrder.getCompleteQuantity().add(productQty));
                if (productOrder.getCompleteQuantity().compareTo(productOrder.getQuantity()) == 0) {
                    productOrder.setEndTime(LocalDateTime.now());//结束时间
                }
            }
            productOrderMapper.updateById(productOrder);
            /*添加生产核算        åŒºåˆ†å·¥åºæ˜¯è®¡ä»¶è¿˜æ˜¯è®¡æ—¶*/
            BigDecimal workHours = (productProcess.getType() == 1)
                    ? productProcess.getSalaryQuota().multiply(productQty)
                    : productProcess.getSalaryQuota();
            BigDecimal workHours = durationMinutes.divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
            SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder()
                    .productMainId(productionProductMain.getId())
@@ -245,7 +406,7 @@
                    .schedulingUserName(user.getNickName())
                    .finishedNum(productQty)
                    .workHours(workHours)
                    .process(productProcess.getName())
                    .process(resolveProcessTypeName(productWorkOrder))
                    .schedulingDate(LocalDate.now())
                    .tenantId(dto.getTenantId())
                    .build();
@@ -254,27 +415,159 @@
        //如果报废数量>0,需要进入报废的库存
        if (ObjectUtils.isNotEmpty(dto.getScrapQty())) {
            if (dto.getScrapQty().compareTo(BigDecimal.ZERO) > 0) {
                stockUtils.addUnStock(productModel.getId(), dto.getScrapQty(), StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productionProductMain.getId());
                stockUtils.addUnStock(outputProductModelId, dto.getScrapQty(), StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode(), productionProductMain.getId());
            }
        }
        return true;
    }
    /**
     * åˆ›å»ºè´¨æ£€åŠè´¨æ£€å‚æ•°
     */
    private void createQualityInspect(Long productMainId, Long productModelId, BigDecimal qty, Integer inspectType, String process) {
        ProductModel productModel = productModelMapper.selectById(productModelId);
        if (productModel == null) {
            return;
        }
        Product product = productMapper.selectById(productModel.getProductId());
        if (product == null) {
            return;
        }
        QualityInspect qualityInspect = new QualityInspect();
        qualityInspect.setProductId(product.getId());
        qualityInspect.setProductName(product.getProductName());
        qualityInspect.setModel(productModel.getModel());
        qualityInspect.setUnit(productModel.getUnit());
        qualityInspect.setQuantity(qty);
        qualityInspect.setProcess(process);
        qualityInspect.setInspectState(0);
        qualityInspect.setInspectType(inspectType);
        qualityInspect.setProductMainId(productMainId);
        qualityInspect.setProductModelId(productModelId);
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
        if (qualityTestStandard.isEmpty()) {
            return;
        }
        qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
        qualityInspectMapper.updateById(qualityInspect);
        qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                        .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
                .forEach(qualityTestStandardParam -> {
                    QualityInspectParam param = new QualityInspectParam();
                    BeanUtils.copyProperties(qualityTestStandardParam, param);
                    param.setId(null);
                    param.setInspectId(qualityInspect.getId());
                    qualityInspectParamMapper.insert(param);
                });
    }
    /**
     * æ˜¯å¦éœ€è¦è´¨æ£€ï¼šä¾æ®å·¥åºè¡¨ is_quality(1-需要,0-不需要)
     */
    private boolean isNeedQualityByWorkOrder(ProductWorkOrder workOrder) {
        if (workOrder == null || workOrder.getProductProcessRouteItemId() == null) {
            return true;
        }
        ProductProcessRouteItem routeItem = productProcessRouteItemMapper.selectById(workOrder.getProductProcessRouteItemId());
        if (routeItem == null || routeItem.getProcessId() == null) {
            return true;
        }
        ProductProcess process = productProcessMapper.selectById(routeItem.getProcessId());
        if (process == null || process.getIsQuality() == null) {
            return true;
        }
        return process.getIsQuality();
    }
    /**
     * èŽ·å–å·¥åºå¯¹åº”çš„â€œéƒ¨ä»¶ç±»åž‹â€æ–‡æ¡ˆ
     */
    private String resolveProcessTypeName(ProductWorkOrder workOrder) {
        if (workOrder == null || workOrder.getProductProcessRouteItemId() == null) {
            return "其他";
        }
        ProductProcessRouteItem routeItem = productProcessRouteItemMapper.selectById(workOrder.getProductProcessRouteItemId());
        if (routeItem == null || routeItem.getProcessId() == null) {
            return "其他";
        }
        ProductProcess process = productProcessMapper.selectById(routeItem.getProcessId());
        if (process == null || process.getType() == null) {
            return "其他";
        }
        for (ProductProcessEnum value : ProductProcessEnum.values()) {
            if (value.getCode().equals(process.getType())) {
                return value.getInfo();
            }
        }
        return "其他";
    }
    private void saveDailyDurations(ProductionProductMain main, LocalDateTime start, LocalDateTime end) {
        if (main == null || start == null || end == null) {
            return;
        }
        if (end.isBefore(start)) {
            return;
        }
        // é˜²æ­¢é‡å¤å†™ï¼ˆä¾‹å¦‚误重复结束时),先删再插
        productionProductReportDailyMapper.delete(
                Wrappers.<ProductionProductReportDaily>lambdaQuery()
                        .eq(ProductionProductReportDaily::getProductMainId, main.getId())
        );
        LocalDateTime cursor = start;
        while (cursor.isBefore(end)) {
            LocalDate date = cursor.toLocalDate();
            LocalDateTime nextDayStart = date.plusDays(1).atStartOfDay();
            LocalDateTime sliceEnd = end.isBefore(nextDayStart) ? end : nextDayStart;
            long seconds = Duration.between(cursor, sliceEnd).getSeconds();
            BigDecimal minutes = secondsToMinutesExact(seconds);
            if (minutes.compareTo(BigDecimal.ZERO) > 0) {
                ProductionProductReportDaily daily = new ProductionProductReportDaily();
                daily.setProductMainId(main.getId());
                daily.setWorkOrderId(main.getWorkOrderId());
                daily.setProductProcessRouteItemId(main.getProductProcessRouteItemId());
                daily.setUserId(main.getUserId());
                daily.setReportDate(date);
                daily.setStartTime(cursor);
                daily.setEndTime(sliceEnd);
                daily.setDurationMinutes(minutes);
                productionProductReportDailyMapper.insert(daily);
            }
            cursor = sliceEnd;
        }
    }
    /**
     * ç§’转分钟:包含分秒并向上取整到分钟
     */
    private BigDecimal secondsToMinutesExact(long seconds) {
        if (seconds <= 0L) {
            return BigDecimal.ZERO;
        }
        return BigDecimal.valueOf(seconds).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
    }
    @Override
    public Boolean removeProductMain(Long id) {
        //判断该条报工是否不合格处理,如果不合格处理了,则不允许删除
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(Wrappers.<QualityInspect>lambdaQuery().eq(QualityInspect::getProductMainId, id));
        if (qualityInspects.size() > 0){
        if (qualityInspects.size() > 0) {
            List<QualityUnqualified> qualityUnqualifieds = qualityUnqualifiedMapper.selectList(Wrappers.<QualityUnqualified>lambdaQuery()
                    .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList())));
            if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState()==1) {
            if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState() == 1) {
                throw new ServiceException("该条报工已经不合格处理了,不允许删除");
            }
        }
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(id);
        //该报工对应的工艺路线详情
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
        ProductionProductOutput productionProductOutput = productionProductOutputMapper.selectList(Wrappers.<ProductionProductOutput>lambdaQuery().eq(ProductionProductOutput::getProductMainId, productionProductMain.getId())).get(0);
        List<ProductionProductOutput> outputList = productionProductOutputMapper.selectList(
                Wrappers.<ProductionProductOutput>lambdaQuery().eq(ProductionProductOutput::getProductMainId, productionProductMain.getId())
        );
        ProductionProductOutput productionProductOutput = outputList.isEmpty() ? null : outputList.get(0);
        /*删除核算*/
        salesLedgerProductionAccountingMapper.delete(
                new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
@@ -282,35 +575,31 @@
        );
        /*更新工单和生产订单*/
        ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
        BigDecimal validQuantity = BigDecimal.ZERO;
        if (productWorkOrder != null && productionProductOutput != null) {
            BigDecimal outputQty = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
            BigDecimal scrapQty = productionProductOutput.getScrapQty() == null ? BigDecimal.ZERO : productionProductOutput.getScrapQty();
            BigDecimal completeQty = productWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productWorkOrder.getCompleteQuantity();
            BigDecimal validQuantity = outputQty.subtract(scrapQty);
            validQuantity = outputQty.subtract(scrapQty);
            productWorkOrder.setCompleteQuantity(completeQty.subtract(validQuantity));
            productWorkOrder.setActualEndTime(null);
            productWorkOrderMapper.updateById(productWorkOrder);
        } else {
        } else if (productWorkOrder == null) {
            throw new ServiceException("操作失败:工单信息或产出记录不存在");
        }
        //判断是否是最后一道工序
        List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
        if (productProcessRouteItem.getDragSort() != null && productProcessRouteItems != null && productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
        // æ— å·¥è‰ºè·¯çº¿åœºæ™¯ï¼šåˆ é™¤æŠ¥å·¥æ—¶åªè¦æœ‰æœ‰æ•ˆäº§å‡ºå°±æ‰£å‡ç”Ÿäº§è®¢å•完成数量
        if (productionProductOutput != null && validQuantity.compareTo(BigDecimal.ZERO) > 0) {
            ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
            if (productOrder != null) {
                BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
                BigDecimal totalQty = productionProductOutput.getQuantity() != null ? productionProductOutput.getQuantity() : BigDecimal.ZERO;
                BigDecimal scrapQty = productionProductOutput.getScrapQty() != null ? productionProductOutput.getScrapQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.subtract(scrapQty);
                BigDecimal newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty);
                productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
                productOrder.setEndTime(null);
                productOrderMapper.updateById(productOrder);
            } else {
            if (productOrder == null) {
                throw new ServiceException("关联的生产订单不存在");
            }
            BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
            BigDecimal newCompleteQty = orderCompleteQty.subtract(validQuantity);
            productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
            productOrder.setEndTime(null);
            productOrderMapper.updateById(productOrder);
        }
        //删除质检
        qualityInspectMapper.selectList(
@@ -321,7 +610,7 @@
                    new LambdaQueryWrapper<QualityInspectParam>()
                            .eq(QualityInspectParam::getInspectId, q.getId()));
            qualityInspectMapper.deleteById(q.getId());
                stockUtils.deleteStockInRecord(q.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
            stockUtils.deleteStockInRecord(q.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
        });
        // åˆ é™¤äº§å‡ºè®°å½•
src/main/resources/mapper/production/ProductOrderMapper.xml
@@ -110,4 +110,14 @@
        WHERE create_time &gt;= #{startDate} AND create_time &lt;= #{endDate}
          AND complete_quantity &lt; quantity
    </select>
    <update id="addCompleteQtyIfNotExceed">
        update product_order
        set
            complete_quantity = complete_quantity + #{delta},
            start_time = ifnull(start_time, now()),
            end_time = case when (complete_quantity + #{delta}) = quantity then now() else end_time end
        where id = #{id}
          and (complete_quantity + #{delta}) <![CDATA[ <= ]]> quantity
    </update>
</mapper>
src/main/resources/mapper/production/ProductWorkOrderMapper.xml
@@ -21,7 +21,15 @@
    <select id="pageProductWorkOrder" resultType="com.ruoyi.production.dto.ProductWorkOrderDto">
        SELECT
        pwo.*,
        pp.NAME as processName,
        CASE pp.type
            WHEN 1 THEN '加工'
            WHEN 2 THEN '刮板冷芯制作'
            WHEN 3 THEN '管路组对'
            WHEN 4 THEN '罐体连接及调试'
            WHEN 5 THEN '测试打压'
            WHEN 6 THEN '其他'
            ELSE pp.NAME
        END as processName,
        pm.model,
        pm.unit,
        p.product_name AS productName,
@@ -62,7 +70,15 @@
    <select id="getProductWorkOrderFlowCard" resultType="com.ruoyi.production.dto.ProductWorkOrderDto">
        SELECT
        pwo.*,
        pp.NAME as processName,
        CASE pp.type
            WHEN 1 THEN '加工'
            WHEN 2 THEN '刮板冷芯制作'
            WHEN 3 THEN '管路组对'
            WHEN 4 THEN '罐体连接及调试'
            WHEN 5 THEN '测试打压'
            WHEN 6 THEN '其他'
            ELSE pp.NAME
        END as processName,
        pm.model,
        pm.unit,
        p.product_name AS productName,
@@ -105,4 +121,14 @@
        limit 1
        ;
    </select>
    <update id="addCompleteQtyIfNotExceed">
        update product_work_order
        set
            complete_quantity = complete_quantity + #{delta},
            actual_start_time = ifnull(actual_start_time, now()),
            actual_end_time = case when (complete_quantity + #{delta}) = plan_quantity then now() else actual_end_time end
        where id = #{id}
          and (complete_quantity + #{delta}) <![CDATA[ <= ]]> plan_quantity
    </update>
</mapper>
src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -10,20 +10,55 @@
        <result property="tenantId" column="tenant_id"/>
        <result property="createTime" column="create_time"/>
        <result property="status" column="status"/>
        <result property="reportStartTime" column="report_start_time"/>
        <result property="reportEndTime" column="report_end_time"/>
        <result property="reportDurationMinutes" column="report_duration_minutes"/>
    </resultMap>
    <select id="listPageProductionProductMainDto" resultType="com.ruoyi.production.dto.ProductionProductMainDto">
        select ppm.*,
        pwo.work_order_no as workOrderNo,
        pwo.status as workOrderStatus,
        u.nick_name as nickName,
        p.product_name as productName,
        pp.name as process,
        pm.model as productModelName,
        ppo.quantity,
        ppo.scrap_qty,
        pm.unit,
        sl.sales_contract_no salesContractNo
        select
        min(ppm.id) as id,
        null as productNo,
        min(ppm.user_id) as userId,
        min(ppm.work_order_id) as workOrderId,
        min(ppm.status) as status,
        min(ppm.report_start_time) as reportStartTime,
        max(ppm.report_end_time) as reportEndTime,
        sum(ifnull(ppm.report_duration_minutes, 0)) as reportDurationMinutes,
        max(pwo.work_order_no) as workOrderNo,
        max(po.nps_no) as productOrderNpsNo,
        max(pwo.status) as workOrderStatus,
        max(u.nick_name) as nickName,
        max(p.product_name) as productName,
        max(CASE pp.type
            WHEN 1 THEN '加工'
            WHEN 2 THEN '刮板冷芯制作'
            WHEN 3 THEN '管路组对'
            WHEN 4 THEN '罐体连接及调试'
            WHEN 5 THEN '测试打压'
            WHEN 6 THEN '其他'
            ELSE pp.name
        END) as process,
        max(pm.model) as productModelName,
        sum(ifnull(ppo.quantity, 0)) as quantity,
        sum(ifnull(ppo.scrap_qty, 0)) as scrapQty,
        sum(ifnull(ppo.quantity, 0)) as outputTotalQuantity,
        sum(ifnull(ppo.scrap_qty, 0)) as scrapTotalQuantity,
        round(ifnull((
            select sum(ifnull(pri.planned_work_hours, 0))
            from product_process_route_item pri
            where pri.product_order_id = po.id
        ), 0), 2) as projectTotalHours,
        ifnull(max(pp.salary_quota), 0) as processStandardHours,
        round(sum(ifnull(ppm.report_duration_minutes, 0)) / 60, 2) as actualReportHours,
        round(ifnull((
            select sum(d.duration_minutes)
            from production_product_report_daily d
            where d.user_id = ppm.user_id
              and d.report_date = curdate()
        ), 0) / 60, 2) as dailyPersonHours,
        max(pm.unit) as unit,
        max(sl.sales_contract_no) as salesContractNo
        from
        production_product_main ppm
        left join product_work_order pwo on pwo.id = ppm.work_order_id
@@ -48,10 +83,155 @@
            <if test="c.status != null and c.status != ''">
                and ppm.status = #{c.status}
            </if>
            <if test="c.userId != null">
                and ppm.user_id = #{c.userId}
            </if>
        </where>
        order by ppm.id
        group by ppm.work_order_id, po.sales_ledger_id, po.sale_ledger_product_id, ppm.user_id
        order by max(ppm.id) desc
    </select>
    <select id="listPageProductionProductMainDailyDto" resultType="com.ruoyi.production.dto.ProductionProductMainDto">
        select
        min(ppm.id) as id,
        min(ppm.product_no) as productNo,
        d.user_id as userId,
        d.work_order_id as workOrderId,
        max(pwo.work_order_no) as workOrderNo,
        max(po.nps_no) as productOrderNpsNo,
        max(pwo.status) as workOrderStatus,
        max(u.nick_name) as nickName,
        max(p.product_name) as productName,
        max(CASE pp.type
            WHEN 1 THEN '加工'
            WHEN 2 THEN '刮板冷芯制作'
            WHEN 3 THEN '管路组对'
            WHEN 4 THEN '罐体连接及调试'
            WHEN 5 THEN '测试打压'
            WHEN 6 THEN '其他'
            ELSE pp.name
        END) as process,
        max(pm.model) as productModelName,
        max(pm.unit) as unit,
        max(sl.sales_contract_no) as salesContractNo,
        d.report_date as schedulingDate,
        sum(d.duration_minutes) as reportDurationMinutes,
        round(sum(d.duration_minutes) / 60, 2) as dailyPersonHours,
        round(sum(d.duration_minutes) / 60, 2) as actualReportHours,
        sum(ifnull(ppo.quantity, 0)) as quantity,
        sum(ifnull(ppo.scrap_qty, 0)) as scrapQty,
        sum(ifnull(ppo.quantity, 0)) as outputTotalQuantity,
        sum(ifnull(ppo.scrap_qty, 0)) as scrapTotalQuantity,
        round(ifnull((
            select sum(ifnull(pri.planned_work_hours, 0))
            from product_process_route_item pri
            where pri.product_order_id = po.id
        ), 0), 2) as projectTotalHours,
        ifnull(max(pp.salary_quota), 0) as processStandardHours
        from production_product_report_daily d
        left join production_product_main ppm on ppm.id = d.product_main_id
        left join product_work_order pwo on pwo.id = d.work_order_id
        left join product_process_route_item ppri on ppri.id = pwo.product_process_route_item_id
        left join product_process pp on pp.id = ppri.process_id
        left join product_order po on po.id = pwo.product_order_id
        left join production_product_output ppo
            on ppm.id = ppo.product_main_id
           and date(ppm.report_end_time) = d.report_date
        left join product_model pm on pm.id = ppo.product_model_id
        left join product p on p.id = pm.product_id
        left join sales_ledger sl on sl.id = po.sales_ledger_id
        left join sys_user u on u.user_id = d.user_id
        <where>
            <if test="c.workOrderId != null">
                and d.work_order_id = #{c.workOrderId}
            </if>
            <if test="c.nickName != null and c.nickName != ''">
                and u.nick_name like concat('%',#{c.nickName},'%')
            </if>
            <if test="c.workOrderNo != null and c.workOrderNo != ''">
                and pwo.work_order_no like concat('%',#{c.workOrderNo},'%')
            </if>
            <if test="c.workOrderStatus != null and c.workOrderStatus != ''">
                and pwo.status = #{c.workOrderStatus}
            </if>
            <if test="c.userId != null">
                and d.user_id = #{c.userId}
            </if>
            <if test="c.startDate != null">
                and d.report_date <![CDATA[ >= ]]> #{c.startDate}
            </if>
            <if test="c.endDate != null">
                and d.report_date <![CDATA[ <= ]]> #{c.endDate}
            </if>
        </where>
        group by d.work_order_id, d.user_id, d.report_date, po.sales_ledger_id, po.sale_ledger_product_id
        order by d.report_date desc, d.user_id
    </select>
    <select id="listPageProductionProductMainDetailDto" resultType="com.ruoyi.production.dto.ProductionProductMainDto">
        select
        ppm.*,
        pwo.work_order_no as workOrderNo,
        po.nps_no as productOrderNpsNo,
        pwo.status as workOrderStatus,
        u.nick_name as nickName,
        p.product_name as productName,
        CASE pp.type
            WHEN 1 THEN '加工'
            WHEN 2 THEN '刮板冷芯制作'
            WHEN 3 THEN '管路组对'
            WHEN 4 THEN '罐体连接及调试'
            WHEN 5 THEN '测试打压'
            WHEN 6 THEN '其他'
            ELSE pp.name
        END as process,
        pm.model as productModelName,
        ppo.quantity,
        ppo.scrap_qty as scrapQty,
        ppo.quantity as outputTotalQuantity,
        ppo.scrap_qty as scrapTotalQuantity,
        pm.unit,
        sl.sales_contract_no salesContractNo,
        round(ifnull((
            select sum(ifnull(pri.planned_work_hours, 0))
            from product_process_route_item pri
            where pri.product_order_id = po.id
        ), 0), 2) as projectTotalHours,
        ifnull(pp.salary_quota, 0) as processStandardHours,
        round(ifnull(ppm.report_duration_minutes, 0) / 60, 2) as actualReportHours,
        round(ifnull((
            select sum(d.duration_minutes)
            from production_product_report_daily d
            where d.user_id = ppm.user_id
              and d.report_date = curdate()
        ), 0) / 60, 2) as dailyPersonHours
        from production_product_main ppm
        left join product_work_order pwo on pwo.id = ppm.work_order_id
        left join product_process_route_item ppri on ppri.id = pwo.product_process_route_item_id
        left join product_process pp on pp.id = ppri.process_id
        left join product_order po on po.id = pwo.product_order_id
        left join production_product_output ppo on ppm.id = ppo.product_main_id
        left join product_model pm on pm.id = ppo.product_model_id
        left join product p on p.id = pm.product_id
        left join sales_ledger sl on sl.id = po.sales_ledger_id
        left join sys_user u on u.user_id = ppm.user_id
        <where>
            <if test="c.workOrderId != null">
                and ppm.work_order_id = #{c.workOrderId}
            </if>
            <if test="c.userId != null">
                and ppm.user_id = #{c.userId}
            </if>
            <if test="c.startDate != null">
                and date(ppm.report_end_time) <![CDATA[ >= ]]> #{c.startDate}
            </if>
            <if test="c.endDate != null">
                and date(ppm.report_end_time) <![CDATA[ <= ]]> #{c.endDate}
            </if>
        </where>
        order by ppm.id desc
    </select>
    <select id="getOrderByMainId" resultType="com.ruoyi.production.pojo.ProductOrder">
        select po.*
        from product_order po
src/main/resources/mapper/production/ProductionProductReportDailyMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
<?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.ruoyi.production.mapper.ProductionProductReportDailyMapper">
    <select id="listDailySummary" resultType="com.ruoyi.production.dto.ProductionReportDailySummaryDto">
        select
        report_date as reportDate,
        sum(duration_minutes) as durationMinutes
        from production_product_report_daily
        where 1=1
        <if test="workOrderId != null">
            and work_order_id = #{workOrderId}
        </if>
        <if test="productProcessRouteItemId != null">
            and product_process_route_item_id = #{productProcessRouteItemId}
        </if>
        <if test="userId != null">
            and user_id = #{userId}
        </if>
        <if test="startDate != null">
            and report_date <![CDATA[ >= ]]> #{startDate}
        </if>
        <if test="endDate != null">
            and report_date <![CDATA[ <= ]]> #{endDate}
        </if>
        group by report_date
        order by report_date
    </select>
</mapper>
src/main/resources/mapper/system/SysUserMapper.xml
@@ -153,6 +153,7 @@
    <select id="checkEmailUnique" parameterType="String" resultMap="SysUserResult">
        select user_id, email from sys_user where email = #{email} and del_flag = '0' limit 1
    </select>
    <select id="selectUserByIds" resultType="com.ruoyi.project.system.domain.SysUser">
        <include refid="selectUserVo"/>
        where u.user_id in <foreach collection="userIds" item="item" open="(" separator="," close=")">
@@ -160,6 +161,7 @@
        </foreach>
        and u.del_flag = '0'
    </select>
    <select id="selectRegistrantIds" resultType="com.ruoyi.project.system.domain.SysUser">
        SELECT user_id, nick_name FROM sys_user
        <where>