12 changed files with 792 additions and 64 deletions
@ -0,0 +1,121 @@ |
|||||||
|
package apelet.common.botp.service.execute; |
||||||
|
|
||||||
|
import apelet.common.botp.dao.ConvertRelationEntryMapper; |
||||||
|
import apelet.common.botp.dao.ConvertRelationMapper; |
||||||
|
import apelet.common.botp.model.ConvertRelation; |
||||||
|
import apelet.common.botp.model.ConvertRelationEntry; |
||||||
|
import apelet.common.botp.service.execute.entity.BillRelation; |
||||||
|
import apelet.common.botp.service.execute.entity.BillRelationEntry; |
||||||
|
import apelet.common.core.botp.BotpConvertRelationFacade; |
||||||
|
import apelet.common.core.object.TokenData; |
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
import org.springframework.transaction.annotation.Transactional; |
||||||
|
|
||||||
|
import java.util.Date; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* 转换关系补写实现。 |
||||||
|
* |
||||||
|
* <p><b>背景</b>:{@code convert_relation} / {@code convert_relation_entry} 原来只在 |
||||||
|
* {@code BotpService#transform} 的 autoSave 分支写入。{@code formStatus=2}(未落库、前端手动保存) |
||||||
|
* 时不写,保存后反写就找不到来源单。本实现负责在「保存落库之后」把关系补上。</p> |
||||||
|
* |
||||||
|
* <p><b>幂等</b>:按关联关系主键判重,重复保存不会写出重复关系。</p> |
||||||
|
* |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
@Component |
||||||
|
public class ConvertRelationFacade implements BotpConvertRelationFacade { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ConvertRelationMapper convertRelationMapper; |
||||||
|
|
||||||
|
@Autowired |
||||||
|
private ConvertRelationEntryMapper convertRelationEntryMapper; |
||||||
|
|
||||||
|
@Override |
||||||
|
@Transactional(rollbackFor = Exception.class) |
||||||
|
public void saveBillRelation(String billRelationJson, Long targetBillId) { |
||||||
|
if (StringUtils.isBlank(billRelationJson) || targetBillId == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
BillRelation billRelation; |
||||||
|
try { |
||||||
|
billRelation = JSON.parseObject(billRelationJson, BillRelation.class); |
||||||
|
} catch (Exception e) { |
||||||
|
log.warn("转换关系 JSON 解析失败,跳过补写。目标单id={}", targetBillId, e); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (billRelation == null || billRelation.getId() == null) { |
||||||
|
log.warn("转换关系缺少主键,跳过补写。目标单id={}", targetBillId); |
||||||
|
return; |
||||||
|
} |
||||||
|
// 幂等:同一张目标单可能被多次保存,关系已存在就不再写。
|
||||||
|
if (convertRelationMapper.selectById(billRelation.getId()) != null) { |
||||||
|
log.debug("转换关系已存在,跳过补写。关系id={},目标单id={}", billRelation.getId(), targetBillId); |
||||||
|
return; |
||||||
|
} |
||||||
|
ConvertRelation relation = new ConvertRelation(); |
||||||
|
relation.setId(billRelation.getId()); |
||||||
|
relation.setConvertRuleId(billRelation.getConvertRuleId()); |
||||||
|
relation.setSourceId(billRelation.getSourceId()); |
||||||
|
relation.setSourceNumber(billRelation.getSourceNumber()); |
||||||
|
// 目标单 id 以落库后的真实主键为准,覆盖下发的值。
|
||||||
|
relation.setTargetId(String.valueOf(targetBillId)); |
||||||
|
relation.setTargetNumber(billRelation.getTargetNumber()); |
||||||
|
fillAudit(relation); |
||||||
|
convertRelationMapper.insert(relation); |
||||||
|
|
||||||
|
List<BillRelationEntry> entryList = billRelation.getBillRelationEntryList(); |
||||||
|
if (entryList == null || entryList.isEmpty()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (BillRelationEntry billRelationEntry : entryList) { |
||||||
|
ConvertRelationEntry entry = new ConvertRelationEntry(); |
||||||
|
entry.setId(billRelationEntry.getId()); |
||||||
|
entry.setConvertRuleId(billRelationEntry.getConvertRuleId()); |
||||||
|
entry.setSourceEntryId(billRelationEntry.getSourceEntryId()); |
||||||
|
entry.setSourceEntryNumber(billRelationEntry.getSourceEntryNumber()); |
||||||
|
entry.setSourceId(billRelationEntry.getSourceId()); |
||||||
|
entry.setSourceNumber(billRelationEntry.getSourceNumber()); |
||||||
|
entry.setTargeEntryId(billRelationEntry.getTargeEntryId()); |
||||||
|
entry.setTargetId(String.valueOf(targetBillId)); |
||||||
|
entry.setTargeEntryNumber(billRelationEntry.getTargeEntryNumber()); |
||||||
|
entry.setTargetNumber(billRelationEntry.getTargetNumber()); |
||||||
|
fillAudit(entry); |
||||||
|
convertRelationEntryMapper.insert(entry); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 统一填审计字段。 |
||||||
|
* |
||||||
|
* @param target 待填对象。 |
||||||
|
*/ |
||||||
|
private void fillAudit(Object target) { |
||||||
|
Long userId = null; |
||||||
|
TokenData tokenData = TokenData.takeFromRequest(); |
||||||
|
if (tokenData != null) { |
||||||
|
userId = tokenData.getUserId(); |
||||||
|
} |
||||||
|
Date now = new Date(); |
||||||
|
if (target instanceof ConvertRelation) { |
||||||
|
ConvertRelation relation = (ConvertRelation) target; |
||||||
|
relation.setCreateUserId(userId); |
||||||
|
relation.setCreateTime(now); |
||||||
|
relation.setUpdateUserId(userId); |
||||||
|
relation.setUpdateTime(now); |
||||||
|
} else if (target instanceof ConvertRelationEntry) { |
||||||
|
ConvertRelationEntry entry = (ConvertRelationEntry) target; |
||||||
|
entry.setCreateUserId(userId); |
||||||
|
entry.setCreateTime(now); |
||||||
|
entry.setUpdateUserId(userId); |
||||||
|
entry.setUpdateTime(now); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
package apelet.common.core.botp; |
||||||
|
|
||||||
|
/** |
||||||
|
* 转换关系落库门面(依赖反转用)。 |
||||||
|
* |
||||||
|
* <p><b>为什么需要</b>:转换关系表 {@code convert_relation} / {@code convert_relation_entry} 只在 |
||||||
|
* BOTP 转换的 autoSave 分支写入。当 {@code formStatus=2}(未落库、由前端手动保存)时, |
||||||
|
* 转换阶段不会写关系,保存接口落库后必须补写,否则后续「保存即反写」「上查下查」都找不到来源单。</p> |
||||||
|
* |
||||||
|
* <p><b>为什么放在 common-core</b>:与 {@link BotpReverseWriteFacade} 同理——补写实现与实体都在 |
||||||
|
* common-botp,而补写时机在 common-online,直接依赖会成环。</p> |
||||||
|
* |
||||||
|
* <p>调用方应通过 {@link BotpReverseWriteSupport#saveRelationQuietly} 调用,未挂载 botp 时静默跳过。</p> |
||||||
|
* |
||||||
|
*/ |
||||||
|
public interface BotpConvertRelationFacade { |
||||||
|
|
||||||
|
/** |
||||||
|
* 按转换结果下发的关联关系补写转换关系表。 |
||||||
|
* |
||||||
|
* <p>已存在的关联关系(按关联关系主键判重)直接跳过,保证重复保存不会产生重复关系。</p> |
||||||
|
* |
||||||
|
* @param billRelationJson 单张目标单的关联关系 JSON 串,结构见 BillRelation。 |
||||||
|
* @param targetBillId 目标单落库后的真实主键,会覆盖 JSON 里的 targetId。 |
||||||
|
*/ |
||||||
|
void saveBillRelation(String billRelationJson, Long targetBillId); |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
-- ---------------------------- |
||||||
|
-- convert_relation_entry 补反写回滚所需的两个字段。 |
||||||
|
-- |
||||||
|
-- 背景:ConvertRelationEntry 实体上声明了 beforeValue / writebackRuleVersion, |
||||||
|
-- 但 DDL 没跟上,MyBatis-Plus 查询会带上这两列,导致 |
||||||
|
-- Unknown column 'before_value' in 'field list' |
||||||
|
-- 反写(WriteBackOrchestrator#loadRelationEntries)与转换落库都会直接报错。 |
||||||
|
-- |
||||||
|
-- 请在 tenant-admin 服务指向的默认数据库执行该脚本;脚本可重复执行。 |
||||||
|
-- ---------------------------- |
||||||
|
SET NAMES utf8mb4; |
||||||
|
SET FOREIGN_KEY_CHECKS = 0; |
||||||
|
|
||||||
|
-- ---------------------------- |
||||||
|
-- 1. before_value:反写前的源单字段原值。 |
||||||
|
-- 只有「覆盖」方式需要——累加/扣减天然可逆,覆盖把原值冲掉了,不记下来撤销时就回不去。 |
||||||
|
-- ---------------------------- |
||||||
|
SET @ddl = (SELECT IF(COUNT(*) = 0, |
||||||
|
'ALTER TABLE `convert_relation_entry` ADD COLUMN `before_value` decimal(24,8) DEFAULT NULL COMMENT ''反写前的源单字段原值,仅覆盖方式使用,供撤销回滚恢复''', |
||||||
|
'DO 0') |
||||||
|
FROM information_schema.columns |
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'convert_relation_entry' AND column_name = 'before_value'); |
||||||
|
PREPARE stmt FROM @ddl; |
||||||
|
EXECUTE stmt; |
||||||
|
DEALLOCATE PREPARE stmt; |
||||||
|
|
||||||
|
-- ---------------------------- |
||||||
|
-- 2. writeback_rule_version:本次反写实际使用的反写规则版本。 |
||||||
|
-- 撤销/弃审时按该版本取快照执行,保证「怎么加的就怎么退」。 |
||||||
|
-- ---------------------------- |
||||||
|
SET @ddl = (SELECT IF(COUNT(*) = 0, |
||||||
|
'ALTER TABLE `convert_relation_entry` ADD COLUMN `writeback_rule_version` int DEFAULT NULL COMMENT ''本次反写使用的反写规则版本,供撤销回滚按同一版本执行''', |
||||||
|
'DO 0') |
||||||
|
FROM information_schema.columns |
||||||
|
WHERE table_schema = DATABASE() AND table_name = 'convert_relation_entry' AND column_name = 'writeback_rule_version'); |
||||||
|
PREPARE stmt FROM @ddl; |
||||||
|
EXECUTE stmt; |
||||||
|
DEALLOCATE PREPARE stmt; |
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1; |
||||||
Loading…
Reference in new issue