| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- package com.xuekairui.user.service;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.xuekairui.common.BusinessException;
- import com.xuekairui.common.ErrorCode;
- import com.xuekairui.common.event.ActivityEventRedisService;
- import com.xuekairui.user.dto.BusinessLicenseReviewResponse;
- import com.xuekairui.user.entity.BusinessLicense;
- import com.xuekairui.user.entity.User;
- import com.xuekairui.user.enums.LicenseStatus;
- import com.xuekairui.user.enums.MembershipLevel;
- import com.xuekairui.user.enums.OperationSource;
- import com.xuekairui.user.enums.OperationType;
- import com.xuekairui.user.enums.OperatorRole;
- import com.xuekairui.user.mapper.BusinessLicenseMapper;
- import com.xuekairui.user.mapper.UserMapper;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import org.springframework.transaction.support.TransactionSynchronization;
- import org.springframework.transaction.support.TransactionSynchronizationManager;
- import java.time.LocalDateTime;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- /**
- * 入驻信息服务(封装所有 BusinessLicenseMapper 操作 + 审核业务逻辑)
- * <p>
- * 用户侧:提交/查询入驻信息
- * 运营侧:列表查询、审核通过(赠送会员)、驳回
- *
- * @author ProPrice Team
- * @since 2026-07-09
- */
- @Slf4j
- @Service
- @RequiredArgsConstructor
- public class BusinessLicenseService {
- private final BusinessLicenseMapper businessLicenseMapper;
- private final MembershipService membershipService;
- private final ActivityEventRedisService activityEventRedisService;
- private final OperationAuditLogService auditLogService;
- private final UserMapper userMapper;
- private final ShopResourceSyncService shopResourceSyncService;
- // ======================== 查询方法 ========================
- /**
- * 按审核状态 + 手机号关键词查询列表(不传则查全部),LEFT JOIN 查提交者信息,一次 SQL
- */
- public List<BusinessLicenseReviewResponse> listByReviewStatus(String reviewStatus, String keyword) {
- if (reviewStatus != null && reviewStatus.isBlank()) {
- reviewStatus = null;
- }
- if (keyword != null && keyword.isBlank()) {
- keyword = null;
- }
- return businessLicenseMapper.selectReviewList(reviewStatus, keyword);
- }
- /**
- * 根据 ID 查询
- */
- public BusinessLicense getById(Long id) {
- return businessLicenseMapper.selectById(id);
- }
- /**
- * 根据用户 ID 查询入驻信息
- */
- public BusinessLicense getByUserId(Long userId) {
- return businessLicenseMapper.selectByUserId(userId);
- }
- /**
- * 批量查询用户入驻状态,返回 userId → reviewStatus
- * 未提交的用户不在 Map 中(视为 NOT_SUBMITTED)
- */
- public Map<Long, String> getLicenseStatusByUserIds(List<Long> userIds) {
- if (userIds == null || userIds.isEmpty()) {
- return Map.of();
- }
- List<BusinessLicense> licenses = businessLicenseMapper.selectList(
- new LambdaQueryWrapper<BusinessLicense>()
- .in(BusinessLicense::getUserId, userIds));
- Map<Long, String> result = new HashMap<>();
- for (BusinessLicense l : licenses) {
- result.put(l.getUserId(), l.getReviewStatus());
- }
- return result;
- }
- /**
- * 插入新入驻信息(通用方法,供同步等场景使用)
- * <p>必须包含 userId,否则拒绝写入,避免产生无主入驻记录。
- */
- public void insert(BusinessLicense license) {
- if (license.getUserId() == null) {
- throw new BusinessException(ErrorCode.PARAM_ERROR, "入驻信息缺少提交用户ID,无法写入");
- }
- businessLicenseMapper.insert(license);
- }
- /**
- * 更新入驻信息(通用方法,供同步等场景使用)
- */
- public void updateById(BusinessLicense license) {
- businessLicenseMapper.updateById(license);
- }
- /**
- * 检查用户是否已有其他审核通过的入驻信息(避免重复赠送会员)
- */
- private boolean hasOtherApprovedLicense(Long userId, Long excludeLicenseId) {
- Long count = businessLicenseMapper.selectCount(
- new LambdaQueryWrapper<BusinessLicense>()
- .eq(BusinessLicense::getUserId, userId)
- .eq(BusinessLicense::getReviewStatus, LicenseStatus.APPROVED.name())
- .ne(BusinessLicense::getId, excludeLicenseId));
- return count != null && count > 0;
- }
- // ======================== 用户侧:提交入驻信息 ========================
- /**
- * 提交或更新入驻信息(用户端)
- */
- @Transactional
- public void saveOrUpdate(Long userId, BusinessLicenseSaveRequest request) {
- BusinessLicense existing = getByUserId(userId);
- final BusinessLicense targetLicense;
- if (existing == null) {
- BusinessLicense license = BusinessLicense.builder()
- .userId(userId)
- .storeName(request.getStoreName())
- .terminalType(request.getTerminalType())
- .province(request.getProvince())
- .city(request.getCity())
- .district(request.getDistrict())
- .storeAddress(request.getStoreAddress())
- .contactPerson(request.getContactPerson())
- .contactPhone(request.getContactPhone())
- .licenseImageUrl(request.getBusinessLicenseUrl())
- .drugLicenseUrl(request.getDrugLicenseUrl())
- .creditCode(request.getCreditCode())
- .medicalDeviceClass2Url(request.getMedicalDeviceClass2Url())
- .medicalDeviceClass3Url(request.getMedicalDeviceClass3Url())
- .reviewStatus(LicenseStatus.PENDING.name())
- .showVerifiedBadge(false)
- .build();
- businessLicenseMapper.insert(license);
- targetLicense = license;
- log.info("用户{}提交入驻信息:id={}", userId, license.getId());
- } else {
- existing.setStoreName(request.getStoreName());
- existing.setTerminalType(request.getTerminalType());
- existing.setProvince(request.getProvince());
- existing.setCity(request.getCity());
- existing.setDistrict(request.getDistrict());
- existing.setStoreAddress(request.getStoreAddress());
- existing.setContactPerson(request.getContactPerson());
- existing.setContactPhone(request.getContactPhone());
- existing.setLicenseImageUrl(request.getBusinessLicenseUrl());
- existing.setDrugLicenseUrl(request.getDrugLicenseUrl());
- if (request.getCreditCode() != null) {
- existing.setCreditCode(request.getCreditCode());
- }
- if (request.getMedicalDeviceClass2Url() != null) {
- existing.setMedicalDeviceClass2Url(request.getMedicalDeviceClass2Url());
- }
- if (request.getMedicalDeviceClass3Url() != null) {
- existing.setMedicalDeviceClass3Url(request.getMedicalDeviceClass3Url());
- }
- existing.setReviewStatus(LicenseStatus.PENDING.name());
- businessLicenseMapper.updateById(existing);
- targetLicense = existing;
- log.info("用户{}更新入驻信息:id={}", userId, existing.getId());
- }
- // 事务提交后同步客户入驻信息到第三方系统(智价云药店版),失败不阻塞主流程
- User submitter = userMapper.selectById(userId);
- if (submitter != null) {
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- @Override
- public void afterCommit() {
- syncShopResourceToThirdParty(submitter, targetLicense);
- }
- });
- }
- }
- // ======================== 运营侧:管理员新增入驻 ========================
- /**
- * 管理员后台新增入驻信息(代客户填写)
- * <p>
- * 业务规则:
- * <ul>
- * <li>必须选择一个未提交过入驻信息的客户(t_business_license 中无该 userId 记录)</li>
- * <li>必填基础信息 + 营业执照 + 药品经营许可;二类/三类医疗器械至少上传一个</li>
- * <li>管理员手动创建的记录状态为 PENDING,需再操作一次审核确认才转为 APPROVED</li>
- * <li>审核确认通过后赠送 30 天高级会员并同步第三方(由 approve 方法处理)</li>
- * </ul>
- *
- * @param request 入驻信息(含 userId)
- * @param reviewerId 操作管理员 ID
- */
- @Transactional
- public void createByAdmin(BusinessLicenseSaveRequest request, Long reviewerId) {
- Long userId = request.getUserId();
- if (userId == null) {
- throw new BusinessException(ErrorCode.PARAM_ERROR, "必须选择一个客户");
- }
- // 校验客户存在
- User submitter = userMapper.selectById(userId);
- if (submitter == null) {
- throw new BusinessException(ErrorCode.NOT_FOUND, "客户(ID=" + userId + ")不存在");
- }
- // 校验该客户未提交过入驻信息
- BusinessLicense existing = getByUserId(userId);
- if (existing != null) {
- throw new BusinessException(ErrorCode.BUSINESS_ERROR,
- "该客户已提交过入驻信息,无法重复新增,请使用编辑功能");
- }
- // 校验资质必填项
- validateRequiredQualifications(request);
- // 构建入驻记录,状态为 PENDING,需管理员再操作一次审核确认
- BusinessLicense license = BusinessLicense.builder()
- .userId(userId)
- .storeName(request.getStoreName())
- .terminalType(request.getTerminalType())
- .province(request.getProvince())
- .city(request.getCity())
- .district(request.getDistrict())
- .storeAddress(request.getStoreAddress())
- .contactPerson(request.getContactPerson())
- .contactPhone(request.getContactPhone())
- .licenseImageUrl(request.getBusinessLicenseUrl())
- .drugLicenseUrl(request.getDrugLicenseUrl())
- .creditCode(request.getCreditCode())
- .medicalDeviceClass2Url(request.getMedicalDeviceClass2Url())
- .medicalDeviceClass3Url(request.getMedicalDeviceClass3Url())
- .reviewStatus(LicenseStatus.PENDING.name())
- .showVerifiedBadge(false)
- .syncSource("MANUAL")
- .status(0)
- .build();
- businessLicenseMapper.insert(license);
- log.info("管理员{}新增入驻信息(待审核确认): licenseId={}, userId={}",
- reviewerId, license.getId(), userId);
- // 审计日志:管理员新增,记录变更后快照
- String userName = submitter.getNickname() != null
- ? submitter.getNickname() : String.valueOf(userId);
- String storeName = license.getStoreName() != null ? license.getStoreName() : "未知药店";
- auditLogService.logSuccess(
- reviewerId != null ? reviewerId : 0L,
- OperatorRole.ADMIN,
- OperationType.LICENSE_CREATE,
- "LICENSE:" + license.getId(),
- license.getId(),
- null,
- Map.of("userId", userId, "userName", userName, "storeName", storeName,
- "reviewStatus", LicenseStatus.PENDING.name(), "syncSource", "MANUAL"),
- String.format("管理员新增客户「%s」(药店:%s)的入驻信息,待审核确认", userName, storeName),
- OperationSource.ADMIN_CREATE);
- }
- /**
- * 校验资质必填项
- * <p>
- * 营业执照、药品经营许可必填;二类/三类医疗器械至少上传一个。
- */
- private void validateRequiredQualifications(BusinessLicenseSaveRequest request) {
- if (isBlank(request.getBusinessLicenseUrl())) {
- throw new BusinessException(ErrorCode.PARAM_ERROR, "营业执照图片不能为空");
- }
- if (isBlank(request.getDrugLicenseUrl())) {
- throw new BusinessException(ErrorCode.PARAM_ERROR, "药品经营许可证不能为空");
- }
- boolean hasClass2 = !isBlank(request.getMedicalDeviceClass2Url());
- boolean hasClass3 = !isBlank(request.getMedicalDeviceClass3Url());
- if (!hasClass2 && !hasClass3) {
- throw new BusinessException(ErrorCode.PARAM_ERROR,
- "必须上传二类或三类医疗器械资质(二选一)");
- }
- }
- private static boolean isBlank(String s) {
- return s == null || s.isBlank();
- }
- // ======================== 运营侧:管理员编辑入驻 ========================
- /**
- * 管理员编辑入驻信息(可修改客户信息、替换资质图片,保存后即时生效)
- * <p>
- * 业务规则:
- * <ul>
- * <li>可编辑全部入驻数据(含已审核 APPROVED),不受 PENDING 限制</li>
- * <li>必填项约束同样生效:营业执照、药品经营许可必填,二类/三类至少一个</li>
- * <li>资质图片覆盖原内容(非空即覆盖,空值保留原值)</li>
- * <li>记录变更前/变更后快照,便于追溯</li>
- * <li>事务提交后同步到第三方系统,失败不阻塞主流程</li>
- * </ul>
- *
- * @param licenseId 入驻信息 ID
- * @param request 编辑后的入驻信息
- * @param reviewerId 操作管理员 ID
- */
- @Transactional
- public void editByAdmin(Long licenseId, BusinessLicenseSaveRequest request, Long reviewerId) {
- BusinessLicense license = getById(licenseId);
- if (license == null) {
- throw new BusinessException(ErrorCode.NOT_FOUND, "入驻信息记录不存在");
- }
- // 校验资质必填项
- validateRequiredQualifications(request);
- // 变更前快照
- Map<String, Object> beforeData = buildSnapshot(license);
- Long userId = license.getUserId();
- User submitter = userMapper.selectById(userId);
- if (submitter == null) {
- throw new BusinessException(ErrorCode.NOT_FOUND, "客户(ID=" + userId + ")不存在");
- }
- // 覆盖更新所有字段(资质图片非空即覆盖)
- license.setStoreName(request.getStoreName());
- license.setTerminalType(request.getTerminalType());
- license.setProvince(request.getProvince());
- license.setCity(request.getCity());
- license.setDistrict(request.getDistrict());
- license.setStoreAddress(request.getStoreAddress());
- license.setContactPerson(request.getContactPerson());
- license.setContactPhone(request.getContactPhone());
- license.setLicenseImageUrl(request.getBusinessLicenseUrl());
- license.setDrugLicenseUrl(request.getDrugLicenseUrl());
- if (request.getCreditCode() != null) {
- license.setCreditCode(request.getCreditCode());
- }
- if (request.getMedicalDeviceClass2Url() != null) {
- license.setMedicalDeviceClass2Url(request.getMedicalDeviceClass2Url());
- }
- if (request.getMedicalDeviceClass3Url() != null) {
- license.setMedicalDeviceClass3Url(request.getMedicalDeviceClass3Url());
- }
- license.setReviewerId(reviewerId);
- license.setReviewTime(LocalDateTime.now());
- businessLicenseMapper.updateById(license);
- log.info("管理员{}编辑入驻信息: licenseId={}, userId={}", reviewerId, licenseId, userId);
- // 变更后快照
- Map<String, Object> afterData = buildSnapshot(license);
- // 事务提交后同步到第三方系统
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- @Override
- public void afterCommit() {
- syncShopResourceToThirdParty(submitter, license);
- }
- });
- // 审计日志:管理员编辑,记录变更前/后快照
- String userName = submitter.getNickname() != null
- ? submitter.getNickname() : String.valueOf(userId);
- String storeName = license.getStoreName() != null ? license.getStoreName() : "未知药店";
- auditLogService.logSuccess(
- reviewerId != null ? reviewerId : 0L,
- OperatorRole.ADMIN,
- OperationType.LICENSE_EDIT,
- "LICENSE:" + license.getId(),
- license.getId(),
- beforeData,
- afterData,
- String.format("管理员编辑客户「%s」(药店:%s)的入驻信息", userName, storeName),
- OperationSource.ADMIN_CREATE);
- }
- /**
- * 构建入驻信息快照(用于审计日志的变更前/后对比)
- */
- private Map<String, Object> buildSnapshot(BusinessLicense license) {
- Map<String, Object> snapshot = new HashMap<>();
- snapshot.put("userId", license.getUserId());
- snapshot.put("storeName", license.getStoreName());
- snapshot.put("terminalType", license.getTerminalType());
- snapshot.put("province", license.getProvince());
- snapshot.put("city", license.getCity());
- snapshot.put("district", license.getDistrict());
- snapshot.put("storeAddress", license.getStoreAddress());
- snapshot.put("contactPerson", license.getContactPerson());
- snapshot.put("contactPhone", license.getContactPhone());
- snapshot.put("creditCode", license.getCreditCode());
- snapshot.put("businessLicenseUrl", license.getLicenseImageUrl());
- snapshot.put("drugLicenseUrl", license.getDrugLicenseUrl());
- snapshot.put("medicalDeviceClass2Url", license.getMedicalDeviceClass2Url());
- snapshot.put("medicalDeviceClass3Url", license.getMedicalDeviceClass3Url());
- snapshot.put("reviewStatus", license.getReviewStatus());
- snapshot.put("showVerifiedBadge", license.getShowVerifiedBadge());
- return snapshot;
- }
- // ======================== 运营侧:审核操作 ========================
- /**
- * 审核通过
- * <p>
- * 更新审核状态为 APPROVED,展示认证标识。首次审核通过时赠送 30 天高级会员,
- * 并推送 Redis 事件供活动模块消费(邀请有礼:被邀请人资质通过后发邀请人奖励)。
- *
- * @param licenseId 入驻信息 ID
- * @param reviewerId 审核人 ID
- */
- @Transactional
- public void approve(Long licenseId, Long reviewerId) {
- BusinessLicense license = validateReviewable(licenseId);
- // 提交用户必须存在,否则拒绝审核
- Long userId = license.getUserId();
- User submitter = userMapper.selectById(userId);
- if (submitter == null) {
- throw new BusinessException(ErrorCode.NOT_FOUND, "提交用户(ID=" + userId + ")不存在,无法审核");
- }
- license.setReviewStatus(LicenseStatus.APPROVED.name());
- license.setReviewerId(reviewerId);
- license.setReviewTime(LocalDateTime.now());
- license.setShowVerifiedBadge(true);
- businessLicenseMapper.updateById(license);
- // 首次审核通过,赠送 30 天高级会员
- if (!hasOtherApprovedLicense(userId, licenseId)) {
- membershipService.grantOrExtendMembership(
- userId,
- MembershipLevel.PRO,
- 30,
- "LICENSE",
- "入驻信息审核通过赠送30天高级会员");
- log.info("入驻信息审核通过,赠送用户{} 30天高级会员", userId);
- // 事务提交后推送 Redis 事件,供活动模块消费
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- @Override
- public void afterCommit() {
- try {
- activityEventRedisService.pushLicenseApproved(userId, license.getId(), "BUSINESS_LICENSE");
- } catch (Exception e) {
- log.warn("存入入驻审核通过事件失败,不影响主流程: userId={}", userId, e);
- }
- }
- });
- }
- // 事务提交后同步客户入驻信息到第三方系统(智价云药店版),失败不阻塞主流程
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- @Override
- public void afterCommit() {
- syncShopResourceToThirdParty(submitter, license);
- }
- });
- // 审计日志:审核通过,记录客户信息
- String userName = submitter.getNickname() != null
- ? submitter.getNickname() : String.valueOf(userId);
- String storeName = license.getStoreName() != null ? license.getStoreName() : "未知药店";
- auditLogService.logSuccess(
- reviewerId != null ? reviewerId : 0L,
- OperatorRole.ADMIN,
- OperationType.LICENSE_APPROVE,
- "LICENSE:" + license.getId(),
- license.getId(),
- null,
- Map.of("userId", userId, "userName", userName, "storeName", storeName),
- String.format("审核通过客户「%s」(药店:%s)的入驻申请", userName, storeName),
- OperationSource.ADMIN_CREATE);
- log.info("入驻审核通过: licenseId={}, userId={}, userName={}, reviewerId={}",
- licenseId, userId, userName, reviewerId);
- }
- /**
- * 同步客户入驻信息到第三方系统(智价云药店版)
- * <p>
- * 将客户手机号 + 店铺信息推送到第三方接口,第三方返回该客户的访问令牌。
- * 在入驻新增、审核驳回、审核通过三个环节均触发同步,确保第三方实时获取入驻状态变更。
- * 同步成功后将第三方 user_id 回写到入驻记录的 externalId 字段,并标记 status=1。
- * 第三方不可用或同步失败时标记 status=2,不影响本地主流程,
- * 后续可通过 status 筛选失败记录进行补推。
- */
- private void syncShopResourceToThirdParty(User submitter, BusinessLicense license) {
- try {
- ShopResourceSyncService.SyncResult result =
- shopResourceSyncService.syncShopResource(submitter, license);
- if (result != null && result.getUserId() != null) {
- license.setExternalId(result.getUserId());
- license.setStatus(1);
- businessLicenseMapper.updateById(license);
- log.info("同步客户入驻信息到第三方成功: licenseId={}, externalUserId={}, newUser={}",
- license.getId(), result.getUserId(), result.getNewUser());
- } else {
- license.setStatus(2);
- businessLicenseMapper.updateById(license);
- log.warn("同步客户入驻信息到第三方未成功(第三方未返回user_id),标记为2: licenseId={}",
- license.getId());
- }
- } catch (Exception e) {
- license.setStatus(2);
- businessLicenseMapper.updateById(license);
- log.warn("同步客户入驻信息到第三方失败,标记为2,不影响主流程: licenseId={}, error={}",
- license.getId(), e.getMessage());
- }
- }
- /**
- * 审核驳回
- * <p>
- * 业务规则:
- * <ul>
- * <li>只能对 PENDING(新提交/未审核通过)的记录驳回,APPROVED 不允许驳回</li>
- * <li>驳回原因必填</li>
- * <li>驳回后状态为 REJECTED,客户端/后台客户详情同步移除该条入驻展示</li>
- * <li>用户可重新上传提交(saveOrUpdate 会将状态改回 PENDING)</li>
- * <li>记录变更前/后快照,便于追溯</li>
- * <li>事务提交后同步到第三方系统,失败不阻塞主流程</li>
- * </ul>
- *
- * @param licenseId 入驻信息 ID
- * @param reviewerId 审核人 ID
- * @param rejectReason 驳回原因(必填)
- */
- @Transactional
- public void reject(Long licenseId, Long reviewerId, String rejectReason) {
- // 驳回原因必填
- if (rejectReason == null || rejectReason.isBlank()) {
- throw new BusinessException(ErrorCode.PARAM_ERROR, "驳回原因不能为空");
- }
- BusinessLicense license = validateReviewable(licenseId);
- // 审核通过后不允许驳回
- if (LicenseStatus.APPROVED.name().equals(license.getReviewStatus())) {
- throw new BusinessException(ErrorCode.BAD_REQUEST, "该入驻信息已审核通过,不允许驳回");
- }
- // 变更前快照
- Map<String, Object> beforeData = buildSnapshot(license);
- // 提交用户必须存在
- Long userId = license.getUserId();
- User submitter = userMapper.selectById(userId);
- if (submitter == null) {
- throw new BusinessException(ErrorCode.NOT_FOUND, "提交用户(ID=" + userId + ")不存在,无法审核");
- }
- license.setReviewStatus(LicenseStatus.REJECTED.name());
- license.setReviewerId(reviewerId);
- license.setReviewTime(LocalDateTime.now());
- license.setShowVerifiedBadge(false);
- license.setRejectReason(rejectReason);
- businessLicenseMapper.updateById(license);
- // 变更后快照
- Map<String, Object> afterData = buildSnapshot(license);
- // 审计日志:审核驳回,记录变更前/后快照
- String userName = submitter.getNickname() != null
- ? submitter.getNickname() : String.valueOf(userId);
- String storeName = license.getStoreName() != null ? license.getStoreName() : "未知药店";
- auditLogService.logSuccess(
- reviewerId != null ? reviewerId : 0L,
- OperatorRole.ADMIN,
- OperationType.LICENSE_REJECT,
- "LICENSE:" + license.getId(),
- license.getId(),
- beforeData,
- afterData,
- String.format("驳回客户「%s」(药店:%s)的入驻申请,原因:%s",
- userName, storeName, rejectReason),
- OperationSource.ADMIN_CREATE);
- log.info("入驻审核驳回: licenseId={}, userId={}, userName={}, reviewerId={}, reason={}",
- licenseId, userId, userName, reviewerId, rejectReason);
- // 事务提交后同步客户入驻信息到第三方系统(智价云药店版),失败不阻塞主流程
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
- @Override
- public void afterCommit() {
- syncShopResourceToThirdParty(submitter, license);
- }
- });
- }
- /**
- * 校验入驻信息是否可审核(存在 + 状态为 PENDING)
- */
- private BusinessLicense validateReviewable(Long licenseId) {
- BusinessLicense license = getById(licenseId);
- if (license == null) {
- throw new BusinessException(ErrorCode.NOT_FOUND, "入驻信息记录不存在");
- }
- if (!LicenseStatus.PENDING.name().equals(license.getReviewStatus())) {
- throw new BusinessException(ErrorCode.BAD_REQUEST, "该入驻信息已审核,无法重复操作");
- }
- return license;
- }
- // ======================== 请求 DTO ========================
- /**
- * 入驻信息提交请求参数(用于 Service 层,避免依赖 Controller 层 DTO)
- */
- @lombok.Data
- @lombok.Builder
- @lombok.NoArgsConstructor
- @lombok.AllArgsConstructor
- public static class BusinessLicenseSaveRequest {
- /** 客户用户ID(管理员新增时必填,用户自主提交时由 Service 注入) */
- private Long userId;
- private String storeName;
- private String terminalType;
- private String province;
- private String city;
- private String district;
- private String storeAddress;
- private String contactPerson;
- private String contactPhone;
- private String businessLicenseUrl;
- private String drugLicenseUrl;
- private String creditCode;
- private String medicalDeviceClass2Url;
- private String medicalDeviceClass3Url;
- }
- }
|