✨ 组织机构支持合同初始流水号

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-21 11:51:13 +08:00
parent 8dd0fa3260
commit 777102485c
6 changed files with 63 additions and 4 deletions
@@ -150,6 +150,13 @@ public class Dept extends TenantEntity {
@Schema(description = "合同编码前缀")
private String contractCodePrefix;
/**
* 合同初始流水号(该部门当年首个合同时从此号起编,范围1-999)
*/
@Schema(description = "合同初始流水号")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Integer contractInitialSerial;
/**
* 组织简称
*/
@@ -14,6 +14,7 @@
<result column="dept_code" property="deptCode"/>
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
<result column="contract_code_prefix" property="contractCodePrefix"/>
<result column="contract_initial_serial" property="contractInitialSerial"/>
<result column="short_name" property="shortName"/>
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
<result column="mnemonic_code" property="mnemonicCode"/>
@@ -36,6 +37,7 @@
<result column="dept_code" property="deptCode"/>
<result column="kingdee_dept_code" property="kingdeeDeptCode"/>
<result column="contract_code_prefix" property="contractCodePrefix"/>
<result column="contract_initial_serial" property="contractInitialSerial"/>
<result column="short_name" property="shortName"/>
<result column="pinyin_mnemonic" property="pinyinMnemonic"/>
<result column="mnemonic_code" property="mnemonicCode"/>
@@ -687,6 +687,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
}
validateDeptCategory(dept);
validateDeptCode(dept, parent);
validateContractInitialSerial(dept);
return saveOrUpdate(dept);
}
@@ -874,6 +875,16 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
}
}
private void validateContractInitialSerial(Dept dept) {
Integer serial = dept.getContractInitialSerial();
if (serial == null) {
return;
}
if (serial < 1 || serial > 999) {
throw new ServiceException("合同初始流水号范围为1到999");
}
}
private void validateDeptCode(Dept dept, Dept parent) {
String deptCode = dept.getDeptCode();
// 组织编码非必填,为空时存 null,避免唯一索引冲突
@@ -298,6 +298,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
validateBillingPlans(request.getBillingPlanJson());
validatePaymentRatios(request.getPaymentRatioJson());
Map<String, Object> beforeData = buildContractChangeSnapshot(source);
applyContractNo(source, request.getContractNo(), false);
source.setContractName(request.getContractName()); source.setPartyB(request.getPartyB());
source.setStartDate(request.getStartDate()); source.setEndDate(request.getEndDate()); source.setContractFormat(request.getContractFormat());
source.setSettlementMode(request.getSettlementMode()); source.setLegalSealFlag(request.getLegalSealFlag()); source.setCopyCount(request.getCopyCount());
@@ -507,8 +508,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setOrganizationId(TransportBusinessSupport.currentDeptId("合同管理"));
contractManage.setOrganizationName(TransportBusinessSupport.currentDept("合同管理").getDeptName());
}
// 新增始终按部门规则生成合同编号,避免前端预览号并发冲突
contractManage.setContractNo(nextContractNo(contractManage.getOrganizationId()));
applyContractNo(contractManage, contractManage.getContractNo(), true);
if (Func.isEmpty(contractManage.getHandlerUserId())) {
contractManage.setHandlerUserId(AuthUtil.getUserId());
contractManage.setHandlerUserName(AuthUtil.getUserName());
@@ -516,7 +516,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setArchiveStatus(ARCHIVE_STATUS_PENDING);
} else {
ContractManage oldRecord = loadExists(contractManage.getId());
contractManage.setContractNo(oldRecord.getContractNo());
applyContractNo(contractManage, contractManage.getContractNo(), false);
if (Func.isEmpty(contractManage.getOrganizationId())) {
contractManage.setOrganizationId(oldRecord.getOrganizationId());
contractManage.setOrganizationName(oldRecord.getOrganizationName());
@@ -535,6 +535,28 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
}
}
/**
* 保存合同编号。新增未填写时按部门规则生成,已填写或编辑时保留手工编号并校验唯一。
*/
private void applyContractNo(ContractManage contractManage, String contractNo, boolean generateWhenEmpty) {
String normalizedContractNo = TransportBusinessSupport.trimToNull(contractNo);
if (Func.isEmpty(normalizedContractNo)) {
if (!generateWhenEmpty) {
throw new ServiceException("请输入合同编号");
}
normalizedContractNo = nextContractNo(contractManage.getOrganizationId());
}
TransportBusinessSupport.validateLength(normalizedContractNo, 100, "合同编号不能超过100字");
Long duplicatedCount = count(Wrappers.<ContractManage>lambdaQuery()
.eq(ContractManage::getIsDeleted, 0)
.eq(ContractManage::getContractNo, normalizedContractNo)
.ne(Func.isNotEmpty(contractManage.getId()), ContractManage::getId, contractManage.getId()));
if (duplicatedCount != null && duplicatedCount > 0) {
throw new ServiceException("合同编号已存在");
}
contractManage.setContractNo(normalizedContractNo);
}
@Override
public synchronized String nextContractNo(Long organizationId) {
if (Func.isEmpty(organizationId)) {
@@ -558,7 +580,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
.orderByDesc(ContractManage::getContractNo)
.last("LIMIT 50"));
Pattern pattern = Pattern.compile("^" + Pattern.quote(prefix) + "(" + year + ")(\\d{3})号$");
int next = 1;
// 当年尚无匹配编号时,从组织配置的初始流水号起编;否则在已有最大流水号上加1
int next = resolveContractInitialSerial(dept);
for (ContractManage item : latestList) {
String contractNo = TransportBusinessSupport.trimToNull(item.getContractNo());
if (Func.isEmpty(contractNo)) {
@@ -577,6 +600,17 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
return head + String.format("%03d", next) + "号";
}
private int resolveContractInitialSerial(Dept dept) {
Integer serial = dept.getContractInitialSerial();
if (serial == null || serial < 1) {
return 1;
}
if (serial > 999) {
throw new ServiceException("组织机构中配置的合同初始流水号不能超过999");
}
return serial;
}
private void refreshArchiveStatusOnApprove(ContractManage contractManage) {
if (hasSealArchiveFile(contractManage.getContractFileJson())) {
contractManage.setArchiveStatus(ARCHIVE_STATUS_ARCHIVED);
@@ -1015,6 +1049,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private Map<String, Object> buildContractChangeSnapshot(ContractManage contractManage) {
Map<String, Object> data = new LinkedHashMap<>();
data.put("contractNo", contractManage.getContractNo());
data.put("contractName", contractManage.getContractName());
data.put("partyB", contractManage.getPartyB());
data.put("startDate", contractManage.getStartDate());
@@ -305,6 +305,7 @@ CREATE TABLE `blade_dept` (
`dept_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织编码',
`kingdee_dept_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '金蝶部门代码',
`contract_code_prefix` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '合同编码前缀',
`contract_initial_serial` int NULL DEFAULT NULL COMMENT '合同初始流水号',
`short_name` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织简称',
`pinyin_mnemonic` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拼音助记码',
`mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码',
@@ -0,0 +1,3 @@
-- 组织机构新增合同初始流水号:该部门当年首个合同从此流水号起编,后续递增
ALTER TABLE `blade_dept`
ADD COLUMN `contract_initial_serial` int(11) DEFAULT NULL COMMENT '合同初始流水号' AFTER `contract_code_prefix`;