diff --git a/aucma-base/src/main/java/com/aucma/base/domain/BaseBizCodeSequence.java b/aucma-base/src/main/java/com/aucma/base/domain/BaseBizCodeSequence.java
new file mode 100644
index 0000000..4abb243
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/domain/BaseBizCodeSequence.java
@@ -0,0 +1,92 @@
+package com.aucma.base.domain;
+
+import java.util.Date;
+
+/**
+ * 业务编码序列表对象 base_biz_code_seq
+ *
+ * @author Codex
+ */
+public class BaseBizCodeSequence {
+ private Long objId;
+
+ /**
+ * 业务类型编码
+ */
+ private String bizType;
+
+ /**
+ * 当前编码周期,如 yyyyMM
+ */
+ private String currentPeriod;
+
+ /**
+ * 当前已使用序号
+ */
+ private Long currentSeq;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ private Date createdTime;
+
+ private Date updatedTime;
+
+ public Long getObjId() {
+ return objId;
+ }
+
+ public void setObjId(Long objId) {
+ this.objId = objId;
+ }
+
+ public String getBizType() {
+ return bizType;
+ }
+
+ public void setBizType(String bizType) {
+ this.bizType = bizType;
+ }
+
+ public String getCurrentPeriod() {
+ return currentPeriod;
+ }
+
+ public void setCurrentPeriod(String currentPeriod) {
+ this.currentPeriod = currentPeriod;
+ }
+
+ public Long getCurrentSeq() {
+ return currentSeq;
+ }
+
+ public void setCurrentSeq(Long currentSeq) {
+ this.currentSeq = currentSeq;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+
+ public Date getCreatedTime() {
+ return createdTime;
+ }
+
+ public void setCreatedTime(Date createdTime) {
+ this.createdTime = createdTime;
+ }
+
+ public Date getUpdatedTime() {
+ return updatedTime;
+ }
+
+ public void setUpdatedTime(Date updatedTime) {
+ this.updatedTime = updatedTime;
+ }
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/domain/RtDailyProdState.java b/aucma-base/src/main/java/com/aucma/base/domain/RtDailyProdState.java
new file mode 100644
index 0000000..c2e25c4
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/domain/RtDailyProdState.java
@@ -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;
+ }
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/mapper/BaseBizCodeSequenceMapper.java b/aucma-base/src/main/java/com/aucma/base/mapper/BaseBizCodeSequenceMapper.java
new file mode 100644
index 0000000..f563099
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/mapper/BaseBizCodeSequenceMapper.java
@@ -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);
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/mapper/RtDailyProdStateMapper.java b/aucma-base/src/main/java/com/aucma/base/mapper/RtDailyProdStateMapper.java
new file mode 100644
index 0000000..439a2ea
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/mapper/RtDailyProdStateMapper.java
@@ -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);
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/service/IBusinessCodeGeneratorService.java b/aucma-base/src/main/java/com/aucma/base/service/IBusinessCodeGeneratorService.java
new file mode 100644
index 0000000..767ce02
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/service/IBusinessCodeGeneratorService.java
@@ -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);
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/service/IRtDailyProdStateService.java b/aucma-base/src/main/java/com/aucma/base/service/IRtDailyProdStateService.java
new file mode 100644
index 0000000..1f996c0
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/service/IRtDailyProdStateService.java
@@ -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);
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/service/impl/BusinessCodeGeneratorServiceImpl.java b/aucma-base/src/main/java/com/aucma/base/service/impl/BusinessCodeGeneratorServiceImpl.java
new file mode 100644
index 0000000..8e7e21e
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/service/impl/BusinessCodeGeneratorServiceImpl.java
@@ -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;
+ }
+ }
+}
diff --git a/aucma-base/src/main/java/com/aucma/base/service/impl/RtDailyProdStateServiceImpl.java b/aucma-base/src/main/java/com/aucma/base/service/impl/RtDailyProdStateServiceImpl.java
new file mode 100644
index 0000000..7263283
--- /dev/null
+++ b/aucma-base/src/main/java/com/aucma/base/service/impl/RtDailyProdStateServiceImpl.java
@@ -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();
+ }
+}
diff --git a/aucma-base/src/main/resources/mapper/base/BaseBizCodeSequenceMapper.xml b/aucma-base/src/main/resources/mapper/base/BaseBizCodeSequenceMapper.xml
new file mode 100644
index 0000000..77bdb77
--- /dev/null
+++ b/aucma-base/src/main/resources/mapper/base/BaseBizCodeSequenceMapper.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT seq_base_biz_code_seq.NEXTVAL as objId FROM DUAL
+
+ insert into base_biz_code_seq
+
+ obj_id,
+ biz_type,
+ current_period,
+ current_seq,
+ remark,
+ created_time,
+ updated_time,
+
+
+ #{objId},
+ #{bizType},
+ #{currentPeriod},
+ #{currentSeq},
+ #{remark},
+ #{createdTime},
+ #{updatedTime},
+
+
+
+
+ update base_biz_code_seq
+
+ current_period = #{currentPeriod},
+ current_seq = #{currentSeq},
+ remark = #{remark},
+ updated_time = #{updatedTime},
+
+ where obj_id = #{objId}
+
+
diff --git a/aucma-base/src/main/resources/mapper/base/RtDailyProdStateMapper.xml b/aucma-base/src/main/resources/mapper/base/RtDailyProdStateMapper.xml
new file mode 100644
index 0000000..bfe3901
--- /dev/null
+++ b/aucma-base/src/main/resources/mapper/base/RtDailyProdStateMapper.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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}
+ )
+
+
+
+ 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}
+
+