自动化立体仓库 - WMS系统
chen.llin
2025-12-24 46b422214d5d422be5dfa0df57560cda678058c9
月结功能-主体
17个文件已添加
2201 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/MonthlySettleController.java 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/MonthlySettle.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/MonthlySettleDetail.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/param/DateRangeParam.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/param/MonthlySettleQueryParam.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/result/MaterialInOutStatDTO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/result/MonthlySettleResultVO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/result/MonthlySettleStatisticsVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/result/PreviousSettleEndingQtyDTO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/MonthlySettleDetailMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/MonthlySettleMapper.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/MonthlySettleService.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/MonthlySettleServiceImpl.java 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/MonthlySettleDetailMapper.xml 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/MonthlySettleMapper.xml 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/monthlySettle/monthlySettle.js 630 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/monthlySettle/monthlySettle.html 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/MonthlySettleController.java
New file
@@ -0,0 +1,135 @@
package com.zy.asrs.controller;
import com.baomidou.mybatisplus.plugins.Page;
import com.core.annotations.ManagerAuth;
import com.core.common.R;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.param.DateRangeParam;
import com.zy.asrs.entity.param.MonthlySettleQueryParam;
import com.zy.asrs.service.MonthlySettleService;
import com.zy.common.web.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.text.SimpleDateFormat;
import java.util.Date;
@RestController
public class MonthlySettleController extends BaseController {
    @Autowired
    private MonthlySettleService monthlySettleService;
    /**
     * 获取月结信息
     */
    @RequestMapping(value = "/monthlySettle/{id}/auth")
    @ManagerAuth
    public R get(@PathVariable("id") Long id) {
        return R.ok(monthlySettleService.selectById(id));
    }
    /**
     * 分页查询月结列表
     */
    @RequestMapping(value = "/monthlySettle/list/auth")
    @ManagerAuth
    public R list(MonthlySettleQueryParam param) {
        Page<MonthlySettle> page = new Page<>(param.getCurr(), param.getLimit());
        java.util.Map<String, Object> condition = new java.util.HashMap<>();
        if (param.getSettleNo() != null && !param.getSettleNo().trim().isEmpty()) {
            condition.put("settleNo", param.getSettleNo());
        }
        if (param.getStatus() != null) {
            condition.put("status", param.getStatus());
        }
        if (param.getStartDate() != null && !param.getStartDate().trim().isEmpty()
                && param.getEndDate() != null && !param.getEndDate().trim().isEmpty()) {
            condition.put("startDate", param.getStartDate());
            condition.put("endDate", param.getEndDate());
        }
        page.setCondition(condition);
        return R.ok(monthlySettleService.getPage(page));
    }
    /**
     * 获取最近的月结记录
     */
    @RequestMapping(value = "/monthlySettle/latest/auth")
    @ManagerAuth
    public R getLatest() {
        MonthlySettle latest = monthlySettleService.getLatestSettle();
        return R.ok(latest);
    }
    /**
     * 获取下一个月结的起始日期
     */
    @RequestMapping(value = "/monthlySettle/nextStartDate/auth")
    @ManagerAuth
    public R getNextStartDate() {
        Date nextStartDate = monthlySettleService.getNextStartDate();
        if (nextStartDate != null) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            return R.ok(sdf.format(nextStartDate));
        }
        return R.ok(null);
    }
    /**
     * 获取最晚月结记录的结束日期
     */
    @RequestMapping(value = "/monthlySettle/latestEndDate/auth")
    @ManagerAuth
    public R getLatestEndDate() {
        Date latestEndDate = monthlySettleService.getLatestEndDate();
        if (latestEndDate != null) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            return R.ok(sdf.format(latestEndDate));
        }
        return R.ok(null);
    }
    /**
     * 检查月结时间范围内是否有未完成的订单
     */
    @RequestMapping(value = "/monthlySettle/checkUnfinished/auth")
    @ManagerAuth
    public R checkUnfinished(DateRangeParam param) {
        boolean hasUnfinished = monthlySettleService.hasUnfinishedOrders(param.getStartDate(), param.getEndDate());
        if (hasUnfinished) {
            return R.error("月结时间范围内存在未完成的订单,无法进行月结");
        }
        return R.ok();
    }
    /**
     * 发起月结
     */
    @RequestMapping(value = "/monthlySettle/start/auth")
    @ManagerAuth
    public R startSettle(DateRangeParam param) {
        return R.ok(monthlySettleService.startSettle(param.getStartDate(), param.getEndDate(), getUserId()));
    }
    /**
     * 获取月结统计信息
     */
    @RequestMapping(value = "/monthlySettle/statistics/{id}/auth")
    @ManagerAuth
    public R getStatistics(@PathVariable("id") Long id) {
        return R.ok(monthlySettleService.getSettleStatistics(id));
    }
    /**
     * 删除月结记录
     */
    @RequestMapping(value = "/monthlySettle/{id}/auth", method = RequestMethod.DELETE)
    @ManagerAuth
    public R delete(@PathVariable("id") Long id) {
        monthlySettleService.deleteSettle(id);
        return R.ok("删除成功");
    }
}
src/main/java/com/zy/asrs/entity/MonthlySettle.java
New file
@@ -0,0 +1,188 @@
package com.zy.asrs.entity;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.core.common.Cools;
import com.core.common.SpringUtils;
import com.zy.system.entity.User;
import com.zy.system.service.UserService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
@Data
@TableName("man_monthly_settle")
public class MonthlySettle implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    @ApiModelProperty(value = "ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 月结编号
     */
    @ApiModelProperty(value = "月结编号")
    @TableField("settle_no")
    private String settleNo;
    /**
     * 起始日期
     */
    @ApiModelProperty(value = "起始日期")
    @TableField("start_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date startDate;
    /**
     * 结束日期
     */
    @ApiModelProperty(value = "结束日期")
    @TableField("end_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date endDate;
    /**
     * 状态 0:待月结 1:已月结
     */
    @ApiModelProperty(value = "状态 0:待月结 1:已月结")
    private Integer status;
    /**
     * 总入库数量
     */
    @ApiModelProperty(value = "总入库数量")
    @TableField("total_in_qty")
    private BigDecimal totalInQty;
    /**
     * 总出库数量
     */
    @ApiModelProperty(value = "总出库数量")
    @TableField("total_out_qty")
    private BigDecimal totalOutQty;
    /**
     * 物料种类数
     */
    @ApiModelProperty(value = "物料种类数")
    @TableField("total_materials")
    private Integer totalMaterials;
    /**
     * 备注
     */
    @ApiModelProperty(value = "备注")
    private String memo;
    /**
     * 创建人员
     */
    @ApiModelProperty(value = "创建人员")
    @TableField("create_by")
    private Long createBy;
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField("create_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /**
     * 修改人员
     */
    @ApiModelProperty(value = "修改人员")
    @TableField("update_by")
    private Long updateBy;
    /**
     * 修改时间
     */
    @ApiModelProperty(value = "修改时间")
    @TableField("update_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    /**
     * 删除标记 0:未删除 1:已删除
     */
    @ApiModelProperty(value = "删除标记 0:未删除 1:已删除")
    @TableField("is_deleted")
    private Integer isDeleted;
    public String getStatus$() {
        if (null == this.status) {
            return null;
        }
        switch (this.status) {
            case 0:
                return "待月结";
            case 1:
                return "已月结";
            default:
                return String.valueOf(this.status);
        }
    }
    public String getStartDate$() {
        if (Cools.isEmpty(this.startDate)) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd").format(this.startDate);
    }
    public String getEndDate$() {
        if (Cools.isEmpty(this.endDate)) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd").format(this.endDate);
    }
    public String getCreateBy$() {
        UserService service = SpringUtils.getBean(UserService.class);
        User user = service.selectById(this.createBy);
        if (!Cools.isEmpty(user)) {
            return String.valueOf(user.getUsername());
        }
        return null;
    }
    public String getCreateTime$() {
        if (Cools.isEmpty(this.createTime)) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
    }
    public String getUpdateBy$() {
        UserService service = SpringUtils.getBean(UserService.class);
        User user = service.selectById(this.updateBy);
        if (!Cools.isEmpty(user)) {
            return String.valueOf(user.getUsername());
        }
        return null;
    }
    public String getUpdateTime$() {
        if (Cools.isEmpty(this.updateTime)) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
    }
}
src/main/java/com/zy/asrs/entity/MonthlySettleDetail.java
New file
@@ -0,0 +1,157 @@
package com.zy.asrs.entity;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("man_monthly_settle_detail")
public class MonthlySettleDetail implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    @ApiModelProperty(value = "ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 月结主表ID
     */
    @ApiModelProperty(value = "月结主表ID")
    @TableField("settle_id")
    private Long settleId;
    /**
     * 月结编号
     */
    @ApiModelProperty(value = "月结编号")
    @TableField("settle_no")
    private String settleNo;
    // ========== 基本信息 ==========
    /**
     * 物料编码
     */
    @ApiModelProperty(value = "物料编码")
    private String matnr;
    /**
     * 批次(订单明细批次)
     */
    @ApiModelProperty(value = "批次")
    private String batch;
    /**
     * 物料名称(月结时从订单明细表获取)
     */
    @ApiModelProperty(value = "物料名称")
    private String maktx;
    /**
     * 品牌(月结时从订单明细表获取)
     */
    @ApiModelProperty(value = "品牌")
    private String brand;
    // ========== 数量信息 ==========
    /**
     * 期初库存(上期结余)
     */
    @ApiModelProperty(value = "期初库存(上期结余)")
    @TableField("beginning_qty")
    private BigDecimal beginningQty;
    /**
     * 本期入库数量
     */
    @ApiModelProperty(value = "本期入库数量")
    @TableField("in_qty")
    private BigDecimal inQty;
    /**
     * 本期出库数量
     */
    @ApiModelProperty(value = "本期出库数量")
    @TableField("out_qty")
    private BigDecimal outQty;
    /**
     * 期末库存(期初+入库-出库)
     */
    @ApiModelProperty(value = "期末库存(期初+入库-出库)")
    @TableField("ending_qty")
    private BigDecimal endingQty;
    /**
     * 当前实际库存数量
     */
    @ApiModelProperty(value = "当前实际库存数量")
    @TableField("stock_qty")
    private BigDecimal stockQty;
    /**
     * 差异数量(实际库存-期末库存)
     */
    @ApiModelProperty(value = "差异数量(实际库存-期末库存)")
    @TableField("diff_qty")
    private BigDecimal diffQty;
    // ========== 关联字段(通过关联查询获取,不存储在明细表中)==========
    /**
     * 规格(优先从出入库订单明细表获取,如果为空则从物料表获取)
     */
    @ApiModelProperty(value = "规格")
    @TableField(exist = false)
    private String specs;
    /**
     * 型号(优先从出入库订单明细表获取,如果为空则从物料表获取)
     */
    @ApiModelProperty(value = "型号")
    @TableField(exist = false)
    private String model;
    /**
     * 颜色(优先从出入库订单明细表获取,如果为空则从物料表获取)
     */
    @ApiModelProperty(value = "颜色")
    @TableField(exist = false)
    private String color;
    /**
     * 单位(从物料表获取)
     */
    @ApiModelProperty(value = "单位")
    @TableField(exist = false)
    private String unit;
    // ========== 时间信息 ==========
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField("create_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /**
     * 删除标记 0:未删除 1:已删除
     */
    @ApiModelProperty(value = "删除标记 0:未删除 1:已删除")
    @TableField("is_deleted")
    private Integer isDeleted;
}
src/main/java/com/zy/asrs/entity/param/DateRangeParam.java
New file
@@ -0,0 +1,29 @@
package com.zy.asrs.entity.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
 * 日期范围参数
 */
@Data
public class DateRangeParam {
    /**
     * 起始日期
     */
    @ApiModelProperty(value = "起始日期", required = true)
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    /**
     * 结束日期(可以接受 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss 格式)
     */
    @ApiModelProperty(value = "结束日期", required = true)
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
}
src/main/java/com/zy/asrs/entity/param/MonthlySettleQueryParam.java
New file
@@ -0,0 +1,48 @@
package com.zy.asrs.entity.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * 月结查询参数
 */
@Data
public class MonthlySettleQueryParam {
    /**
     * 当前页
     */
    @ApiModelProperty(value = "当前页")
    private Integer curr = 1;
    /**
     * 每页数量
     */
    @ApiModelProperty(value = "每页数量")
    private Integer limit = 10;
    /**
     * 月结编号
     */
    @ApiModelProperty(value = "月结编号")
    private String settleNo;
    /**
     * 状态
     */
    @ApiModelProperty(value = "状态")
    private Integer status;
    /**
     * 起始日期
     */
    @ApiModelProperty(value = "起始日期")
    private String startDate;
    /**
     * 结束日期
     */
    @ApiModelProperty(value = "结束日期")
    private String endDate;
}
src/main/java/com/zy/asrs/entity/result/MaterialInOutStatDTO.java
New file
@@ -0,0 +1,43 @@
package com.zy.asrs.entity.result;
import lombok.Data;
import java.math.BigDecimal;
/**
 * 物料出入库统计
 */
@Data
public class MaterialInOutStatDTO {
    /**
     * 物料编码
     */
    private String matnr;
    /**
     * 物料名称
     */
    private String maktx;
    /**
     * 批次
     */
    private String batch;
    /**
     * 品牌
     */
    private String brand;
    /**
     * 入库数量
     */
    private BigDecimal inQty;
    /**
     * 出库数量
     */
    private BigDecimal outQty;
}
src/main/java/com/zy/asrs/entity/result/MonthlySettleResultVO.java
New file
@@ -0,0 +1,44 @@
package com.zy.asrs.entity.result;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * 月结结果
 */
@Data
public class MonthlySettleResultVO {
    /**
     * 月结ID
     */
    @ApiModelProperty(value = "月结ID")
    private Long settleId;
    /**
     * 月结编号
     */
    @ApiModelProperty(value = "月结编号")
    private String settleNo;
    /**
     * 总入库数量
     */
    @ApiModelProperty(value = "总入库数量")
    private BigDecimal totalInQty;
    /**
     * 总出库数量
     */
    @ApiModelProperty(value = "总出库数量")
    private BigDecimal totalOutQty;
    /**
     * 物料种类数
     */
    @ApiModelProperty(value = "物料种类数")
    private Integer totalMaterials;
}
src/main/java/com/zy/asrs/entity/result/MonthlySettleStatisticsVO.java
New file
@@ -0,0 +1,28 @@
package com.zy.asrs.entity.result;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.MonthlySettleDetail;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * 月结统计信息
 */
@Data
public class MonthlySettleStatisticsVO {
    /**
     * 月结主记录
     */
    @ApiModelProperty(value = "月结主记录")
    private MonthlySettle settle;
    /**
     * 月结明细列表
     */
    @ApiModelProperty(value = "月结明细列表")
    private List<MonthlySettleDetail> details;
}
src/main/java/com/zy/asrs/entity/result/PreviousSettleEndingQtyDTO.java
New file
@@ -0,0 +1,33 @@
package com.zy.asrs.entity.result;
import lombok.Data;
import java.math.BigDecimal;
/**
 * 上期月结期末库存
 */
@Data
public class PreviousSettleEndingQtyDTO {
    /**
     * 物料编码
     */
    private String matnr;
    /**
     * 批次
     */
    private String batch;
    /**
     * 品牌
     */
    private String brand;
    /**
     * 期末库存
     */
    private BigDecimal endingQty;
}
src/main/java/com/zy/asrs/mapper/MonthlySettleDetailMapper.java
New file
@@ -0,0 +1,20 @@
package com.zy.asrs.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.zy.asrs.entity.MonthlySettleDetail;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface MonthlySettleDetailMapper extends BaseMapper<MonthlySettleDetail> {
    /**
     * 查询月结明细(关联物料表)
     */
    List<MonthlySettleDetail> selectDetailWithMat(@Param("settleId") Long settleId);
}
src/main/java/com/zy/asrs/mapper/MonthlySettleMapper.java
New file
@@ -0,0 +1,64 @@
package com.zy.asrs.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.result.MaterialInOutStatDTO;
import com.zy.asrs.entity.result.PreviousSettleEndingQtyDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface MonthlySettleMapper extends BaseMapper<MonthlySettle> {
    /**
     * 获取最近的月结记录
     */
    MonthlySettle selectLatestSettle();
    /**
     * 统计月结时间范围内的订单数量
     */
    int countOrdersInRange(@Param("startDate") String startDate, @Param("endDate") String endDate);
    /**
     * 统计月结时间范围内未完成的订单数量(入库和出库)
     */
    int countUnfinishedOrdersInRange(@Param("startDate") String startDate, @Param("endDate") String endDate);
    /**
     * 统计月结时间范围内的物料出入库数量(合并入库和出库)
     */
    List<MaterialInOutStatDTO> statisticsMaterialInOut(@Param("startDate") String startDate, @Param("endDate") String endDate);
    /**
     * 获取上一个月结的物料期末库存
     */
    List<PreviousSettleEndingQtyDTO> getPreviousSettleEndingQty(@Param("previousSettleId") Long previousSettleId);
    /**
     * 更新入库订单的月结信息
     */
    int updateOrderSettleInfo(@Param("settleId") Long settleId, @Param("settleNo") String settleNo,
                              @Param("startDate") String startDate, @Param("endDate") String endDate);
    /**
     * 更新出库订单的月结信息
     */
    int updateOrderSettleInfoPakout(@Param("settleId") Long settleId, @Param("settleNo") String settleNo,
                                    @Param("startDate") String startDate, @Param("endDate") String endDate);
    /**
     * 清除入库订单的月结信息
     */
    int clearOrderSettleInfo(@Param("settleId") Long settleId);
    /**
     * 清除出库订单的月结信息
     */
    int clearOrderSettleInfoPakout(@Param("settleId") Long settleId);
}
src/main/java/com/zy/asrs/service/MonthlySettleService.java
New file
@@ -0,0 +1,54 @@
package com.zy.asrs.service;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.IService;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.result.MonthlySettleResultVO;
import com.zy.asrs.entity.result.MonthlySettleStatisticsVO;
import java.util.Date;
public interface MonthlySettleService extends IService<MonthlySettle> {
    /**
     * 获取最近的月结记录
     */
    MonthlySettle getLatestSettle();
    /**
     * 获取下一个月结的起始日期
     */
    Date getNextStartDate();
    /**
     * 获取最晚月结记录的结束日期
     */
    Date getLatestEndDate();
    /**
     * 检查月结时间范围内是否有未完成的订单
     */
    boolean hasUnfinishedOrders(Date startDate, Date endDate);
    /**
     * 发起月结
     */
    MonthlySettleResultVO startSettle(Date startDate, Date endDate, Long userId);
    /**
     * 获取月结统计信息
     */
    MonthlySettleStatisticsVO getSettleStatistics(Long settleId);
    /**
     * 分页查询月结列表
     */
    Page<MonthlySettle> getPage(Page<MonthlySettle> page);
    /**
     * 删除月结记录
     */
    void deleteSettle(Long settleId);
}
src/main/java/com/zy/asrs/service/impl/MonthlySettleServiceImpl.java
New file
@@ -0,0 +1,385 @@
package com.zy.asrs.service.impl;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.core.common.SnowflakeIdWorker;
import com.core.exception.CoolException;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.MonthlySettleDetail;
import com.zy.asrs.entity.result.MaterialInOutStatDTO;
import com.zy.asrs.entity.result.MonthlySettleResultVO;
import com.zy.asrs.entity.result.MonthlySettleStatisticsVO;
import com.zy.asrs.entity.result.PreviousSettleEndingQtyDTO;
import com.zy.asrs.mapper.MonthlySettleDetailMapper;
import com.zy.asrs.mapper.MonthlySettleMapper;
import com.zy.asrs.service.ManLocDetlService;
import com.zy.asrs.service.MonthlySettleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service("monthlySettleService")
public class MonthlySettleServiceImpl extends ServiceImpl<MonthlySettleMapper, MonthlySettle> implements MonthlySettleService {
    @Autowired
    private MonthlySettleDetailMapper monthlySettleDetailMapper;
    @Autowired
    private ManLocDetlService manLocDetlService;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Override
    public MonthlySettle getLatestSettle() {
        return this.baseMapper.selectLatestSettle();
    }
    @Override
    public Date getNextStartDate() {
        MonthlySettle latestSettle = getLatestSettle();
        if (latestSettle == null) {
            // 如果没有月结记录,返回null,由前端选择起始日期
            return null;
        }
        // 返回最晚月结记录结束日期的下一天(起始日期应该是结束日期+1天的00:00:00)
        Calendar cal = Calendar.getInstance();
        cal.setTime(latestSettle.getEndDate());
        // 先设置为当天的00:00:00,然后加1天,确保返回的是下一天的00:00:00
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DAY_OF_MONTH, 1);
        return cal.getTime();
    }
    @Override
    public Date getLatestEndDate() {
        MonthlySettle latestSettle = getLatestSettle();
        if (latestSettle == null) {
            return null;
        }
        // 返回最晚月结记录的结束日期(格式化为当天的00:00:00,用于显示)
        Calendar cal = Calendar.getInstance();
        cal.setTime(latestSettle.getEndDate());
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }
    @Override
    public boolean hasUnfinishedOrders(Date startDate, Date endDate) {
        // 结束日期+23:59:59
        Calendar cal = Calendar.getInstance();
        cal.setTime(endDate);
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MILLISECOND, 999);
        endDate = cal.getTime();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String startDateStr = dateFormat.format(startDate);
        String endDateStr = dateTimeFormat.format(endDate);
        int count = this.baseMapper.countUnfinishedOrdersInRange(startDateStr, endDateStr);
        return count > 0;
    }
    @Override
    @Transactional
    public MonthlySettleResultVO startSettle(Date startDate, Date endDate, Long userId) {
        // 结束日期+23:59:59
        Calendar cal = Calendar.getInstance();
        cal.setTime(endDate);
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MILLISECOND, 999);
        endDate = cal.getTime();
        // 检查起始日期必须大于最晚月结记录的结束日期
        MonthlySettle latestSettle = getLatestSettle();
        if (latestSettle != null) {
            Calendar startCal = Calendar.getInstance();
            startCal.setTime(startDate);
            startCal.set(Calendar.HOUR_OF_DAY, 0);
            startCal.set(Calendar.MINUTE, 0);
            startCal.set(Calendar.SECOND, 0);
            startCal.set(Calendar.MILLISECOND, 0);
            Calendar latestEndCal = Calendar.getInstance();
            latestEndCal.setTime(latestSettle.getEndDate());
            latestEndCal.set(Calendar.HOUR_OF_DAY, 0);
            latestEndCal.set(Calendar.MINUTE, 0);
            latestEndCal.set(Calendar.SECOND, 0);
            latestEndCal.set(Calendar.MILLISECOND, 0);
            // 起始日期必须大于最晚结束日期,不能等于或小于
            if (!startCal.getTime().after(latestEndCal.getTime())) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                throw new CoolException("起始日期必须大于最晚月结记录的结束日期:" + sdf.format(latestSettle.getEndDate()));
            }
        }
        // 检查是否有未完成的订单
        if (hasUnfinishedOrders(startDate, endDate)) {
            throw new CoolException("月结时间范围内存在未完成的订单,无法进行月结");
        }
        // 统计物料出入库数量(合并入库和出库)
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String startDateStr = dateFormat.format(startDate);
        String endDateStr = dateTimeFormat.format(endDate);
        List<MaterialInOutStatDTO> materialStats = this.baseMapper.statisticsMaterialInOut(startDateStr, endDateStr);
        // 检查是否有出入库历史订单
        if (materialStats == null || materialStats.isEmpty()) {
            throw new CoolException("月结时间范围内没有出入库历史订单,无法进行月结");
        }
        // 获取上一个月结记录(用于计算期初库存)
        MonthlySettle previousSettle = getLatestSettle();
        Map<String, BigDecimal> previousEndingQtyMap = new HashMap<>();
        if (previousSettle != null) {
            List<PreviousSettleEndingQtyDTO> previousDetails = this.baseMapper.getPreviousSettleEndingQty(previousSettle.getId());
            for (PreviousSettleEndingQtyDTO detail : previousDetails) {
                String key = detail.getMatnr() + "_" +
                            (detail.getBatch() != null ? detail.getBatch() : "") + "_" +
                            (detail.getBrand() != null ? detail.getBrand() : "");
                BigDecimal endingQty = detail.getEndingQty() != null ? detail.getEndingQty() : BigDecimal.ZERO;
                previousEndingQtyMap.put(key, endingQty);
            }
        }
        // 生成月结编号
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String settleNo = "MS" + sdf.format(new Date()) + String.format("%04d", snowflakeIdWorker.nextId() % 10000);
        // 创建月结主记录
        MonthlySettle monthlySettle = new MonthlySettle();
        monthlySettle.setSettleNo(settleNo);
        monthlySettle.setStartDate(startDate);
        monthlySettle.setEndDate(endDate);
        monthlySettle.setStatus(1); // 已月结
        monthlySettle.setIsDeleted(0); // 未删除
        monthlySettle.setCreateBy(userId);
        monthlySettle.setCreateTime(new Date());
        this.insert(monthlySettle);
        BigDecimal totalInQty = BigDecimal.ZERO;
        BigDecimal totalOutQty = BigDecimal.ZERO;
        int materialCount = 0;
        // 创建月结明细
        for (MaterialInOutStatDTO stat : materialStats) {
            // 1. 提取基础信息
            String matnr = stat.getMatnr();
            String batch = stat.getBatch() != null ? stat.getBatch() : "";
            String brand = stat.getBrand() != null ? stat.getBrand() : "";
            String maktx = stat.getMaktx();
            // 2. 提取数量信息
            BigDecimal inQty = stat.getInQty() != null ? stat.getInQty() : BigDecimal.ZERO;
            BigDecimal outQty = stat.getOutQty() != null ? stat.getOutQty() : BigDecimal.ZERO;
            // 3. 计算库存相关数量
            BigDecimal beginningQty = getBeginningQty(matnr, batch, brand, previousEndingQtyMap);
            BigDecimal endingQty = calculateEndingQty(beginningQty, inQty, outQty);
            BigDecimal stockQtyDecimal = getCurrentStockQty(matnr, batch);
            BigDecimal diffQty = calculateDiffQty(stockQtyDecimal, endingQty);
            // 4. 创建并保存明细记录
            MonthlySettleDetail detail = buildMonthlySettleDetail(
                monthlySettle.getId(), settleNo, matnr, batch, maktx, brand,
                beginningQty, inQty, outQty, endingQty, stockQtyDecimal, diffQty
            );
            detail.setIsDeleted(0); // 未删除
            monthlySettleDetailMapper.insert(detail);
            // 5. 累计统计
            totalInQty = totalInQty.add(inQty);
            totalOutQty = totalOutQty.add(outQty);
            materialCount++;
        }
        // 更新月结主记录
        monthlySettle.setTotalInQty(totalInQty);
        monthlySettle.setTotalOutQty(totalOutQty);
        monthlySettle.setTotalMaterials(materialCount);
        monthlySettle.setStatus(1); // 已月结
        monthlySettle.setUpdateBy(userId);
        monthlySettle.setUpdateTime(new Date());
        this.updateById(monthlySettle);
        // 更新订单的月结信息(入库和出库都要更新)
        this.baseMapper.updateOrderSettleInfo(monthlySettle.getId(), settleNo, startDateStr, endDateStr);
        this.baseMapper.updateOrderSettleInfoPakout(monthlySettle.getId(), settleNo, startDateStr, endDateStr);
        MonthlySettleResultVO result = new MonthlySettleResultVO();
        result.setSettleId(monthlySettle.getId());
        result.setSettleNo(settleNo);
        result.setTotalInQty(totalInQty);
        result.setTotalOutQty(totalOutQty);
        result.setTotalMaterials(materialCount);
        return result;
    }
    @Override
    public MonthlySettleStatisticsVO getSettleStatistics(Long settleId) {
        MonthlySettle settle = this.selectById(settleId);
        if (settle == null || (settle.getIsDeleted() != null && settle.getIsDeleted() == 1)) {
            throw new CoolException("月结记录不存在");
        }
        // 关联物料表查询明细
        List<MonthlySettleDetail> details = monthlySettleDetailMapper.selectDetailWithMat(settleId);
        MonthlySettleStatisticsVO result = new MonthlySettleStatisticsVO();
        result.setSettle(settle);
        result.setDetails(details);
        return result;
    }
    @Override
    public Page<MonthlySettle> getPage(Page<MonthlySettle> page) {
        Map<String, Object> condition = page.getCondition();
        EntityWrapper<MonthlySettle> wrapper = new EntityWrapper<>();
        wrapper.eq("is_deleted", 0);
        if (condition != null) {
            if (condition.get("settleNo") != null) {
                wrapper.like("settle_no", condition.get("settleNo").toString());
            }
            if (condition.get("status") != null) {
                wrapper.eq("status", condition.get("status"));
            }
            if (condition.get("startDate") != null && condition.get("endDate") != null) {
                wrapper.ge("start_date", condition.get("startDate"));
                wrapper.le("end_date", condition.get("endDate"));
            }
        }
        wrapper.orderBy("create_time", false);
        List<MonthlySettle> list = this.selectList(wrapper);
        EntityWrapper<MonthlySettle> countWrapper = new EntityWrapper<>();
        countWrapper.eq("is_deleted", 0);
        if (condition != null) {
            if (condition.get("settleNo") != null) {
                countWrapper.like("settle_no", condition.get("settleNo").toString());
            }
            if (condition.get("status") != null) {
                countWrapper.eq("status", condition.get("status"));
            }
            if (condition.get("startDate") != null && condition.get("endDate") != null) {
                countWrapper.ge("start_date", condition.get("startDate"));
                countWrapper.le("end_date", condition.get("endDate"));
            }
        }
        page.setRecords(list);
        page.setTotal(this.selectCount(countWrapper));
        return page;
    }
    /**
     * 获取期初库存(上期结余)
     */
    private BigDecimal getBeginningQty(String matnr, String batch, String brand, Map<String, BigDecimal> previousEndingQtyMap) {
        String key = matnr + "_" + batch + "_" + brand;
        return previousEndingQtyMap.getOrDefault(key, BigDecimal.ZERO);
    }
    /**
     * 计算期末库存(期初+入库-出库)
     */
    private BigDecimal calculateEndingQty(BigDecimal beginningQty, BigDecimal inQty, BigDecimal outQty) {
        return beginningQty.add(inQty).subtract(outQty);
    }
    /**
     * 获取当前实际库存数量
     */
    private BigDecimal getCurrentStockQty(String matnr, String batch) {
        Double stockQty = manLocDetlService.queryStockAnfme(matnr, batch);
        return stockQty != null ? BigDecimal.valueOf(stockQty) : BigDecimal.ZERO;
    }
    /**
     * 计算差异数量(实际库存-期末库存)
     */
    private BigDecimal calculateDiffQty(BigDecimal stockQty, BigDecimal endingQty) {
        return stockQty.subtract(endingQty);
    }
    /**
     * 构建月结明细对象
     */
    private MonthlySettleDetail buildMonthlySettleDetail(
            Long settleId, String settleNo, String matnr, String batch, String maktx, String brand,
            BigDecimal beginningQty, BigDecimal inQty, BigDecimal outQty, BigDecimal endingQty,
            BigDecimal stockQty, BigDecimal diffQty) {
        MonthlySettleDetail detail = new MonthlySettleDetail();
        // 基本信息
        detail.setSettleId(settleId);
        detail.setSettleNo(settleNo);
        detail.setMatnr(matnr);
        detail.setBatch(batch);
        detail.setMaktx(maktx);
        detail.setBrand(brand);
        // 数量信息
        detail.setBeginningQty(beginningQty);
        detail.setInQty(inQty);
        detail.setOutQty(outQty);
        detail.setEndingQty(endingQty);
        detail.setStockQty(stockQty);
        detail.setDiffQty(diffQty);
        // 时间信息
        detail.setCreateTime(new Date());
        return detail;
    }
    @Override
    @Transactional
    public void deleteSettle(Long settleId) {
        MonthlySettle settle = this.selectById(settleId);
        if (settle == null || (settle.getIsDeleted() != null && settle.getIsDeleted() == 1)) {
            throw new CoolException("月结记录不存在");
        }
        // 清除出入库订单的月结信息
        this.baseMapper.clearOrderSettleInfo(settleId);
        this.baseMapper.clearOrderSettleInfoPakout(settleId);
        // 逻辑删除月结明细
        EntityWrapper<MonthlySettleDetail> detailWrapper = new EntityWrapper<>();
        detailWrapper.eq("settle_id", settleId);
        detailWrapper.eq("is_deleted", 0);
        List<MonthlySettleDetail> details = monthlySettleDetailMapper.selectList(detailWrapper);
        if (details != null && !details.isEmpty()) {
            for (MonthlySettleDetail detail : details) {
                detail.setIsDeleted(1);
                monthlySettleDetailMapper.updateById(detail);
            }
        }
        // 逻辑删除月结主记录
        settle.setIsDeleted(1);
        settle.setUpdateTime(new Date());
        this.updateById(settle);
    }
}
src/main/resources/mapper/MonthlySettleDetailMapper.xml
New file
@@ -0,0 +1,62 @@
<?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.zy.asrs.mapper.MonthlySettleDetailMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.zy.asrs.entity.MonthlySettleDetail">
        <id column="id" property="id" />
        <result column="settle_id" property="settleId" />
        <result column="settle_no" property="settleNo" />
        <result column="matnr" property="matnr" />
        <result column="batch" property="batch" />
        <result column="maktx" property="maktx" />
        <result column="brand" property="brand" />
        <result column="beginning_qty" property="beginningQty" />
        <result column="in_qty" property="inQty" />
        <result column="out_qty" property="outQty" />
        <result column="ending_qty" property="endingQty" />
        <result column="stock_qty" property="stockQty" />
        <result column="diff_qty" property="diffQty" />
        <result column="create_time" property="createTime" />
        <result column="is_deleted" property="isDeleted" />
    </resultMap>
    <!-- 关联物料表的查询映射结果(获取其他物料信息) -->
    <resultMap id="DetailWithMatResultMap" type="com.zy.asrs.entity.MonthlySettleDetail" extends="BaseResultMap">
        <result column="specs" property="specs" />
        <result column="model" property="model" />
        <result column="color" property="color" />
        <result column="unit" property="unit" />
    </resultMap>
    <!-- 查询月结明细(从明细表查询,关联物料表获取补充信息) -->
    <select id="selectDetailWithMat" resultMap="DetailWithMatResultMap">
        SELECT
            d.id,
            d.settle_id,
            d.settle_no,
            d.matnr,
            d.batch,
            d.maktx,
            d.brand,
            d.beginning_qty,
            d.in_qty,
            d.out_qty,
            d.ending_qty,
            d.stock_qty,
            d.diff_qty,
            d.create_time,
            m.specs,
            m.model,
            m.color,
            m.unit
        FROM man_monthly_settle_detail d
        LEFT JOIN man_mat m ON d.matnr = m.matnr
        WHERE d.settle_id = #{settleId}
        AND d.is_deleted = 0
        ORDER BY d.matnr, d.batch
    </select>
</mapper>
src/main/resources/mapper/MonthlySettleMapper.xml
New file
@@ -0,0 +1,159 @@
<?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.zy.asrs.mapper.MonthlySettleMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.zy.asrs.entity.MonthlySettle">
        <id column="id" property="id" />
    <result column="settle_no" property="settleNo" />
    <result column="start_date" property="startDate" />
        <result column="end_date" property="endDate" />
        <result column="status" property="status" />
        <result column="total_in_qty" property="totalInQty" />
        <result column="total_out_qty" property="totalOutQty" />
        <result column="total_materials" property="totalMaterials" />
        <result column="memo" property="memo" />
        <result column="create_by" property="createBy" />
        <result column="create_time" property="createTime" />
        <result column="update_by" property="updateBy" />
        <result column="update_time" property="updateTime" />
        <result column="is_deleted" property="isDeleted" />
    </resultMap>
    <!-- 获取最近的月结记录 -->
    <select id="selectLatestSettle" resultMap="BaseResultMap">
        SELECT TOP 1 * FROM man_monthly_settle
        WHERE is_deleted = 0
        ORDER BY end_date DESC
    </select>
    <!-- 统计月结时间范围内的订单数量 -->
    <select id="countOrdersInRange" resultType="int">
        SELECT COUNT(*) FROM man_order
        WHERE status = 1
        AND order_time >= #{startDate}
        AND order_time &lt;= #{endDate}
    </select>
    <!-- 统计月结时间范围内未完成的订单数量(入库和出库) -->
    <select id="countUnfinishedOrdersInRange" resultType="int">
        SELECT COUNT(*) FROM (
            SELECT DISTINCT o.id FROM man_order_log_pakin o
            INNER JOIN man_order_detl_log_pakin od ON o.id = od.order_id
            WHERE o.status = 1
            AND CONVERT(date, o.order_time) >= #{startDate}
            AND o.order_time &lt;= #{endDate}
            AND o.move_status != 2
            AND (od.anfme > od.qty OR od.qty IS NULL)
            AND (o.monthly_settle_id IS NULL OR o.monthly_settle_id = 0)
            UNION
            SELECT DISTINCT o.id FROM man_order_log_pakout o
            INNER JOIN man_order_detl_log_pakout od ON o.id = od.order_id
            WHERE o.status = 1
            AND CONVERT(date, o.order_time) >= #{startDate}
            AND o.order_time &lt;= #{endDate}
            AND o.move_status != 2
            AND (od.anfme > od.qty OR od.qty IS NULL)
            AND (o.monthly_settle_id IS NULL OR o.monthly_settle_id = 0)
        ) t
    </select>
    <!-- 统计月结时间范围内的物料出入库数量(合并入库出库) -->
    <select id="statisticsMaterialInOut" resultType="com.zy.asrs.entity.result.MaterialInOutStatDTO">
        SELECT
            matnr,
            MAX(maktx) as maktx,
            batch,
            brand,
            SUM(in_qty) as inQty,
            SUM(out_qty) as outQty
        FROM (
            SELECT
                od.matnr,
                MAX(od.maktx) as maktx,
                ISNULL(od.batch, '') as batch,
                ISNULL(od.brand, '') as brand,
                SUM(od.qty) as in_qty,
                0 as out_qty
            FROM man_order_log_pakin o
            INNER JOIN man_order_detl_log_pakin od ON o.id = od.order_id
            WHERE o.status = 1
            AND CONVERT(date, o.order_time) >= #{startDate}
            AND o.order_time &lt;= #{endDate}
            AND o.move_status = 2
            AND (o.monthly_settle_id IS NULL OR o.monthly_settle_id = 0)
            GROUP BY od.matnr, od.batch, od.brand
            UNION ALL
            SELECT
                od.matnr,
                MAX(od.maktx) as maktx,
                ISNULL(od.batch, '') as batch,
                ISNULL(od.brand, '') as brand,
                0 as in_qty,
                SUM(od.qty) as out_qty
            FROM man_order_log_pakout o
            INNER JOIN man_order_detl_log_pakout od ON o.id = od.order_id
            WHERE o.status = 1
            AND CONVERT(date, o.order_time) >= #{startDate}
            AND o.order_time &lt;= #{endDate}
            AND o.move_status = 2
            AND (o.monthly_settle_id IS NULL OR o.monthly_settle_id = 0)
            GROUP BY od.matnr, od.batch, od.brand
        ) t
        GROUP BY matnr, batch, brand
    </select>
    <!-- 获取上一个月结的物料期末库存 -->
    <select id="getPreviousSettleEndingQty" resultType="com.zy.asrs.entity.result.PreviousSettleEndingQtyDTO">
        SELECT
            matnr,
            batch,
            brand,
            ending_qty as endingQty
        FROM man_monthly_settle_detail
        WHERE settle_id = #{previousSettleId}
    </select>
    <!-- 更新入库订单的月结信息 -->
    <update id="updateOrderSettleInfo">
        UPDATE man_order_log_pakin
        SET monthly_settle_id = #{settleId},
            monthly_settle_no = #{settleNo}
        WHERE status = 1
        AND CONVERT(date, order_time) >= #{startDate}
        AND order_time &lt;= #{endDate}
        AND move_status = 2
        AND (monthly_settle_id IS NULL OR monthly_settle_id = 0)
    </update>
    <!-- 更新出库订单的月结信息 -->
    <update id="updateOrderSettleInfoPakout">
        UPDATE man_order_log_pakout
        SET monthly_settle_id = #{settleId},
            monthly_settle_no = #{settleNo}
        WHERE status = 1
        AND CONVERT(date, order_time) >= #{startDate}
        AND order_time &lt;= #{endDate}
        AND move_status = 2
        AND (monthly_settle_id IS NULL OR monthly_settle_id = 0)
    </update>
    <!-- 清除入库订单的月结信息 -->
    <update id="clearOrderSettleInfo">
        UPDATE man_order_log_pakin
        SET monthly_settle_id = NULL,
            monthly_settle_no = NULL
        WHERE monthly_settle_id = #{settleId}
    </update>
    <!-- 清除出库订单的月结信息 -->
    <update id="clearOrderSettleInfoPakout">
        UPDATE man_order_log_pakout
        SET monthly_settle_id = NULL,
            monthly_settle_no = NULL
        WHERE monthly_settle_id = #{settleId}
    </update>
</mapper>
src/main/webapp/static/js/monthlySettle/monthlySettle.js
New file
@@ -0,0 +1,630 @@
var pageCurr;
layui.config({
    base: baseUrl + "/static/layui/lay/modules/"
}).use(['layer', 'form', 'table', 'util', 'admin', 'laydate', 'laytpl'], function () {
    var $ = layui.jquery;
    var layer = layui.layer;
    var form = layui.form;
    var table = layui.table;
    var util = layui.util;
    var admin = layui.admin;
    var layDate = layui.laydate;
    var laytpl = layui.laytpl;
    // 渲染表格
    tableIns = table.render({
        elem: '#monthlySettle',
        url: baseUrl + '/monthlySettle/list/auth',
        headers: {token: localStorage.getItem('token')},
        method: 'POST',
        page: true,
        cellMinWidth: 100,
        where: {},
        cols: [[
            {type: 'numbers'},
            {field: 'settleNo', title: '月结编号', width: 180},
            {field: 'startDate$', align: 'center', title: '起始日期', width: 120},
            {field: 'endDate$', align: 'center', title: '结束日期', width: 120},
            {field: 'totalInQty', align: 'center', title: '总入库数量', width: 120},
            {field: 'totalOutQty', align: 'center', title: '总出库数量', width: 120},
            {field: 'totalMaterials', align: 'center', title: '物料种类数', width: 120},
            {field: 'createTime$', title: '创建时间', minWidth: 180, width: 180},
            {align: 'center', title: '操作', toolbar: '#operate', width: 180}
        ]],
        request: {
            pageName: 'curr',
            pageSize: 'limit'
        },
        parseData: function (res) {
            return {
                'code': res.code,
                'msg': res.msg,
                'count': res.data.total,
                'data': res.data.records
            }
        },
        response: {
            statusCode: 200
        },
        done: function (res, curr, count) {
            if (res.code === 403) {
                top.location.href = baseUrl + "/";
            }
            pageCurr = curr;
        }
    });
    // 搜索
    form.on('submit(search)', function (data) {
        pageCurr = 1;
        tableReload(false);
    });
    // 重置
    form.on('submit(reset)', function (data) {
        pageCurr = 1;
        clearFormVal($('#search-box'));
        // 手动清空日期范围选择器
        $('input[name="date_range"]').val('');
        // 显式清空表格配置中的 where 参数
        if (tableIns && tableIns.config) {
            tableIns.config.where = {};
        }
        // 使用 setTimeout 确保表单值被完全清空后再重新加载表格
        setTimeout(function() {
            tableReload(false);
        }, 0);
    });
    // 日期范围选择器
    layDate.render({
        elem: '.layui-laydate-range',
        type: 'date',
        range: true
    });
    // 发起月结
    $('#startSettleBtn').click(function () {
        showStartSettleDialog();
    });
    // 显示发起月结弹窗
    function showStartSettleDialog() {
        admin.open({
            type: 1,
            title: '发起月结',
            content: $('#startSettleDialog').html(),
            area: '500px',
            success: function (layero, dIndex) {
                var startDateIns = null;
                var endDateIns = null;
                // 初始化结束日期选择器(先初始化,后续再设置min)
                // 使用 setTimeout 确保 DOM 已完全渲染
                setTimeout(function() {
                    var $endDate = layero.find('#endDate');
                    if ($endDate.length > 0) {
                        endDateIns = layDate.render({
                            elem: $endDate[0],
                            type: 'date',
                            done: function(value, date, endDate){
                                var startDate = layero.find('#startDate').val();
                                if (startDate && value < startDate) {
                                    layer.msg('结束日期不能早于起始日期', {icon: 2});
                                    layero.find('#endDate').val('');
                                    checkUnfinishedOrders(startDate, '');
                                    return;
                                }
                                checkUnfinishedOrders(startDate, value);
                            }
                        });
                    }
                }, 50);
                // 获取下一个月结的起始日期(最晚月结记录结束日期的下一天)
                $.ajax({
                    url: baseUrl + '/monthlySettle/nextStartDate/auth',
                    headers: {'token': localStorage.getItem('token')},
                    method: 'POST',
                    success: function (res) {
                        if (res.code === 200) {
                            // 数据在 msg 字段中
                            var nextStartDate = res.msg;
                            // 确保 nextStartDate 是字符串格式
                            if (nextStartDate != null && nextStartDate !== '') {
                                // 转换为字符串,处理可能的数字或其他类型
                                nextStartDate = String(nextStartDate).trim();
                                if (nextStartDate !== '' && nextStartDate !== 'null') {
                                    // 使用 setTimeout 确保 DOM 已完全渲染
                                    setTimeout(function() {
                                        // 使用 layero.find 查找弹窗内的元素
                                        var $startDate = layero.find('#startDate');
                                        if ($startDate.length > 0) {
                                            $startDate.val(nextStartDate);
                                            $startDate.attr('readonly', true);
                                            $startDate.css('background-color', '#f5f5f5');
                                            $startDate.css('cursor', 'not-allowed');
                                            console.log('已设置起始日期:', $startDate.val());
                                        } else {
                                            console.error('未找到 #startDate 元素');
                                        }
                                    }, 50);
                                    // 获取最晚结束日期用于提示
                                    $.ajax({
                                        url: baseUrl + '/monthlySettle/latestEndDate/auth',
                                        headers: {'token': localStorage.getItem('token')},
                                        method: 'POST',
                                        success: function (latestRes) {
                                            var latestEndDate = (latestRes.code === 200 && latestRes.msg) ? String(latestRes.msg) : '';
                                            var tipMsg = latestEndDate
                                                ? '提示:最晚月结记录结束日期为 ' + latestEndDate + ',本次月结起始日期已自动设置为 ' + nextStartDate + ',不可修改'
                                                : '提示:本次月结起始日期已自动设置为 ' + nextStartDate + ',不可修改';
                                            layero.find('#settleTip').html(tipMsg);
                                        },
                                        error: function() {
                                            layero.find('#settleTip').html('提示:本次月结起始日期已自动设置为 ' + nextStartDate + ',不可修改');
                                        }
                                    });
                                    // 重新渲染结束日期选择器,设置最小日期为起始日期
                                    setTimeout(function() {
                                        endDateIns = layDate.render({
                                            elem: layero.find('#endDate')[0],
                                            type: 'date',
                                            min: nextStartDate,
                                            done: function(value, date, endDate){
                                                var startDate = layero.find('#startDate').val();
                                                if (startDate && value < startDate) {
                                                    layer.msg('结束日期不能早于起始日期', {icon: 2});
                                                    layero.find('#endDate').val('');
                                                    checkUnfinishedOrders(startDate, '');
                                                    return;
                                                }
                                                checkUnfinishedOrders(startDate, value);
                                            }
                                        });
                                    }, 100);
                                }
                            } else {
                                // 没有月结记录,允许自由选择起始日期
                                setTimeout(function() {
                                    var $startDate = layero.find('#startDate');
                                    if ($startDate.length > 0) {
                                        startDateIns = layDate.render({
                                            elem: $startDate[0],
                                            type: 'date',
                                            done: function(value, date, endDate){
                                                // 当起始日期改变时,重新渲染结束日期选择器,设置最小日期
                                                var currentEndDate = layero.find('#endDate').val();
                                                if (currentEndDate && currentEndDate < value) {
                                                    layero.find('#endDate').val('');
                                                    layero.find('#settleTip').html('<span style="color: #ff5722;">警告:结束日期不能早于起始日期,请重新选择结束日期</span>');
                                                }
                                                // 重新渲染结束日期选择器
                                                endDateIns = layDate.render({
                                                    elem: layero.find('#endDate')[0],
                                                    type: 'date',
                                                    min: value,
                                                    done: function(endValue, endDate, endEndDate){
                                                        if (value && endValue < value) {
                                                            layer.msg('结束日期不能早于起始日期', {icon: 2});
                                                            layero.find('#endDate').val('');
                                                            checkUnfinishedOrders(value, '');
                                                            return;
                                                        }
                                                        checkUnfinishedOrders(value, endValue);
                                                    }
                                                });
                                                checkUnfinishedOrders(value, layero.find('#endDate').val());
                                            }
                                        });
                                        layero.find('#settleTip').html('提示:首次月结,请选择起始日期');
                                    }
                                }, 50);
                            }
                        } else if (res.code === 403) {
                            top.location.href = baseUrl + "/";
                        }
                    },
                    error: function() {
                        // 如果获取失败,允许自由选择起始日期
                        setTimeout(function() {
                            var $startDate = layero.find('#startDate');
                            if ($startDate.length > 0) {
                                startDateIns = layDate.render({
                                    elem: $startDate[0],
                                    type: 'date',
                                    done: function(value, date, endDate){
                                        var currentEndDate = layero.find('#endDate').val();
                                        if (currentEndDate && currentEndDate < value) {
                                            layero.find('#endDate').val('');
                                            layero.find('#settleTip').html('<span style="color: #ff5722;">警告:结束日期不能早于起始日期,请重新选择结束日期</span>');
                                        }
                                        endDateIns = layDate.render({
                                            elem: layero.find('#endDate')[0],
                                            type: 'date',
                                            min: value,
                                            done: function(endValue, endDate, endEndDate){
                                                if (value && endValue < value) {
                                                    layer.msg('结束日期不能早于起始日期', {icon: 2});
                                                    layero.find('#endDate').val('');
                                                    checkUnfinishedOrders(value, '');
                                                    return;
                                                }
                                                checkUnfinishedOrders(value, endValue);
                                            }
                                        });
                                        checkUnfinishedOrders(value, layero.find('#endDate').val());
                                    }
                                });
                                layero.find('#settleTip').html('提示:请选择起始日期');
                            }
                        }, 50);
                    }
                });
                // 表单提交事件
                form.on('submit(startSettleSubmit)', function (data) {
                    var startDate = layero.find('#startDate').val();
                    var endDate = layero.find('#endDate').val();
                    if (!startDate || !endDate) {
                        layer.msg('请选择起始日期和结束日期', {icon: 2});
                        return false;
                    }
                    if (startDate > endDate) {
                        layer.msg('结束日期不能早于起始日期,请重新选择', {icon: 2});
                        return false;
                    }
                    // 将结束日期设置为当天的 23:59:59
                    var endDateTime = endDate + ' 23:59:59';
                    layer.load(2);
                    $.ajax({
                        url: baseUrl + '/monthlySettle/start/auth',
                        headers: {'token': localStorage.getItem('token')},
                        data: {
                            startDate: startDate,
                            endDate: endDateTime
                        },
                        method: 'POST',
                        success: function (res) {
                            layer.closeAll('loading');
                            if (res.code === 200) {
                                layer.close(dIndex);
                                tableIns.reload({page: {curr: 1}});
                                layer.msg(res.msg || '月结成功', {icon: 1});
                            } else if (res.code === 403) {
                                top.location.href = baseUrl + "/";
                            } else {
                                layer.msg(res.msg || '月结失败', {icon: 2});
                            }
                        },
                        error: function() {
                            layer.closeAll('loading');
                            layer.msg('月结请求失败', {icon: 2});
                        }
                    });
                    return false;
                });
            }
        });
    }
    // 检查未完成的订单
    function checkUnfinishedOrders(startDate, endDate) {
        if (!startDate && !endDate) {
            $('#settleTip').html('<span style="color: #999;">请选择起始日期和结束日期</span>');
            return;
        }
        if (!startDate) {
            $('#settleTip').html('<span style="color: #999;">请选择起始日期</span>');
            return;
        }
        if (!endDate) {
            $('#settleTip').html('<span style="color: #999;">请选择结束日期</span>');
            return;
        }
        // 验证日期范围
        if (startDate > endDate) {
            $('#settleTip').html('<span style="color: #ff5722;">警告:结束日期不能早于起始日期,请重新选择</span>');
            return;
        }
        $.ajax({
            url: baseUrl + '/monthlySettle/checkUnfinished/auth',
            headers: {'token': localStorage.getItem('token')},
            data: {
                startDate: startDate,
                endDate: endDate
            },
            method: 'POST',
            success: function (res) {
                if (res.code === 200) {
                    $('#settleTip').html('<span style="color: #5FB878;">✓ 日期范围有效,可以正常进行月结</span>');
                } else {
                    $('#settleTip').html('<span style="color: #ff5722;">警告:' + (res.msg || '月结时间范围内存在未完成的订单,无法进行月结') + '</span>');
                }
            },
            error: function() {
                $('#settleTip').html('<span style="color: #ff5722;">检查失败,请重试</span>');
            }
        });
    }
    // 工具条点击事件
    table.on('tool(monthlySettle)', function (obj) {
        var data = obj.data;
        var layEvent = obj.event;
        if (layEvent === 'detail') {
            showDetailDialog(data);
        } else if (layEvent === 'delete') {
            deleteSettle(data);
        }
    });
    // 显示明细弹窗
    function showDetailDialog(data) {
        layer.load(2);
        $.ajax({
            url: baseUrl + '/monthlySettle/statistics/' + data.id + '/auth',
            headers: {'token': localStorage.getItem('token')},
            method: 'POST',
            success: function (res) {
                layer.closeAll('loading');
                if (res.code === 200) {
                    var settle = res.data.settle;
                    var details = res.data.details;
                    // 先渲染模板
                    var template = $('#detailDialog').html();
                    var html = laytpl(template).render(settle);
                    admin.open({
                        type: 1,
                        title: '月结明细 - ' + settle.settleNo,
                        content: html,
                        area: ['90%', '80%'],
                        success: function (layero, dIndex) {
                            // 渲染明细表格(对账单格式)
                            table.render({
                                elem: '#detailTable',
                                data: details,
                                page: true,
                                cellMinWidth: 100,
                                cols: [[
                                    {type: 'numbers', title: '序号', width: 60, align: 'center'},
                                    {field: 'matnr', title: '物料编码', width: 150},
                                    {field: 'maktx', title: '物料名称', width: 200},
                                    {field: 'batch', title: '批次', width: 120},
                                    {field: 'brand', title: '品牌', width: 120},
                                    {
                                        field: 'beginningQty',
                                        align: 'right',
                                        title: '期初库存',
                                        width: 120,
                                        templet: function (d) {
                                            var qty = parseFloat(d.beginningQty || 0);
                                            return qty.toFixed(2);
                                        }
                                    },
                                    {
                                        field: 'inQty',
                                        align: 'right',
                                        title: '本期入库',
                                        width: 120,
                                        templet: function (d) {
                                            var qty = parseFloat(d.inQty || 0);
                                            return qty.toFixed(2);
                                        }
                                    },
                                    {
                                        field: 'outQty',
                                        align: 'right',
                                        title: '本期出库',
                                        width: 120,
                                        templet: function (d) {
                                            var qty = parseFloat(d.outQty || 0);
                                            return qty.toFixed(2);
                                        }
                                    },
                                    {
                                        field: 'endingQty',
                                        align: 'right',
                                        title: '期末库存',
                                        width: 120,
                                        templet: function (d) {
                                            var qty = parseFloat(d.endingQty || 0);
                                            return qty.toFixed(2);
                                        }
                                    },
                                    {
                                        field: 'stockQty',
                                        align: 'right',
                                        title: '实际库存',
                                        width: 120,
                                        templet: function (d) {
                                            var qty = parseFloat(d.stockQty || 0);
                                            return qty.toFixed(2);
                                        }
                                    },
                                    {
                                        field: 'diffQty',
                                        align: 'right',
                                        title: '差异数量',
                                        width: 120,
                                        templet: function (d) {
                                            var diff = parseFloat(d.diffQty || 0);
                                            if (diff > 0) {
                                                return '<span style="color: #5FB878;">+' + diff.toFixed(2) + '</span>';
                                            } else if (diff < 0) {
                                                return '<span style="color: #ff5722;">' + diff.toFixed(2) + '</span>';
                                            } else {
                                                return diff.toFixed(2);
                                            }
                                        }
                                    }
                                ]]
                            });
                        }
                    });
                } else if (res.code === 403) {
                    top.location.href = baseUrl + "/";
                } else {
                    layer.msg(res.msg || '获取明细失败', {icon: 2});
                }
            }
        });
    }
    // 删除月结记录
    function deleteSettle(data) {
        layer.confirm('确认要删除月结记录 "' + data.settleNo + '" 吗?删除后将清除关联的出入库订单月结信息,可以重新进行月结。', {
            shade: .1,
            skin: 'layui-layer-admin'
        }, function (i) {
            layer.close(i);
            layer.load(2);
            $.ajax({
                url: baseUrl + '/monthlySettle/' + data.id + '/auth',
                headers: {'token': localStorage.getItem('token')},
                method: 'DELETE',
                success: function (res) {
                    layer.closeAll('loading');
                    if (res.code === 200) {
                        tableIns.reload({page: {curr: 1}});
                        layer.msg(res.msg || '删除成功', {icon: 1});
                    } else if (res.code === 403) {
                        top.location.href = baseUrl + "/";
                    } else {
                        layer.msg(res.msg || '删除失败', {icon: 2});
                    }
                },
                error: function() {
                    layer.closeAll('loading');
                    layer.msg('删除失败', {icon: 2});
                }
            });
        });
    }
});
function tableReload(child) {
    var searchData = {};
    $.each($('#search-box [name]').serializeArray(), function() {
        var value = this.value;
        // 处理日期范围字段
        if (this.name === 'date_range') {
            // 只处理非空值
            if (value && value.trim() !== '') {
                var dates = value.split(' - ');
                if (dates.length === 2) {
                    var startDate = dates[0].trim();
                    var endDate = dates[1].trim();
                    if (startDate !== '' && endDate !== '') {
                        searchData.startDate = startDate;
                        searchData.endDate = endDate;
                    }
                }
            }
        } else if (this.name === 'settle_no') {
            // 只处理非空值
            var trimmedValue = value ? value.trim() : '';
            if (trimmedValue !== '') {
                searchData.settleNo = trimmedValue;
            }
        }
    });
    debugger; // 调试用:检查 searchData 的值
    // 获取 table 实例
    var tableInstance = child ? parent.tableIns : tableIns;
    // 如果 searchData 为空对象,需要显式传入覆盖所有可能参数的对象
    // 因为 layui table 可能会合并旧的参数,即使传入空对象也可能保留旧值
    if (Object.keys(searchData).length === 0) {
        if (tableInstance && tableInstance.config) {
            // 先保存旧的 where 中可能存在的所有键
            var oldWhereKeys = [];
            if (tableInstance.config.where) {
                for (var key in tableInstance.config.where) {
                    if (tableInstance.config.where.hasOwnProperty(key)) {
                        oldWhereKeys.push(key);
                    }
                }
            }
            // 完全替换 where 对象
            tableInstance.config.where = {};
            // 如果之前有参数,创建一个明确覆盖的对象,将所有旧参数设置为空字符串
            // 使用空字符串而不是 null,因为 layui 可能会过滤 null 值
            if (oldWhereKeys.length > 0) {
                var overrideWhere = {};
                oldWhereKeys.forEach(function(key) {
                    overrideWhere[key] = ''; // 设置为空字符串来覆盖旧值
                });
                searchData = overrideWhere;
                console.log('创建覆盖对象,旧参数键:', oldWhereKeys, '覆盖对象:', JSON.stringify(overrideWhere));
            } else {
                // 即使没有旧参数,也创建一个包含所有可能参数的空对象
                // 这样可以确保覆盖任何可能的旧参数
                searchData = {
                    settleNo: '',
                    startDate: '',
                    endDate: ''
                };
                console.log('创建默认覆盖对象:', JSON.stringify(searchData));
            }
        }
    }
    // 构建 reload 参数
    var reloadOptions = {
        where: searchData,
        page: {
            curr: pageCurr
        },
        done: function (res, curr, count) {
            if (res.code === 403) {
                top.location.href = baseUrl+"/";
            }
            pageCurr=curr;
            if (res.data.length === 0 && count !== 0) {
                var reloadTableInstance = child ? parent.tableIns : tableIns;
                // 如果 searchData 为空,也完全替换 where 对象
                if (Object.keys(searchData).length === 0 && reloadTableInstance && reloadTableInstance.config) {
                    reloadTableInstance.config.where = {};
                }
                reloadTableInstance.reload({
                    where: searchData,
                    page: {
                        curr: pageCurr-1
                    }
                });
                pageCurr -= 1;
            }
        }
    };
    // 调试:打印 reload 前的配置
    console.log('reload 前的 config.where:', JSON.stringify(tableInstance.config ? tableInstance.config.where : 'no config'));
    console.log('reload 时的 where 参数:', JSON.stringify(reloadOptions.where));
    tableInstance.reload(reloadOptions);
    // 调试:打印 reload 后的配置
    setTimeout(function() {
        console.log('reload 后的 config.where:', JSON.stringify(tableInstance.config ? tableInstance.config.where : 'no config'));
    }, 100);
}
src/main/webapp/views/monthlySettle/monthlySettle.html
New file
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>月结管理</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
    <link rel="stylesheet" href="../../static/css/admin.css?v=318" media="all">
    <link rel="stylesheet" href="../../static/css/cool.css" media="all">
</head>
<body>
<div class="layui-fluid">
    <div class="layui-card">
        <div class="layui-card-body">
            <div class="layui-form toolbar"  id="search-box">
                <div class="layui-form-item">
                    <div class="layui-inline">
                        <div class="layui-input-inline mr0">
                            <input name="settle_no" class="layui-input" type="text" placeholder="输入月结编号"/>
                        </div>
                    </div>
                    <div class="layui-inline" style="width: 300px">
                        <div class="layui-input-inline">
                            <input class="layui-input layui-laydate-range" name="date_range" type="text" placeholder="起始日期 - 结束日期" autocomplete="off" style="width: 300px">
                        </div>
                    </div>
                    <div class="layui-inline">
                        <button class="layui-btn icon-btn" lay-filter="search" lay-submit>
                            <i class="layui-icon">&#xe615;</i>搜索
                        </button>
                        <button id="startSettleBtn" class="layui-btn icon-btn layui-btn-normal">
                            <i class="layui-icon">&#xe608;</i>发起月结
                        </button>
                        <button type="button" class="layui-btn icon-btn layui-btn-primary" lay-filter="reset" lay-submit>
                            <i class="layui-icon">&#xe669;</i>重置
                        </button>
                    </div>
                </div>
            </div>
            <table class="layui-hide" id="monthlySettle" lay-filter="monthlySettle" ></table>
        </div>
    </div>
</div>
<!-- 表格操作列 -->
<script type="text/html" id="operate">
    <a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="detail">查看明细</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="delete">删除</a>
</script>
<!-- 发起月结弹窗 -->
<script type="text/html" id="startSettleDialog">
    <form id="startSettleForm" lay-filter="startSettleForm" class="layui-form model-form">
        <div class="layui-form-item">
            <label class="layui-form-label">起始日期:</label>
            <div class="layui-input-block">
                <input id="startDate" name="startDate" placeholder="选择起始日期" type="text" class="layui-input" autocomplete="off" lay-verType="tips" lay-verify="required">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">结束日期:</label>
            <div class="layui-input-block">
                <input id="endDate" name="endDate" placeholder="选择结束日期" type="text" class="layui-input" autocomplete="off" lay-verType="tips" lay-verify="required">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">提示:</label>
            <div class="layui-input-block">
                <div id="settleTip" style="color: #ff5722;"></div>
            </div>
        </div>
        <div class="layui-form-item text-right">
            <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog">取消</button>
            <button class="layui-btn" lay-filter="startSettleSubmit" lay-submit>确认月结</button>
        </div>
    </form>
</script>
<!-- 月结明细弹窗 -->
<script type="text/html" id="detailDialog">
    <div style="padding: 20px;">
        <div class="layui-row">
            <div class="layui-col-md6">
                {{# if(d.settleNo) { }}
                <p><strong>月结编号:</strong>{{ d.settleNo }}</p>
                {{# } }}
                {{# if(d.startDate$) { }}
                <p><strong>起始日期:</strong>{{ d.startDate$ }}</p>
                {{# } }}
                {{# if(d.endDate$) { }}
                <p><strong>结束日期:</strong>{{ d.endDate$ }}</p>
                {{# } }}
            </div>
            <div class="layui-col-md6">
                {{# if(d.totalInQty !== null && d.totalInQty !== undefined) { }}
                <p><strong>总入库数量:</strong>{{ d.totalInQty }}</p>
                {{# } }}
                {{# if(d.totalOutQty !== null && d.totalOutQty !== undefined) { }}
                <p><strong>总出库数量:</strong>{{ d.totalOutQty }}</p>
                {{# } }}
                {{# if(d.totalMaterials !== null && d.totalMaterials !== undefined) { }}
                <p><strong>物料种类数:</strong>{{ d.totalMaterials }}</p>
                {{# } }}
            </div>
        </div>
        <hr class="layui-bg-gray">
        <table id="detailTable" lay-filter="detailTable"></table>
    </div>
</script>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/layui/layui.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/cool.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/monthlySettle/monthlySettle.js" charset="utf-8"></script>
</body>
</html>