package jnpf.elnService.impl;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.audit.sdk.AuditClient;
|
import jnpf.database.util.TenantDataSourceUtil;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordForms;
|
import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordVos;
|
import jnpf.elnEntity.template.ElnTemplateVos;
|
import jnpf.elnMapper.ElnRecordMapper;
|
import jnpf.elnMapper.ElnRecordRepeatRowMapper;
|
import jnpf.elnMapper.ElnRecordRevisionMapper;
|
import jnpf.elnService.ElnRecordFillAccessService;
|
import jnpf.elnService.ElnRecordRepeatService;
|
import jnpf.elnService.ElnRecordFillTicketService;
|
import jnpf.elnService.template.ElnTemplateRepeatGroupService;
|
import jnpf.exception.DataException;
|
import jnpf.util.RandomUtil;
|
import jnpf.util.JsonUtil;
|
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.util.StringUtils;
|
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.function.Consumer;
|
|
@Service
|
public class ElnRecordRepeatServiceImpl implements ElnRecordRepeatService {
|
private static final String PREPARED = "PREPARED";
|
private static final String ACTIVE = "ACTIVE";
|
private static final String DELETE_PREPARED = "DELETE_PREPARED";
|
private static final int MAX_OPERATION_CODE_LENGTH = 64;
|
private static final int MAX_DELETE_REASON_LENGTH = 500;
|
|
private final ElnRecordMapper recordMapper;
|
private final ElnRecordRevisionMapper revisionMapper;
|
private final ElnRecordRepeatRowMapper rowMapper;
|
private final ElnTemplateRepeatGroupService repeatGroupService;
|
private final ElnRecordFillAccessService accessService;
|
private final Consumer<AuditEventDTO> auditRecorder;
|
private final TransactionTemplate transactionTemplate;
|
private final TenantRoute tenantRoute;
|
|
@Autowired
|
public ElnRecordRepeatServiceImpl(ElnRecordMapper recordMapper,
|
ElnRecordRevisionMapper revisionMapper,
|
ElnRecordRepeatRowMapper rowMapper,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
ElnRecordFillAccessService accessService,
|
PlatformTransactionManager transactionManager,
|
ObjectProvider<AuditClient> auditClientProvider) {
|
this(recordMapper, revisionMapper, rowMapper, repeatGroupService, accessService,
|
transactionManager, event -> {
|
AuditClient client = auditClientProvider.getIfAvailable();
|
if (client != null) client.record(event);
|
}, new TenantRoute() {
|
@Override public void switchTo(String tenantId) {
|
TenantDataSourceUtil.switchTenant(tenantId);
|
}
|
@Override public void clear() {
|
TenantDataSourceUtil.clearLocalTenantInfo();
|
}
|
});
|
}
|
|
ElnRecordRepeatServiceImpl(ElnRecordMapper recordMapper,
|
ElnRecordRevisionMapper revisionMapper,
|
ElnRecordRepeatRowMapper rowMapper,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
ElnRecordFillAccessService accessService,
|
PlatformTransactionManager transactionManager,
|
Consumer<AuditEventDTO> auditRecorder,
|
TenantRoute tenantRoute) {
|
this.recordMapper = recordMapper;
|
this.revisionMapper = revisionMapper;
|
this.rowMapper = rowMapper;
|
this.repeatGroupService = repeatGroupService;
|
this.accessService = accessService;
|
this.auditRecorder = auditRecorder;
|
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
this.tenantRoute = tenantRoute;
|
}
|
|
@Override
|
public ElnRecordVos.RepeatRow append(ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatAppend form) {
|
requireRequest(ticket, form);
|
if (!accessService.canFill(ticket.getTenantId(), ticket.getUserId(),
|
ticket.getBizDataId(), ticket.getFileId())) {
|
throw new DataException("当前用户或记录状态不允许新增重复行");
|
}
|
try {
|
tenantRoute.switchTo(ticket.getTenantId());
|
return transactionTemplate.execute(status -> appendInTenant(ticket, form));
|
} finally {
|
tenantRoute.clear();
|
}
|
}
|
|
private ElnRecordVos.RepeatRow appendInTenant(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatAppend form) {
|
String operationCode = form.getOperationCode().trim();
|
String groupCode = form.getGroupCode().trim();
|
Current current = lockCurrent(ticket);
|
ElnRecordEntity record = current.record;
|
ElnRecordRevisionEntity revision = current.revision;
|
ElnTemplateVos.RepeatGroup group = requireTemplateGroup(
|
ticket.getTenantId(), record.getMubanId(), groupCode);
|
ElnRecordRepeatRowEntity retried = findByOperation(
|
ticket.getTenantId(), revision.getId(), operationCode);
|
if (retried != null) {
|
if (!Objects.equals(groupCode, retried.getRepeatGroupCode())) {
|
throw new DataException("重复行操作码已用于其他请求");
|
}
|
return toRow(retried);
|
}
|
Long allocated = rowMapper.selectCount(
|
Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, ticket.getTenantId())
|
.eq(ElnRecordRepeatRowEntity::getDianziJiluId, record.getId())
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, revision.getId())
|
.eq(ElnRecordRepeatRowEntity::getRepeatGroupCode, groupCode)
|
.in(ElnRecordRepeatRowEntity::getOperationStatus,
|
ACTIVE, PREPARED, DELETE_PREPARED)
|
.and(q -> q.isNull(ElnRecordRepeatRowEntity::getFDeleteMark)
|
.or().eq(ElnRecordRepeatRowEntity::getFDeleteMark, 0)));
|
if (allocated != null && allocated >= group.getMaxRows()) {
|
throw new DataException("重复组已达到最大行数 " + group.getMaxRows());
|
}
|
ElnRecordRepeatRowEntity last = rowMapper.selectOne(
|
Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, ticket.getTenantId())
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, revision.getId())
|
.eq(ElnRecordRepeatRowEntity::getRepeatGroupCode, groupCode)
|
.orderByDesc(ElnRecordRepeatRowEntity::getRowOrder)
|
.last("limit 1"));
|
String rowId = RandomUtil.uuId();
|
ElnRecordRepeatRowEntity created = new ElnRecordRepeatRowEntity();
|
created.setId(RandomUtil.uuId());
|
created.setRowId(rowId);
|
created.setDianziJiluId(record.getId());
|
created.setXiudingId(revision.getId());
|
created.setMubanId(record.getMubanId());
|
created.setRepeatGroupCode(groupCode);
|
created.setRowOrder(last == null ? 1 : last.getRowOrder() + 1);
|
created.setOperationCode(operationCode);
|
created.setOperationStatus(PREPARED);
|
created.setFTenantId(ticket.getTenantId());
|
created.setFVersion(0);
|
created.setFCreatorUserId(ticket.getUserId());
|
created.setFCreatorTime(new Date());
|
created.setFLastModifyUserId(ticket.getUserId());
|
created.setFLastModifyTime(new Date());
|
created.setFDeleteMark(0);
|
try {
|
if (rowMapper.insert(created) != 1) {
|
throw new DataException("重复行身份创建失败");
|
}
|
} catch (DuplicateKeyException e) {
|
ElnRecordRepeatRowEntity concurrent = findByOperation(
|
ticket.getTenantId(), revision.getId(), operationCode);
|
if (concurrent == null || !Objects.equals(groupCode, concurrent.getRepeatGroupCode())) {
|
throw new DataException("重复行新增发生并发冲突");
|
}
|
return toRow(concurrent);
|
}
|
audit(ticket, record, created, created.getOperationCode(),
|
"ELN_RECORD_REPEAT_APPEND_PREPARE",
|
"分配电子记录重复行");
|
return toRow(created);
|
}
|
|
@Override
|
public ElnRecordVos.RepeatRow requestDelete(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatDelete form) {
|
requireDeleteRequest(ticket, form);
|
if (!accessService.canFill(ticket.getTenantId(), ticket.getUserId(),
|
ticket.getBizDataId(), ticket.getFileId())) {
|
throw new DataException("当前用户或记录状态不允许删除重复行");
|
}
|
try {
|
tenantRoute.switchTo(ticket.getTenantId());
|
return transactionTemplate.execute(status -> requestDeleteInTenant(ticket, form));
|
} finally {
|
tenantRoute.clear();
|
}
|
}
|
|
private ElnRecordVos.RepeatRow requestDeleteInTenant(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatDelete form) {
|
String groupCode = form.getGroupCode().trim();
|
String rowId = form.getRowId().trim();
|
String operationCode = form.getOperationCode().trim();
|
String reason = form.getReason().trim();
|
Current current = lockCurrent(ticket);
|
ElnTemplateVos.RepeatGroup group = requireTemplateGroup(ticket.getTenantId(),
|
current.record.getMubanId(), groupCode);
|
ElnRecordRepeatRowEntity replay = findByDeleteOperation(ticket.getTenantId(),
|
current.revision.getId(), operationCode);
|
if (replay != null) {
|
if (!Objects.equals(groupCode, replay.getRepeatGroupCode())
|
|| !Objects.equals(rowId, replay.getRowId())
|
|| !Objects.equals(reason, replay.getDeleteReason())) {
|
throw new DataException("重复行删除操作码已用于其他请求");
|
}
|
return toRow(replay);
|
}
|
ElnRecordRepeatRowEntity row = findRow(ticket.getTenantId(),
|
current.revision.getId(), groupCode, rowId);
|
if (row == null || !Objects.equals(current.record.getId(), row.getDianziJiluId())
|
|| !Objects.equals(current.record.getMubanId(), row.getMubanId())) {
|
throw new DataException("目标重复行不存在");
|
}
|
if (DELETE_PREPARED.equals(row.getOperationStatus())) {
|
throw new DataException("重复行已有待完成的删除意图");
|
}
|
if (!ACTIVE.equals(row.getOperationStatus())) {
|
throw new DataException("只有已激活的重复行可以删除");
|
}
|
Long activeRows = rowMapper.selectCount(
|
Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, ticket.getTenantId())
|
.eq(ElnRecordRepeatRowEntity::getDianziJiluId, current.record.getId())
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, current.revision.getId())
|
.eq(ElnRecordRepeatRowEntity::getRepeatGroupCode, groupCode)
|
.eq(ElnRecordRepeatRowEntity::getOperationStatus, ACTIVE)
|
.and(q -> q.isNull(ElnRecordRepeatRowEntity::getFDeleteMark)
|
.or().eq(ElnRecordRepeatRowEntity::getFDeleteMark, 0)));
|
if (activeRows == null || activeRows - 1 < group.getMinRows()) {
|
throw new DataException("重复组至少保留 " + group.getMinRows() + " 行");
|
}
|
Date now = new Date();
|
row.setOperationStatus(DELETE_PREPARED);
|
row.setDeleteOperationCode(operationCode);
|
row.setDeleteReason(reason);
|
row.setDeleteRequestedUserId(ticket.getUserId());
|
row.setDeleteRequestedTime(now);
|
row.setFLastModifyUserId(ticket.getUserId());
|
row.setFLastModifyTime(now);
|
if (rowMapper.updateById(row) != 1) {
|
throw new DataException("重复行删除意图登记冲突");
|
}
|
audit(ticket, current.record, row, operationCode,
|
"ELN_RECORD_REPEAT_DELETE_PREPARE", "登记电子记录重复行删除意图");
|
return toRow(row);
|
}
|
|
@Override
|
public ElnRecordVos.RepeatRow restoreDelete(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatRestore form) {
|
requireRestoreRequest(ticket, form);
|
if (!accessService.canFill(ticket.getTenantId(), ticket.getUserId(),
|
ticket.getBizDataId(), ticket.getFileId())) {
|
throw new DataException("当前用户或记录状态不允许恢复重复行");
|
}
|
try {
|
tenantRoute.switchTo(ticket.getTenantId());
|
return transactionTemplate.execute(status -> restoreDeleteInTenant(ticket, form));
|
} finally {
|
tenantRoute.clear();
|
}
|
}
|
|
private ElnRecordVos.RepeatRow restoreDeleteInTenant(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatRestore form) {
|
String groupCode = form.getGroupCode().trim();
|
String rowId = form.getRowId().trim();
|
String operationCode = form.getOperationCode().trim();
|
Current current = lockCurrent(ticket);
|
requireTemplateGroup(ticket.getTenantId(), current.record.getMubanId(), groupCode);
|
ElnRecordRepeatRowEntity row = findRow(ticket.getTenantId(),
|
current.revision.getId(), groupCode, rowId);
|
if (row == null || !Objects.equals(current.record.getId(), row.getDianziJiluId())
|
|| !Objects.equals(current.record.getMubanId(), row.getMubanId())) {
|
throw new DataException("目标重复行不存在");
|
}
|
if (ACTIVE.equals(row.getOperationStatus())) return toRow(row);
|
if (!DELETE_PREPARED.equals(row.getOperationStatus())
|
|| !Objects.equals(operationCode, row.getDeleteOperationCode())) {
|
throw new DataException("重复行删除恢复操作码不匹配");
|
}
|
row.setOperationStatus(ACTIVE);
|
row.setDeleteOperationCode(null);
|
row.setDeleteReason(null);
|
row.setDeleteRequestedUserId(null);
|
row.setDeleteRequestedTime(null);
|
row.setFLastModifyUserId(ticket.getUserId());
|
row.setFLastModifyTime(new Date());
|
if (rowMapper.updateById(row) != 1) {
|
throw new DataException("重复行删除恢复冲突");
|
}
|
audit(ticket, current.record, row, operationCode,
|
"ELN_RECORD_REPEAT_DELETE_RESTORE", "恢复电子记录重复行删除意图");
|
return toRow(row);
|
}
|
|
private Current lockCurrent(ElnRecordFillTicketService.FillTicketContext ticket) {
|
ElnRecordEntity record = recordMapper.selectOne(
|
Wrappers.<ElnRecordEntity>lambdaQuery()
|
.eq(ElnRecordEntity::getFTenantId, ticket.getTenantId())
|
.eq(ElnRecordEntity::getId, ticket.getBizDataId())
|
.and(q -> q.isNull(ElnRecordEntity::getFDeleteMark)
|
.or().eq(ElnRecordEntity::getFDeleteMark, 0))
|
.last("for update"));
|
if (record == null || !StringUtils.hasText(record.getDangqianXiudingId())) {
|
throw new DataException("电子记录不存在或当前修订无效");
|
}
|
ElnRecordRevisionEntity revision = revisionMapper.selectOne(
|
Wrappers.<ElnRecordRevisionEntity>lambdaQuery()
|
.eq(ElnRecordRevisionEntity::getFTenantId, ticket.getTenantId())
|
.eq(ElnRecordRevisionEntity::getId, record.getDangqianXiudingId())
|
.eq(ElnRecordRevisionEntity::getDianziJiluId, record.getId())
|
.eq(ElnRecordRevisionEntity::getJiluWendang, ticket.getFileId())
|
.eq(ElnRecordRevisionEntity::getBizStatus, "DRAFT")
|
.and(q -> q.isNull(ElnRecordRevisionEntity::getFDeleteMark)
|
.or().eq(ElnRecordRevisionEntity::getFDeleteMark, 0))
|
.last("limit 1"));
|
if (revision == null) {
|
throw new DataException("电子记录当前修订与文件不匹配");
|
}
|
return new Current(record, revision);
|
}
|
|
private ElnTemplateVos.RepeatGroup requireTemplateGroup(String tenantId,
|
String templateId, String requestedGroup) {
|
List<ElnTemplateVos.RepeatGroup> groups = repeatGroupService.list(tenantId, templateId);
|
for (ElnTemplateVos.RepeatGroup group : groups) {
|
if (Objects.equals(requestedGroup, group.getGroupCode())) return group;
|
}
|
throw new DataException("模板没有目标重复组配置");
|
}
|
|
private void audit(ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordEntity record, ElnRecordRepeatRowEntity row,
|
String operationCode, String actionCode, String actionLabel) {
|
Map<String, Object> snapshot = new LinkedHashMap<>();
|
snapshot.put("groupCode", row.getRepeatGroupCode());
|
snapshot.put("rowId", row.getRowId());
|
snapshot.put("rowOrder", row.getRowOrder());
|
snapshot.put("status", row.getOperationStatus());
|
snapshot.put("deleteReason", row.getDeleteReason());
|
auditRecorder.accept(AuditEventDTO.builder()
|
.clientEventId(ElnRepeatAuditEventIds.of(operationCode, actionCode))
|
.operationId(operationCode)
|
.eventTime(new Date())
|
.tenantId(ticket.getTenantId())
|
.operatorId(ticket.getUserId())
|
.eventType("DATA_CHANGE")
|
.actionCode(actionCode)
|
.actionLabel(actionLabel)
|
.bizModule("eln")
|
.bizType("eln_record_repeat_row")
|
.bizCode(record.getJiluBianhao())
|
.targetTable("eln_record_repeat_row")
|
.targetId(row.getId())
|
.dataSnapshot(JsonUtil.getObjectToString(snapshot))
|
.build());
|
}
|
|
private ElnRecordRepeatRowEntity findByOperation(
|
String tenantId, String revisionId, String operationCode) {
|
return rowMapper.selectOne(Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, revisionId)
|
.eq(ElnRecordRepeatRowEntity::getOperationCode, operationCode)
|
.and(q -> q.isNull(ElnRecordRepeatRowEntity::getFDeleteMark)
|
.or().eq(ElnRecordRepeatRowEntity::getFDeleteMark, 0))
|
.last("limit 1"));
|
}
|
|
private ElnRecordRepeatRowEntity findByDeleteOperation(
|
String tenantId, String revisionId, String operationCode) {
|
return rowMapper.selectOne(Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, revisionId)
|
.eq(ElnRecordRepeatRowEntity::getDeleteOperationCode, operationCode)
|
.last("limit 1"));
|
}
|
|
private ElnRecordRepeatRowEntity findRow(String tenantId, String revisionId,
|
String groupCode, String rowId) {
|
return rowMapper.selectOne(Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, revisionId)
|
.eq(ElnRecordRepeatRowEntity::getRepeatGroupCode, groupCode)
|
.eq(ElnRecordRepeatRowEntity::getRowId, rowId)
|
.and(q -> q.isNull(ElnRecordRepeatRowEntity::getFDeleteMark)
|
.or().eq(ElnRecordRepeatRowEntity::getFDeleteMark, 0))
|
.last("limit 1"));
|
}
|
|
private static void requireRequest(ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatAppend form) {
|
if (ticket == null || !ElnRecordFillTicketService.BIZ_MODULE.equals(ticket.getBizModule())
|
|| form == null || !StringUtils.hasText(form.getOperationCode())
|
|| !StringUtils.hasText(form.getGroupCode())) {
|
throw new DataException("重复行新增请求无效");
|
}
|
if (form.getOperationCode().trim().length() > MAX_OPERATION_CODE_LENGTH) {
|
throw new DataException("重复行操作码超过 64 个字符");
|
}
|
if (!form.getGroupCode().trim().matches("[A-Za-z0-9_-]{1,80}")) {
|
throw new DataException("重复组编码无效");
|
}
|
}
|
|
private static void requireDeleteRequest(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatDelete form) {
|
if (form == null || !StringUtils.hasText(form.getReason())) {
|
throw new DataException("重复行删除请求无效");
|
}
|
requireRowRequest(ticket, form.getOperationCode(), form.getGroupCode(),
|
form.getRowId(), "重复行删除请求无效");
|
if (form.getReason().trim().length() > MAX_DELETE_REASON_LENGTH) {
|
throw new DataException("重复行删除原因超过 500 个字符");
|
}
|
}
|
|
private static void requireRestoreRequest(
|
ElnRecordFillTicketService.FillTicketContext ticket,
|
ElnRecordForms.RepeatRestore form) {
|
if (form == null) throw new DataException("重复行删除恢复请求无效");
|
requireRowRequest(ticket, form.getOperationCode(), form.getGroupCode(),
|
form.getRowId(), "重复行删除恢复请求无效");
|
}
|
|
private static void requireRowRequest(
|
ElnRecordFillTicketService.FillTicketContext ticket, String operationCode,
|
String groupCode, String rowId, String invalidMessage) {
|
if (ticket == null || !ElnRecordFillTicketService.BIZ_MODULE.equals(ticket.getBizModule())
|
|| !StringUtils.hasText(operationCode) || !StringUtils.hasText(groupCode)
|
|| !StringUtils.hasText(rowId)) {
|
throw new DataException(invalidMessage);
|
}
|
if (operationCode.trim().length() > MAX_OPERATION_CODE_LENGTH) {
|
throw new DataException("重复行操作码超过 64 个字符");
|
}
|
if (!groupCode.trim().matches("[A-Za-z0-9_-]{1,80}")
|
|| !rowId.trim().matches("[A-Za-z0-9_-]{1,80}")
|
|| "prototype".equals(rowId.trim())) {
|
throw new DataException("重复行身份无效");
|
}
|
}
|
|
private static ElnRecordVos.RepeatRow toRow(ElnRecordRepeatRowEntity source) {
|
ElnRecordVos.RepeatRow result = new ElnRecordVos.RepeatRow();
|
result.setRowId(source.getRowId());
|
result.setGroupCode(source.getRepeatGroupCode());
|
result.setRowOrder(source.getRowOrder());
|
result.setStatus(source.getOperationStatus());
|
result.setDeleteOperationCode(source.getDeleteOperationCode());
|
result.setDeleteReason(source.getDeleteReason());
|
return result;
|
}
|
|
private static final class Current {
|
private final ElnRecordEntity record;
|
private final ElnRecordRevisionEntity revision;
|
|
private Current(ElnRecordEntity record, ElnRecordRevisionEntity revision) {
|
this.record = record;
|
this.revision = revision;
|
}
|
}
|
|
interface TenantRoute {
|
void switchTo(String tenantId);
|
void clear();
|
}
|
}
|