feat: 新增业务编码生成及设备产量统计服务
parent
41f4b8f6fb
commit
fe008bb747
@ -0,0 +1,104 @@
|
||||
package com.aucma.base.domain;
|
||||
|
||||
import com.aucma.common.core.domain.BaseEntity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备当日实时产量状态
|
||||
*/
|
||||
public class RtDailyProdState extends BaseEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Date prodDate;
|
||||
|
||||
private String deviceCode;
|
||||
|
||||
private String paramName;
|
||||
|
||||
private BigDecimal lastParamVal;
|
||||
|
||||
private BigDecimal currentTotal;
|
||||
|
||||
private Long resetCount;
|
||||
|
||||
private Long dirtyFlag;
|
||||
|
||||
private Date lastCollectTime;
|
||||
|
||||
private Date updateTime;
|
||||
|
||||
public Date getProdDate() {
|
||||
return prodDate;
|
||||
}
|
||||
|
||||
public void setProdDate(Date prodDate) {
|
||||
this.prodDate = prodDate;
|
||||
}
|
||||
|
||||
public String getDeviceCode() {
|
||||
return deviceCode;
|
||||
}
|
||||
|
||||
public void setDeviceCode(String deviceCode) {
|
||||
this.deviceCode = deviceCode;
|
||||
}
|
||||
|
||||
public String getParamName() {
|
||||
return paramName;
|
||||
}
|
||||
|
||||
public void setParamName(String paramName) {
|
||||
this.paramName = paramName;
|
||||
}
|
||||
|
||||
public BigDecimal getLastParamVal() {
|
||||
return lastParamVal;
|
||||
}
|
||||
|
||||
public void setLastParamVal(BigDecimal lastParamVal) {
|
||||
this.lastParamVal = lastParamVal;
|
||||
}
|
||||
|
||||
public BigDecimal getCurrentTotal() {
|
||||
return currentTotal;
|
||||
}
|
||||
|
||||
public void setCurrentTotal(BigDecimal currentTotal) {
|
||||
this.currentTotal = currentTotal;
|
||||
}
|
||||
|
||||
public Long getResetCount() {
|
||||
return resetCount;
|
||||
}
|
||||
|
||||
public void setResetCount(Long resetCount) {
|
||||
this.resetCount = resetCount;
|
||||
}
|
||||
|
||||
public Long getDirtyFlag() {
|
||||
return dirtyFlag;
|
||||
}
|
||||
|
||||
public void setDirtyFlag(Long dirtyFlag) {
|
||||
this.dirtyFlag = dirtyFlag;
|
||||
}
|
||||
|
||||
public Date getLastCollectTime() {
|
||||
return lastCollectTime;
|
||||
}
|
||||
|
||||
public void setLastCollectTime(Date lastCollectTime) {
|
||||
this.lastCollectTime = lastCollectTime;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return updateTime;
|
||||
}
|
||||
|
||||
public void setUpdateTime(Date updateTime) {
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.aucma.base.mapper;
|
||||
|
||||
import com.aucma.base.domain.BaseBizCodeSequence;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 业务编码序列表 Mapper
|
||||
*
|
||||
* @author Codex
|
||||
*/
|
||||
public interface BaseBizCodeSequenceMapper {
|
||||
/**
|
||||
* 按业务类型加锁查询编码序列
|
||||
*
|
||||
* @param bizType 业务类型
|
||||
* @return 编码序列
|
||||
*/
|
||||
BaseBizCodeSequence selectByBizTypeForUpdate(@Param("bizType") String bizType);
|
||||
|
||||
/**
|
||||
* 新增编码序列
|
||||
*
|
||||
* @param baseBizCodeSequence 编码序列
|
||||
* @return 结果
|
||||
*/
|
||||
int insertBaseBizCodeSequence(BaseBizCodeSequence baseBizCodeSequence);
|
||||
|
||||
/**
|
||||
* 更新编码序列
|
||||
*
|
||||
* @param baseBizCodeSequence 编码序列
|
||||
* @return 结果
|
||||
*/
|
||||
int updateBaseBizCodeSequence(BaseBizCodeSequence baseBizCodeSequence);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.aucma.base.mapper;
|
||||
|
||||
import com.aucma.base.domain.RtDailyProdState;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备当日实时产量状态Mapper
|
||||
*/
|
||||
public interface RtDailyProdStateMapper {
|
||||
|
||||
RtDailyProdState selectForUpdate(@Param("prodDate") Date prodDate,
|
||||
@Param("deviceCode") String deviceCode,
|
||||
@Param("paramName") String paramName);
|
||||
|
||||
int insertRtDailyProdState(RtDailyProdState state);
|
||||
|
||||
int updateRtDailyProdState(RtDailyProdState state);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.aucma.base.service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 统一业务编码生成服务
|
||||
*
|
||||
* @author Codex
|
||||
*/
|
||||
public interface IBusinessCodeGeneratorService {
|
||||
/**
|
||||
* 生成 SAP 计划编号
|
||||
*
|
||||
* @param businessDate 业务日期
|
||||
* @return SAP 计划编号
|
||||
*/
|
||||
String generateSapPlanCode(Date businessDate);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.aucma.base.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备当日实时产量状态Service
|
||||
*/
|
||||
public interface IRtDailyProdStateService {
|
||||
|
||||
void incrementProduction(String deviceCode, String paramName, BigDecimal newVal, Date collectTime);
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
package com.aucma.base.service.impl;
|
||||
|
||||
import com.aucma.base.domain.BaseBizCodeSequence;
|
||||
import com.aucma.base.mapper.BaseBizCodeSequenceMapper;
|
||||
import com.aucma.base.service.IBusinessCodeGeneratorService;
|
||||
import com.aucma.common.exception.ServiceException;
|
||||
import com.aucma.common.utils.DateUtils;
|
||||
import com.aucma.common.utils.StringUtils;
|
||||
import com.aucma.system.service.ISysConfigService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 统一业务编码生成服务实现
|
||||
*
|
||||
* @author Codex
|
||||
*/
|
||||
@Service
|
||||
public class BusinessCodeGeneratorServiceImpl implements IBusinessCodeGeneratorService {
|
||||
private static final String SAP_PLAN_BIZ_TYPE = "SAP_PLAN";
|
||||
private static final String SAP_PLAN_DATE_PATTERN_KEY = "mes.biz.code.sapPlan.datePattern";
|
||||
private static final String SAP_PLAN_SEQ_LENGTH_KEY = "mes.biz.code.sapPlan.seqLength";
|
||||
private static final String DEFAULT_DATE_PATTERN = "yyyyMM";
|
||||
private static final int DEFAULT_SEQ_LENGTH = 4;
|
||||
|
||||
@Autowired
|
||||
private BaseBizCodeSequenceMapper baseBizCodeSequenceMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String generateSapPlanCode(Date businessDate) {
|
||||
Date currentBusinessDate = businessDate != null ? businessDate : DateUtils.getNowDate();
|
||||
String datePattern = getConfigValue(SAP_PLAN_DATE_PATTERN_KEY, DEFAULT_DATE_PATTERN);
|
||||
int seqLength = parseSeqLength(getConfigValue(SAP_PLAN_SEQ_LENGTH_KEY, String.valueOf(DEFAULT_SEQ_LENGTH)));
|
||||
String currentPeriod = DateUtils.parseDateToStr(datePattern, currentBusinessDate);
|
||||
|
||||
BaseBizCodeSequence sequence = getLockedSequence(SAP_PLAN_BIZ_TYPE, currentPeriod);
|
||||
Long nextSeq = calculateNextSeq(sequence, currentPeriod);
|
||||
sequence.setCurrentPeriod(currentPeriod);
|
||||
sequence.setCurrentSeq(nextSeq);
|
||||
sequence.setUpdatedTime(DateUtils.getNowDate());
|
||||
baseBizCodeSequenceMapper.updateBaseBizCodeSequence(sequence);
|
||||
|
||||
return currentPeriod + StringUtils.leftPad(String.valueOf(nextSeq), seqLength, '0');
|
||||
}
|
||||
|
||||
private BaseBizCodeSequence getLockedSequence(String bizType, String currentPeriod) {
|
||||
BaseBizCodeSequence sequence = baseBizCodeSequenceMapper.selectByBizTypeForUpdate(bizType);
|
||||
if (sequence != null) {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
BaseBizCodeSequence initSequence = new BaseBizCodeSequence();
|
||||
initSequence.setBizType(bizType);
|
||||
initSequence.setCurrentPeriod(currentPeriod);
|
||||
initSequence.setCurrentSeq(0L);
|
||||
initSequence.setRemark("统一业务编码序列");
|
||||
initSequence.setCreatedTime(DateUtils.getNowDate());
|
||||
initSequence.setUpdatedTime(DateUtils.getNowDate());
|
||||
try {
|
||||
baseBizCodeSequenceMapper.insertBaseBizCodeSequence(initSequence);
|
||||
} catch (DuplicateKeyException ex) {
|
||||
// 为什么忽略重复插入异常:并发场景下允许只有一个事务初始化成功,其他事务随后重新加锁读取即可。
|
||||
}
|
||||
|
||||
sequence = baseBizCodeSequenceMapper.selectByBizTypeForUpdate(bizType);
|
||||
if (sequence == null) {
|
||||
throw new ServiceException("业务编码序列初始化失败,请检查基础数据配置");
|
||||
}
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private Long calculateNextSeq(BaseBizCodeSequence sequence, String currentPeriod) {
|
||||
if (!StringUtils.equals(sequence.getCurrentPeriod(), currentPeriod)) {
|
||||
// 为什么跨周期要从 1 重新计数:编号规则要求前缀按年月分段,序号必须在新周期内重新累计。
|
||||
return 1L;
|
||||
}
|
||||
Long currentSeq = sequence.getCurrentSeq() == null ? 0L : sequence.getCurrentSeq();
|
||||
return currentSeq + 1L;
|
||||
}
|
||||
|
||||
private String getConfigValue(String configKey, String defaultValue) {
|
||||
String configValue = sysConfigService.selectConfigByKey(configKey);
|
||||
return StringUtils.isBlank(configValue) ? defaultValue : configValue.trim();
|
||||
}
|
||||
|
||||
private int parseSeqLength(String seqLengthValue) {
|
||||
try {
|
||||
int seqLength = Integer.parseInt(seqLengthValue);
|
||||
if (seqLength <= 0) {
|
||||
return DEFAULT_SEQ_LENGTH;
|
||||
}
|
||||
return seqLength;
|
||||
} catch (NumberFormatException ex) {
|
||||
return DEFAULT_SEQ_LENGTH;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
package com.aucma.base.service.impl;
|
||||
|
||||
import com.aucma.base.domain.RtDailyProdState;
|
||||
import com.aucma.base.mapper.RtDailyProdStateMapper;
|
||||
import com.aucma.base.service.IRtDailyProdStateService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 设备当日实时产量状态Service实现
|
||||
*/
|
||||
@Service
|
||||
public class RtDailyProdStateServiceImpl implements IRtDailyProdStateService {
|
||||
|
||||
@Autowired
|
||||
private RtDailyProdStateMapper rtDailyProdStateMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void incrementProduction(String deviceCode, String paramName, BigDecimal newVal, Date collectTime) {
|
||||
if (deviceCode == null || paramName == null || newVal == null || collectTime == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date prodDate = truncateToDay(collectTime);
|
||||
RtDailyProdState state = rtDailyProdStateMapper.selectForUpdate(prodDate, deviceCode, paramName);
|
||||
Date now = new Date();
|
||||
if (state == null) {
|
||||
RtDailyProdState insertState = new RtDailyProdState();
|
||||
insertState.setProdDate(prodDate);
|
||||
insertState.setDeviceCode(deviceCode);
|
||||
insertState.setParamName(paramName);
|
||||
// 为什么这样做:当天首条只建立基准值,避免把历史累计值误算成当天产量。
|
||||
insertState.setLastParamVal(newVal);
|
||||
insertState.setCurrentTotal(BigDecimal.ZERO);
|
||||
insertState.setResetCount(0L);
|
||||
insertState.setDirtyFlag(0L);
|
||||
insertState.setLastCollectTime(collectTime);
|
||||
insertState.setUpdateTime(now);
|
||||
rtDailyProdStateMapper.insertRtDailyProdState(insertState);
|
||||
return;
|
||||
}
|
||||
|
||||
BigDecimal lastParamVal = state.getLastParamVal() == null ? BigDecimal.ZERO : state.getLastParamVal();
|
||||
BigDecimal currentTotal = state.getCurrentTotal() == null ? BigDecimal.ZERO : state.getCurrentTotal();
|
||||
Long resetCount = state.getResetCount() == null ? 0L : state.getResetCount();
|
||||
|
||||
BigDecimal delta = BigDecimal.ZERO;
|
||||
if (newVal.compareTo(lastParamVal) > 0) {
|
||||
delta = newVal.subtract(lastParamVal);
|
||||
} else if (newVal.compareTo(lastParamVal) < 0) {
|
||||
// 为什么这样做:计数器回退时把当前值视为重置后的新增量,避免丢产量。
|
||||
delta = newVal;
|
||||
resetCount += 1;
|
||||
}
|
||||
|
||||
state.setLastParamVal(newVal);
|
||||
state.setCurrentTotal(currentTotal.add(delta));
|
||||
state.setResetCount(resetCount);
|
||||
state.setLastCollectTime(collectTime);
|
||||
state.setUpdateTime(now);
|
||||
rtDailyProdStateMapper.updateRtDailyProdState(state);
|
||||
}
|
||||
|
||||
private Date truncateToDay(Date collectTime) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(collectTime);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
<?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.aucma.base.mapper.BaseBizCodeSequenceMapper">
|
||||
|
||||
<resultMap id="BaseBizCodeSequenceResult" type="BaseBizCodeSequence">
|
||||
<result property="objId" column="obj_id"/>
|
||||
<result property="bizType" column="biz_type"/>
|
||||
<result property="currentPeriod" column="current_period"/>
|
||||
<result property="currentSeq" column="current_seq"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createdTime" column="created_time"/>
|
||||
<result property="updatedTime" column="updated_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByBizTypeForUpdate" resultMap="BaseBizCodeSequenceResult">
|
||||
select obj_id,
|
||||
biz_type,
|
||||
current_period,
|
||||
current_seq,
|
||||
remark,
|
||||
created_time,
|
||||
updated_time
|
||||
from base_biz_code_seq
|
||||
where biz_type = #{bizType}
|
||||
for update
|
||||
</select>
|
||||
|
||||
<insert id="insertBaseBizCodeSequence" parameterType="BaseBizCodeSequence">
|
||||
<selectKey keyProperty="objId" resultType="long" order="BEFORE">
|
||||
SELECT seq_base_biz_code_seq.NEXTVAL as objId FROM DUAL
|
||||
</selectKey>
|
||||
insert into base_biz_code_seq
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="objId != null">obj_id,</if>
|
||||
<if test="bizType != null">biz_type,</if>
|
||||
<if test="currentPeriod != null">current_period,</if>
|
||||
<if test="currentSeq != null">current_seq,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="createdTime != null">created_time,</if>
|
||||
<if test="updatedTime != null">updated_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="objId != null">#{objId},</if>
|
||||
<if test="bizType != null">#{bizType},</if>
|
||||
<if test="currentPeriod != null">#{currentPeriod},</if>
|
||||
<if test="currentSeq != null">#{currentSeq},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="createdTime != null">#{createdTime},</if>
|
||||
<if test="updatedTime != null">#{updatedTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="updateBaseBizCodeSequence" parameterType="BaseBizCodeSequence">
|
||||
update base_biz_code_seq
|
||||
<trim prefix="set" suffixOverrides=",">
|
||||
<if test="currentPeriod != null">current_period = #{currentPeriod},</if>
|
||||
<if test="currentSeq != null">current_seq = #{currentSeq},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
<if test="updatedTime != null">updated_time = #{updatedTime},</if>
|
||||
</trim>
|
||||
where obj_id = #{objId}
|
||||
</update>
|
||||
</mapper>
|
||||
@ -0,0 +1,50 @@
|
||||
<?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.aucma.base.mapper.RtDailyProdStateMapper">
|
||||
|
||||
<resultMap id="RtDailyProdStateResult" type="com.aucma.base.domain.RtDailyProdState">
|
||||
<result property="prodDate" column="prod_date"/>
|
||||
<result property="deviceCode" column="device_code"/>
|
||||
<result property="paramName" column="param_name"/>
|
||||
<result property="lastParamVal" column="last_param_val"/>
|
||||
<result property="currentTotal" column="current_total"/>
|
||||
<result property="resetCount" column="reset_count"/>
|
||||
<result property="dirtyFlag" column="dirty_flag"/>
|
||||
<result property="lastCollectTime" column="last_collect_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectForUpdate" resultMap="RtDailyProdStateResult">
|
||||
SELECT prod_date, device_code, param_name, last_param_val, current_total,
|
||||
reset_count, dirty_flag, last_collect_time, update_time
|
||||
FROM RT_DAILY_PROD_STATE
|
||||
WHERE prod_date = #{prodDate}
|
||||
AND device_code = #{deviceCode}
|
||||
AND param_name = #{paramName}
|
||||
FOR UPDATE
|
||||
</select>
|
||||
|
||||
<insert id="insertRtDailyProdState" parameterType="com.aucma.base.domain.RtDailyProdState">
|
||||
INSERT INTO RT_DAILY_PROD_STATE (
|
||||
prod_date, device_code, param_name, last_param_val, current_total,
|
||||
reset_count, dirty_flag, last_collect_time, update_time
|
||||
) VALUES (
|
||||
#{prodDate}, #{deviceCode}, #{paramName}, #{lastParamVal}, #{currentTotal},
|
||||
#{resetCount}, #{dirtyFlag}, #{lastCollectTime}, #{updateTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateRtDailyProdState" parameterType="com.aucma.base.domain.RtDailyProdState">
|
||||
UPDATE RT_DAILY_PROD_STATE
|
||||
SET last_param_val = #{lastParamVal},
|
||||
current_total = #{currentTotal},
|
||||
reset_count = #{resetCount},
|
||||
last_collect_time = #{lastCollectTime},
|
||||
update_time = #{updateTime}
|
||||
WHERE prod_date = #{prodDate}
|
||||
AND device_code = #{deviceCode}
|
||||
AND param_name = #{paramName}
|
||||
</update>
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue