feat: 超管端实现租户重置密码功能
后端实现:
- TenantService 添加 resetPasswordAndReturnTemp 方法
- TenantServiceImpl 实现重置密码逻辑,生成 8 位随机临时密码
- AdminTenantController 完善 resetTenantPassword 接口,返回临时密码
前端实现:
- TenantListView 添加重置密码模态框组件
- 采用与教师端一致的 UI 样式
- 使用超管端 Indigo 紫色主题色 (#6366F1)
- 支持密码一键复制功能
API 端点:
- POST /api/v1/admin/tenants/{id}/reset-password
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1b1679585d
commit
48c64176e5
@ -91,6 +91,7 @@ export interface UpdateTenantDto {
|
|||||||
startDate?: string;
|
startDate?: string;
|
||||||
expireDate?: string;
|
expireDate?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
forceRemove?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateTenantQuotaDto {
|
export interface UpdateTenantQuotaDto {
|
||||||
|
|||||||
@ -102,7 +102,6 @@
|
|||||||
<a-menu-divider />
|
<a-menu-divider />
|
||||||
<a-menu-item @click="handleQuota(record)">调整配额</a-menu-item>
|
<a-menu-item @click="handleQuota(record)">调整配额</a-menu-item>
|
||||||
<a-menu-item @click="handleResetPassword(record)">重置密码</a-menu-item>
|
<a-menu-item @click="handleResetPassword(record)">重置密码</a-menu-item>
|
||||||
<a-menu-divider />
|
|
||||||
<a-menu-item v-if="record.status === 'ACTIVE'" @click="handleUpdateStatus(record, 'SUSPENDED')">
|
<a-menu-item v-if="record.status === 'ACTIVE'" @click="handleUpdateStatus(record, 'SUSPENDED')">
|
||||||
暂停服务
|
暂停服务
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@ -207,6 +206,38 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 强制移除确认弹窗 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="forceRemoveModalVisible"
|
||||||
|
title="确认移除套餐"
|
||||||
|
:confirm-loading="modalLoading"
|
||||||
|
ok-type="primary"
|
||||||
|
ok-text="确认移除"
|
||||||
|
cancel-text="取消"
|
||||||
|
@ok="handleForceRemoveConfirm"
|
||||||
|
@cancel="handleForceRemoveCancel"
|
||||||
|
>
|
||||||
|
<div style="color: #fa8c16; font-size: 14px; margin-bottom: 16px">
|
||||||
|
<ExclamationCircleOutlined style="margin-right: 8px" />
|
||||||
|
以下套餐下有排课计划,移除后将无法继续为该套餐下的课程排课:
|
||||||
|
</div>
|
||||||
|
<div style="max-height: 300px; overflow-y: auto">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in forceRemoveWarnings"
|
||||||
|
:key="index"
|
||||||
|
style="padding: 12px; border: 1px solid #d9d9d9; border-radius: 4px; margin-bottom: 8px"
|
||||||
|
>
|
||||||
|
<div style="font-weight: 500">{{ item.collectionName }}</div>
|
||||||
|
<div style="font-size: 13px; color: #666; margin-top: 4px">
|
||||||
|
排课数量:{{ item.scheduleCount }} 个
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 16px; color: #999; font-size: 13px">
|
||||||
|
已存在的排课计划不受影响,但将无法新增该套餐下课程包的排课
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
<!-- 详情抽屉 -->
|
<!-- 详情抽屉 -->
|
||||||
<a-drawer v-model:open="drawerVisible" title="租户详情" width="600" :destroy-on-close="true">
|
<a-drawer v-model:open="drawerVisible" title="租户详情" width="600" :destroy-on-close="true">
|
||||||
<template v-if="detailData">
|
<template v-if="detailData">
|
||||||
@ -275,6 +306,28 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-skeleton v-else active />
|
<a-skeleton v-else active />
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
|
|
||||||
|
<!-- 重置密码确认模态框 -->
|
||||||
|
<a-modal v-model:open="resetPasswordVisible" @ok="confirmResetPassword" :confirm-loading="resetting" :width="400">
|
||||||
|
<template #title>
|
||||||
|
<span class="modal-title">
|
||||||
|
<KeyOutlined class="modal-title-icon" />
|
||||||
|
重置密码
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<div class="reset-password-content">
|
||||||
|
<div class="reset-warning">
|
||||||
|
<WarningOutlined class="warning-icon" />
|
||||||
|
<p>确定要重置 <strong>{{ currentTenant?.name }}</strong> 的密码吗?</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="newPassword" class="new-password-box">
|
||||||
|
<p>新密码:</p>
|
||||||
|
<div class="password-display">
|
||||||
|
<a-typography-text copyable>{{ newPassword }}</a-typography-text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -284,6 +337,9 @@ import {
|
|||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
KeyOutlined,
|
||||||
|
WarningOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { message, Modal } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
import type { TableProps, FormInstance } from 'ant-design-vue';
|
import type { TableProps, FormInstance } from 'ant-design-vue';
|
||||||
@ -356,6 +412,10 @@ const modalLoading = ref(false);
|
|||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
const formRef = ref<FormInstance>();
|
const formRef = ref<FormInstance>();
|
||||||
const editingId = ref<number | null>(null);
|
const editingId = ref<number | null>(null);
|
||||||
|
// 强制移除确认相关
|
||||||
|
const forceRemoveModalVisible = ref(false);
|
||||||
|
const forceRemoveWarnings = ref<Array<{ collectionId: number; collectionName: string; scheduleCount: number }>>([]);
|
||||||
|
const pendingFormData = ref<any>(null);
|
||||||
|
|
||||||
const formData = reactive<CreateTenantDto & { dateRange?: [string, string]; collectionIds?: number[] }>({
|
const formData = reactive<CreateTenantDto & { dateRange?: [string, string]; collectionIds?: number[] }>({
|
||||||
name: '',
|
name: '',
|
||||||
@ -425,6 +485,11 @@ const detailData = ref<TenantDetail | null>(null);
|
|||||||
// 套餐列表
|
// 套餐列表
|
||||||
const packageList = ref<CourseCollectionResponse[]>([]);
|
const packageList = ref<CourseCollectionResponse[]>([]);
|
||||||
|
|
||||||
|
// 重置密码相关
|
||||||
|
const resetPasswordVisible = ref(false);
|
||||||
|
const resetting = ref(false);
|
||||||
|
const newPassword = ref('');
|
||||||
|
|
||||||
// 禁用过去的日期(有效期不能选今天之前的日期)
|
// 禁用过去的日期(有效期不能选今天之前的日期)
|
||||||
const disabledPastDate = (current: dayjs.Dayjs) => {
|
const disabledPastDate = (current: dayjs.Dayjs) => {
|
||||||
return current && current < dayjs().startOf('day');
|
return current && current < dayjs().startOf('day');
|
||||||
@ -560,6 +625,25 @@ const handleModalOk = async () => {
|
|||||||
modalVisible.value = false;
|
modalVisible.value = false;
|
||||||
loadData();
|
loadData();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
// 处理错误码 3102 - 套餐下有排课计划
|
||||||
|
if (error.response?.data?.code === 3102) {
|
||||||
|
const warnings = error.response.data.data as Array<{
|
||||||
|
collectionId: number;
|
||||||
|
collectionName: string;
|
||||||
|
scheduleCount: number;
|
||||||
|
}>;
|
||||||
|
// 保存当前表单数据
|
||||||
|
pendingFormData.value = {
|
||||||
|
...formData,
|
||||||
|
startDate,
|
||||||
|
expireDate,
|
||||||
|
};
|
||||||
|
// 显示强制移除确认弹窗
|
||||||
|
forceRemoveWarnings.value = warnings;
|
||||||
|
forceRemoveModalVisible.value = true;
|
||||||
|
modalLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
message.error(error.response?.data?.message || '操作失败');
|
message.error(error.response?.data?.message || '操作失败');
|
||||||
} finally {
|
} finally {
|
||||||
modalLoading.value = false;
|
modalLoading.value = false;
|
||||||
@ -572,6 +656,35 @@ const handleModalCancel = () => {
|
|||||||
formRef.value?.resetFields();
|
formRef.value?.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 强制移除确认弹窗 - 确认
|
||||||
|
const handleForceRemoveConfirm = async () => {
|
||||||
|
if (!pendingFormData.value || !editingId.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 传递 forceRemove: true 重新调用更新接口
|
||||||
|
await updateTenant(editingId.value, { ...pendingFormData.value, forceRemove: true } as UpdateTenantDto);
|
||||||
|
message.success('更新成功');
|
||||||
|
modalVisible.value = false;
|
||||||
|
forceRemoveModalVisible.value = false;
|
||||||
|
pendingFormData.value = null;
|
||||||
|
loadData();
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.message || '操作失败');
|
||||||
|
} finally {
|
||||||
|
modalLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 强制移除确认弹窗 - 取消
|
||||||
|
const handleForceRemoveCancel = () => {
|
||||||
|
forceRemoveModalVisible.value = false;
|
||||||
|
pendingFormData.value = null;
|
||||||
|
forceRemoveWarnings.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
// 查看详情
|
// 查看详情
|
||||||
const handleViewDetail = async (record: Tenant) => {
|
const handleViewDetail = async (record: Tenant) => {
|
||||||
drawerVisible.value = true;
|
drawerVisible.value = true;
|
||||||
@ -616,23 +729,25 @@ const handleQuotaOk = async () => {
|
|||||||
|
|
||||||
// 重置密码
|
// 重置密码
|
||||||
const handleResetPassword = (record: Tenant) => {
|
const handleResetPassword = (record: Tenant) => {
|
||||||
Modal.confirm({
|
currentTenant.value = record;
|
||||||
title: '确认重置密码',
|
newPassword.value = '';
|
||||||
content: `确定要重置 "${record.name}" 的密码吗?`,
|
resetPasswordVisible.value = true;
|
||||||
async onOk() {
|
|
||||||
try {
|
|
||||||
const res = await resetTenantPassword(record.id);
|
|
||||||
Modal.success({
|
|
||||||
title: '密码已重置',
|
|
||||||
content: `新密码: ${res.tempPassword}`,
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.response?.data?.message || '重置失败');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const confirmResetPassword = async () => {
|
||||||
|
if (!currentTenant.value) return;
|
||||||
|
|
||||||
|
resetting.value = true;
|
||||||
|
try {
|
||||||
|
const result = await resetTenantPassword(currentTenant.value.id);
|
||||||
|
newPassword.value = result.tempPassword;
|
||||||
|
message.success('密码重置成功');
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.message || '重置密码失败');
|
||||||
|
} finally {
|
||||||
|
resetting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
// 更新状态
|
// 更新状态
|
||||||
const handleUpdateStatus = (record: Tenant, status: string) => {
|
const handleUpdateStatus = (record: Tenant, status: string) => {
|
||||||
const statusText = status === 'ACTIVE' ? '恢复' : '暂停';
|
const statusText = status === 'ACTIVE' ? '恢复' : '暂停';
|
||||||
@ -717,4 +832,53 @@ onMounted(() => {
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 重置密码弹窗样式 */
|
||||||
|
.reset-password-content {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-warning {
|
||||||
|
padding: 20px;
|
||||||
|
background: #EEF2FF;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
color: #6366F1;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-warning p {
|
||||||
|
margin: 0;
|
||||||
|
color: #636E72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-password-box p {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #636E72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-display {
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal title styling */
|
||||||
|
.modal-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title-icon {
|
||||||
|
color: #6366F1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -122,9 +122,9 @@ public class AdminTenantController {
|
|||||||
|
|
||||||
@Operation(summary = "重置租户密码")
|
@Operation(summary = "重置租户密码")
|
||||||
@PostMapping("/{id}/reset-password")
|
@PostMapping("/{id}/reset-password")
|
||||||
public Result<Void> resetTenantPassword(@PathVariable Long id) {
|
public Result<Map<String, String>> resetTenantPassword(@PathVariable Long id) {
|
||||||
// TODO: 实现重置租户密码逻辑
|
String tempPassword = tenantService.resetPasswordAndReturnTemp(id);
|
||||||
return Result.success();
|
return Result.success(Map.of("tempPassword", tempPassword));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -53,4 +53,9 @@ public interface TenantService extends com.baomidou.mybatisplus.extension.servic
|
|||||||
*/
|
*/
|
||||||
void updateTenantStatus(Long id, String status);
|
void updateTenantStatus(Long id, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置租户密码并返回临时密码
|
||||||
|
*/
|
||||||
|
String resetPasswordAndReturnTemp(Long id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,10 +14,14 @@ import com.reading.platform.entity.Student;
|
|||||||
import com.reading.platform.entity.Teacher;
|
import com.reading.platform.entity.Teacher;
|
||||||
import com.reading.platform.entity.Tenant;
|
import com.reading.platform.entity.Tenant;
|
||||||
import com.reading.platform.entity.TenantPackage;
|
import com.reading.platform.entity.TenantPackage;
|
||||||
|
import com.reading.platform.entity.CourseCollectionPackage;
|
||||||
|
import com.reading.platform.entity.SchedulePlan;
|
||||||
import com.reading.platform.mapper.StudentMapper;
|
import com.reading.platform.mapper.StudentMapper;
|
||||||
import com.reading.platform.mapper.TeacherMapper;
|
import com.reading.platform.mapper.TeacherMapper;
|
||||||
import com.reading.platform.mapper.TenantMapper;
|
import com.reading.platform.mapper.TenantMapper;
|
||||||
import com.reading.platform.mapper.TenantPackageMapper;
|
import com.reading.platform.mapper.TenantPackageMapper;
|
||||||
|
import com.reading.platform.mapper.SchedulePlanMapper;
|
||||||
|
import com.reading.platform.mapper.CourseCollectionPackageMapper;
|
||||||
import com.reading.platform.mapper.CourseCollectionMapper;
|
import com.reading.platform.mapper.CourseCollectionMapper;
|
||||||
import com.reading.platform.entity.CourseCollection;
|
import com.reading.platform.entity.CourseCollection;
|
||||||
import com.reading.platform.service.CoursePackageService;
|
import com.reading.platform.service.CoursePackageService;
|
||||||
@ -30,9 +34,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +59,8 @@ public class TenantServiceImpl extends com.baomidou.mybatisplus.extension.servic
|
|||||||
private final TeacherMapper teacherMapper;
|
private final TeacherMapper teacherMapper;
|
||||||
private final StudentMapper studentMapper;
|
private final StudentMapper studentMapper;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final SchedulePlanMapper schedulePlanMapper;
|
||||||
|
private final CourseCollectionPackageMapper collectionPackageMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -174,25 +184,81 @@ public class TenantServiceImpl extends com.baomidou.mybatisplus.extension.servic
|
|||||||
if (request.getCollectionIds() != null) {
|
if (request.getCollectionIds() != null) {
|
||||||
LocalDate endDate = request.getExpireDate() != null ? request.getExpireDate() : tenant.getExpireDate();
|
LocalDate endDate = request.getExpireDate() != null ? request.getExpireDate() : tenant.getExpireDate();
|
||||||
|
|
||||||
// 1. 删除不在新列表中的关联记录
|
// 1. 获取现有的关联集合 ID(ACTIVE 状态)
|
||||||
tenantPackageMapper.delete(
|
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
|
||||||
.eq(TenantPackage::getTenantId, id)
|
|
||||||
.notIn(TenantPackage::getCollectionId, request.getCollectionIds())
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. 获取现有的关联集合 ID
|
|
||||||
List<TenantPackage> existingPackages = tenantPackageMapper.selectList(
|
List<TenantPackage> existingPackages = tenantPackageMapper.selectList(
|
||||||
new LambdaQueryWrapper<TenantPackage>()
|
new LambdaQueryWrapper<TenantPackage>()
|
||||||
.eq(TenantPackage::getTenantId, id)
|
.eq(TenantPackage::getTenantId, id)
|
||||||
|
.eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE)
|
||||||
);
|
);
|
||||||
Set<Long> existingCollectionIds = existingPackages.stream()
|
Set<Long> existingCollectionIds = existingPackages.stream()
|
||||||
.map(TenantPackage::getCollectionId)
|
.map(TenantPackage::getCollectionId)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// 3. 创建新的关联记录
|
// 2. 计算被移除的套餐 ID(在 existingCollectionIds 中但不在 request.getCollectionIds() 中)
|
||||||
|
Set<Long> removedCollectionIds = existingCollectionIds.stream()
|
||||||
|
.filter(collectionId -> !request.getCollectionIds().contains(collectionId))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 3. 检查被移除的套餐下是否有排课计划(用于提示用户)
|
||||||
|
if (!removedCollectionIds.isEmpty() && (request.getForceRemove() == null || !request.getForceRemove())) {
|
||||||
|
Map<String, Object> warnings = new HashMap<>();
|
||||||
|
for (Long collectionId : removedCollectionIds) {
|
||||||
|
// 查询该套餐下所有课程包
|
||||||
|
List<Long> packageIds = collectionPackageMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<CourseCollectionPackage>()
|
||||||
|
.eq(CourseCollectionPackage::getCollectionId, collectionId)
|
||||||
|
).stream().map(CourseCollectionPackage::getPackageId).collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!packageIds.isEmpty()) {
|
||||||
|
// 查询该套餐下课程包的排课数量
|
||||||
|
long count = schedulePlanMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<SchedulePlan>()
|
||||||
|
.eq(SchedulePlan::getTenantId, id)
|
||||||
|
.in(SchedulePlan::getCoursePackageId, packageIds)
|
||||||
|
);
|
||||||
|
if (count > 0) {
|
||||||
|
// 获取套餐名称
|
||||||
|
CourseCollection collection = collectionMapper.selectById(collectionId);
|
||||||
|
Map<String, Object> packageWarning = new HashMap<>();
|
||||||
|
packageWarning.put("collectionId", collectionId);
|
||||||
|
packageWarning.put("collectionName", collection != null ? collection.getName() : "未知套餐");
|
||||||
|
packageWarning.put("scheduleCount", count);
|
||||||
|
warnings.put("package_" + collectionId, packageWarning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有警告信息,抛出异常让前端显示确认弹窗
|
||||||
|
if (!warnings.isEmpty()) {
|
||||||
|
List<Map<String, Object>> warningList = new ArrayList<>();
|
||||||
|
for (Object warning : warnings.values()) {
|
||||||
|
warningList.add((Map<String, Object>) warning);
|
||||||
|
}
|
||||||
|
throw new BusinessException(ErrorCode.REMOVE_PACKAGE_HAS_SCHEDULES,
|
||||||
|
"该套餐下有排课计划,请确认是否强制移除", warningList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 将被移除的套餐关联状态改为 EXPIRED(不物理删除)
|
||||||
|
for (Long collectionId : removedCollectionIds) {
|
||||||
|
TenantPackage tenantPackage = existingPackages.stream()
|
||||||
|
.filter(tp -> tp.getCollectionId().equals(collectionId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
if (tenantPackage != null) {
|
||||||
|
tenantPackage.setStatus(TenantPackageStatus.EXPIRED);
|
||||||
|
tenantPackage.setUpdatedAt(LocalDateTime.now());
|
||||||
|
tenantPackageMapper.updateById(tenantPackage);
|
||||||
|
log.info("租户套餐关联已过期,tenantId={}, collectionId={}", id, collectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 获取更新后的关联集合 ID(用于创建新关联)
|
||||||
|
Set<Long> currentCollectionIds = existingCollectionIds;
|
||||||
|
|
||||||
|
// 6. 创建新的关联记录
|
||||||
for (Long collectionId : request.getCollectionIds()) {
|
for (Long collectionId : request.getCollectionIds()) {
|
||||||
if (!existingCollectionIds.contains(collectionId)) {
|
if (!currentCollectionIds.contains(collectionId)) {
|
||||||
CourseCollection collection = collectionMapper.selectById(collectionId);
|
CourseCollection collection = collectionMapper.selectById(collectionId);
|
||||||
if (collection == null) {
|
if (collection == null) {
|
||||||
log.warn("课程套餐不存在,collectionId: {}", collectionId);
|
log.warn("课程套餐不存在,collectionId: {}", collectionId);
|
||||||
@ -202,21 +268,21 @@ public class TenantServiceImpl extends com.baomidou.mybatisplus.extension.servic
|
|||||||
TenantPackage tp = new TenantPackage();
|
TenantPackage tp = new TenantPackage();
|
||||||
tp.setTenantId(id);
|
tp.setTenantId(id);
|
||||||
tp.setCollectionId(collectionId);
|
tp.setCollectionId(collectionId);
|
||||||
tp.setStartDate(java.time.LocalDate.now());
|
tp.setStartDate(LocalDate.now());
|
||||||
tp.setEndDate(endDate);
|
tp.setEndDate(endDate);
|
||||||
tp.setStatus(TenantPackageStatus.ACTIVE);
|
tp.setStatus(TenantPackageStatus.ACTIVE);
|
||||||
tp.setPricePaid(collection.getDiscountPrice() != null ? collection.getDiscountPrice() : collection.getPrice());
|
tp.setPricePaid(collection.getDiscountPrice() != null ? collection.getDiscountPrice() : collection.getPrice());
|
||||||
tp.setCreatedAt(java.time.LocalDateTime.now());
|
tp.setCreatedAt(LocalDateTime.now());
|
||||||
tenantPackageMapper.insert(tp);
|
tenantPackageMapper.insert(tp);
|
||||||
log.info("创建租户课程套餐关联,tenantId={}, collectionId={}", id, collectionId);
|
log.info("创建租户课程套餐关联,tenantId={}, collectionId={}", id, collectionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 更新现有关联的结束日期
|
// 7. 更新现有关联的结束日期
|
||||||
for (TenantPackage tp : existingPackages) {
|
for (TenantPackage tp : existingPackages) {
|
||||||
if (request.getCollectionIds().contains(tp.getCollectionId())) {
|
if (request.getCollectionIds().contains(tp.getCollectionId())) {
|
||||||
tp.setEndDate(endDate);
|
tp.setEndDate(endDate);
|
||||||
tp.setUpdatedAt(java.time.LocalDateTime.now());
|
tp.setUpdatedAt(LocalDateTime.now());
|
||||||
tenantPackageMapper.updateById(tp);
|
tenantPackageMapper.updateById(tp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,4 +470,16 @@ public class TenantServiceImpl extends com.baomidou.mybatisplus.extension.servic
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public String resetPasswordAndReturnTemp(Long id) {
|
||||||
|
log.info("开始重置租户密码并返回临时密码,ID: {}", id);
|
||||||
|
Tenant tenant = getTenantById(id);
|
||||||
|
String tempPassword = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
|
||||||
|
tenant.setPassword(passwordEncoder.encode(tempPassword));
|
||||||
|
baseMapper.updateById(tenant);
|
||||||
|
log.info("租户密码重置成功,ID: {}", id);
|
||||||
|
return tempPassword;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user