package com.tcm.training.admin.member.service;
|
|
import cn.idev.excel.FastExcel;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.fasterxml.jackson.databind.JsonNode;
|
import com.tcm.training.admin.agent.entity.AgentRegion;
|
import com.tcm.training.admin.agent.entity.ProvincialAgent;
|
import com.tcm.training.admin.agent.excel.ClinicAgentImportRow;
|
import com.tcm.training.admin.agent.mapper.ProvincialAgentMapper;
|
import com.tcm.training.admin.app.register.entity.SysRegion;
|
import com.tcm.training.admin.app.register.mapper.SysRegionMapper;
|
import com.tcm.training.admin.common.DateTimes;
|
import com.tcm.training.admin.common.PageResponse;
|
import com.tcm.training.admin.common.PhoneMasker;
|
import com.tcm.training.admin.live.entity.Clinic;
|
import com.tcm.training.admin.live.mapper.ClinicMapper;
|
import com.tcm.training.admin.member.dto.ClinicBankAccountUpdateRequest;
|
import com.tcm.training.admin.member.entity.AppUser;
|
import com.tcm.training.admin.member.excel.ClinicExcelRow;
|
import com.tcm.training.admin.member.mapper.ClinicQueryMapper;
|
import com.tcm.training.admin.member.mapper.UserMapper;
|
import com.tcm.training.admin.member.row.ClinicBoundUserRow;
|
import com.tcm.training.admin.member.row.ClinicDetailRow;
|
import com.tcm.training.admin.member.row.ClinicListRow;
|
import com.tcm.training.admin.member.row.ClinicRegionOptionRow;
|
import com.tcm.training.admin.member.vo.ClinicBoundUserItemVO;
|
import com.tcm.training.admin.member.vo.ClinicDetailVO;
|
import com.tcm.training.admin.member.vo.ClinicListItemVO;
|
import com.tcm.training.admin.member.vo.ClinicRegionOptionVO;
|
import com.tcm.training.admin.utils.VzanHttpUtil;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.TransactionDefinition;
|
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
import org.springframework.web.multipart.MultipartFile;
|
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.net.URLEncoder;
|
import java.time.LocalDateTime;
|
import java.util.*;
|
|
@Service
|
@Slf4j
|
public class ClinicAppService {
|
|
private final ClinicQueryMapper clinicQueryMapper;
|
private final ClinicMapper clinicMapper;
|
private final ProvincialAgentMapper provincialAgentMapper;
|
private final UserMapper userMapper;
|
private final SysRegionMapper sysRegionMapper;
|
private final VzanHttpUtil vzanHttpUtil;
|
private final DataSourceTransactionManager transactionManager;
|
|
public ClinicAppService(ClinicQueryMapper clinicQueryMapper,
|
ClinicMapper clinicMapper,
|
ProvincialAgentMapper provincialAgentMapper,
|
UserMapper userMapper,
|
SysRegionMapper sysRegionMapper,
|
VzanHttpUtil vzanHttpUtil,
|
DataSourceTransactionManager transactionManager) {
|
this.clinicQueryMapper = clinicQueryMapper;
|
this.clinicMapper = clinicMapper;
|
this.provincialAgentMapper = provincialAgentMapper;
|
this.userMapper = userMapper;
|
this.sysRegionMapper = sysRegionMapper;
|
this.vzanHttpUtil = vzanHttpUtil;
|
this.transactionManager = transactionManager;
|
}
|
|
public PageResponse<ClinicListItemVO> page(int page,
|
int pageSize,
|
String regionCode,
|
String name,
|
String phone,
|
String agentName,
|
LocalDateTime createdFrom,
|
LocalDateTime createdTo,
|
Integer status) {
|
IPage<ClinicListRow> result = clinicQueryMapper.selectClinicPage(
|
new Page<ClinicListRow>(page, pageSize),
|
trimToNull(regionCode),
|
trimToNull(name),
|
trimToNull(phone),
|
trimToNull(agentName),
|
createdFrom,
|
createdTo,
|
status
|
);
|
List<ClinicListItemVO> list = new ArrayList<ClinicListItemVO>();
|
for (ClinicListRow row : result.getRecords()) {
|
list.add(toListItem(row));
|
}
|
return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
public void downloadClinicImportTemplate(HttpServletResponse response) throws IOException {
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setCharacterEncoding("utf-8");
|
String filename = URLEncoder.encode("诊所省区代理导入模板", "UTF-8").replace("+", "%20");
|
response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");
|
FastExcel.write(response.getOutputStream(), ClinicAgentImportRow.class)
|
.sheet("诊所省区代理导入模板")
|
.doWrite(Collections.emptyList());
|
}
|
|
@Transactional
|
public void importAgentBindings(MultipartFile file) throws IOException {
|
if (file == null || file.isEmpty()) {
|
throw new IllegalArgumentException("请选择导入文件");
|
}
|
List<ClinicAgentImportRow> rows = FastExcel.read(file.getInputStream())
|
.head(ClinicAgentImportRow.class).sheet().doReadSync();
|
if (rows == null || rows.isEmpty()) {
|
throw new IllegalArgumentException("导入文件不能为空");
|
}
|
|
Set<String> clinicNames = new LinkedHashSet<String>();
|
Set<String> agentNames = new LinkedHashSet<String>();
|
Map<String, String> bindingByClinicName = new HashMap<String, String>();
|
int rowNumber = 1;
|
for (ClinicAgentImportRow row : rows) {
|
rowNumber++;
|
if (row == null || (trimToNull(row.getClinicName()) == null && trimToNull(row.getAgentName()) == null))
|
continue;
|
String clinicName = trimToNull(row.getClinicName());
|
String agentName = trimToNull(row.getAgentName());
|
if (clinicName == null) {
|
throw new IllegalArgumentException("第" + rowNumber + "行诊所不能为空");
|
}
|
if (agentName == null) {
|
throw new IllegalArgumentException("第" + rowNumber + "行省区代理名称不能为空");
|
}
|
String previous = bindingByClinicName.put(clinicName, agentName);
|
if (previous != null && !previous.equals(agentName)) {
|
throw new IllegalArgumentException("诊所“" + clinicName + "”存在多个代理绑定");
|
}
|
|
clinicNames.add(clinicName);
|
agentNames.add(agentName);
|
}
|
if (bindingByClinicName.isEmpty()) {
|
throw new IllegalArgumentException("导入文件没有可用数据");
|
}
|
|
List<Clinic> clinics = clinicMapper.selectList(new LambdaQueryWrapper<Clinic>().in(Clinic::getName, clinicNames));
|
List<ProvincialAgent> agents = provincialAgentMapper.selectList(new LambdaQueryWrapper<ProvincialAgent>()
|
.eq(ProvincialAgent::getDeleted, 0).in(ProvincialAgent::getAgentName, agentNames));
|
Map<String, ProvincialAgent> agentByName = new LinkedHashMap<String, ProvincialAgent>();
|
for (ProvincialAgent agent : agents) {
|
agentByName.put(agent.getAgentName(), agent);
|
}
|
for (String agentName : agentNames) {
|
if (!agentByName.containsKey(agentName)) {
|
throw new IllegalArgumentException("省区代理不存在:" + agentName);
|
}
|
}
|
|
|
Set<String> foundClinicNames = new LinkedHashSet<String>();
|
for (Clinic clinic : clinics) {
|
foundClinicNames.add(clinic.getName());
|
ProvincialAgent agent = agentByName.get(bindingByClinicName.get(clinic.getName()));
|
clinic.setAgentId(agent.getId());
|
clinicMapper.updateById(clinic);
|
}
|
for (String clinicName : clinicNames) {
|
if (!foundClinicNames.contains(clinicName)) {
|
throw new IllegalArgumentException("诊所不存在:" + clinicName);
|
}
|
}
|
}
|
|
public void export(String regionCode,
|
String name,
|
String phone,
|
String agentName,
|
LocalDateTime createdFrom,
|
LocalDateTime createdTo,
|
Integer status,
|
HttpServletResponse response) throws IOException {
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
response.setCharacterEncoding("utf-8");
|
String filename = URLEncoder.encode("诊所列表", "UTF-8").replace("+", "%20");
|
response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx");
|
|
List<ClinicListRow> rows = clinicQueryMapper.selectClinicList(
|
trimToNull(regionCode),
|
trimToNull(name),
|
trimToNull(phone),
|
trimToNull(agentName),
|
createdFrom,
|
createdTo,
|
status
|
);
|
List<ClinicExcelRow> exports = new ArrayList<ClinicExcelRow>();
|
for (int i = 0; i < rows.size(); i++) {
|
ClinicListRow row = rows.get(i);
|
ClinicExcelRow excel = new ClinicExcelRow();
|
excel.setSerialNo(i + 1);
|
excel.setName(defaultText(row.getName()));
|
excel.setPhone(defaultText(PhoneMasker.mask(row.getPhone())));
|
excel.setRegionName(defaultText(row.getRegionName()));
|
excel.setUserCount(row.getUserCount() == null ? 0 : row.getUserCount());
|
excel.setStatus(statusText(row.getStatus()));
|
excel.setCreatedAt(DateTimes.format(row.getCreatedAt()));
|
exports.add(excel);
|
}
|
FastExcel.write(response.getOutputStream(), ClinicExcelRow.class)
|
.sheet("诊所列表")
|
.doWrite(exports);
|
}
|
|
public List<ClinicRegionOptionVO> regionOptions() {
|
List<ClinicRegionOptionRow> rows = clinicQueryMapper.selectRegionOptions();
|
List<ClinicRegionOptionVO> list = new ArrayList<ClinicRegionOptionVO>();
|
for (ClinicRegionOptionRow row : rows) {
|
list.add(new ClinicRegionOptionVO(row.getRegionCode(), row.getRegionName()));
|
}
|
return list;
|
}
|
|
public ClinicDetailVO detail(Long id) {
|
ClinicDetailRow row = clinicQueryMapper.selectClinicDetail(id);
|
if (row == null) {
|
throw new IllegalArgumentException("诊所不存在");
|
}
|
ClinicDetailVO vo = new ClinicDetailVO();
|
vo.setId(row.getId());
|
vo.setAgentId(row.getAgentId());
|
vo.setName(row.getName());
|
vo.setStatus(row.getStatus());
|
vo.setPhone(defaultText(row.getContactPhone()));
|
vo.setAddress(defaultText(row.getAddress()));
|
vo.setProvinceCode(row.getProvinceCode());
|
vo.setCityCode(row.getCityCode());
|
vo.setDistrictCode(row.getDistrictCode());
|
vo.setBusinessLicenseUrl(trimToNull(row.getBusinessLicenseUrl()));
|
vo.setUserCount(row.getUserCount() == null ? 0 : row.getUserCount());
|
vo.setBankName(defaultText(row.getBankName()));
|
vo.setAccountHolder(defaultText(row.getAccountHolder()));
|
vo.setAccountNumber(defaultText(row.getAccountNumber()));
|
vo.setBranchName(defaultText(row.getBranchName()));
|
vo.setAvatar(row.getAvatar());
|
vo.setLoginAccount(row.getLoginAccount());
|
return vo;
|
}
|
|
public ClinicDetailVO updateBankAccount(Long id, ClinicBankAccountUpdateRequest request) {
|
// 手动开启事务
|
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
|
TransactionStatus status = transactionManager.getTransaction(def);
|
|
try {
|
Clinic clinic = clinicMapper.selectById(id);
|
if (clinic == null || (clinic.getDeleted() != null && clinic.getDeleted() == 1)) {
|
throw new IllegalArgumentException("诊所不存在");
|
}
|
String loginAccount = trimToNull(request.getLoginAccount());
|
AppUser user = userMapper.selectOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getPhone, loginAccount).eq(AppUser::getDeleted, 0).last("limit 1"));
|
if (user == null) {
|
throw new IllegalArgumentException("登录账号不存在");
|
}
|
user.setAvatar(trimToNull(request.getAvatar()));
|
user.setPhone(loginAccount);
|
userMapper.updateById(user);
|
Set<String> codes = new LinkedHashSet<String>();
|
addCode(codes, request.getProvinceCode());
|
addCode(codes, request.getCityCode());
|
addCode(codes, request.getDistrictCode());
|
Map<String, SysRegion> regions = new HashMap<String, SysRegion>();
|
List<SysRegion> regionList = sysRegionMapper.selectList(new LambdaQueryWrapper<SysRegion>().in(SysRegion::getCode, codes));
|
if (!regionList.isEmpty()) {
|
for (SysRegion region : regionList) {
|
regions.put(region.getCode(), region);
|
}
|
}
|
|
clinic.setName(trimToNull(request.getName()));
|
clinic.setContactPhone(loginAccount);
|
clinic.setProvinceCode(trimToNull(request.getProvinceCode()));
|
clinic.setProvinceName(regionName(request.getProvinceCode(), request.getProvinceName(), regions));
|
clinic.setCityCode(trimToNull(request.getCityCode()));
|
clinic.setCityName(regionName(request.getCityCode(), request.getCityName(), regions));
|
clinic.setDistrictCode(trimToNull(request.getDistrictCode()));
|
clinic.setDistrictName(regionName(request.getDistrictCode(), request.getDistrictName(), regions));
|
clinic.setAddress(trimToNull(request.getAddress()));
|
clinic.setBusinessLicenseUrl(trimToNull(request.getBusinessLicenseUrl()));
|
clinic.setStatus(request.getStatus());
|
clinic.setAgentId(request.getAgentId());
|
clinic.setBankName(trimToNull(request.getBankName()));
|
clinic.setAccountHolder(trimToNull(request.getAccountHolder()));
|
clinic.setAccountNumber(trimToNull(request.getAccountNumber()));
|
clinic.setBranchName(trimToNull(request.getBranchName()));
|
clinicMapper.updateById(clinic);
|
|
// 提交事务
|
transactionManager.commit(status);
|
log.info("诊所本地数据更新成功,clinicId={}", id);
|
|
} catch (Exception e) {
|
// 发生异常,回滚事务
|
transactionManager.rollback(status);
|
log.error("诊所本地数据更新失败,clinicId={}", id, e);
|
throw e;
|
}
|
|
if (request.getAgentId() == null) {
|
return detail(id);
|
}
|
|
// 事务提交后,调用微赞接口(不回滚本地事务)
|
try {
|
Clinic clinic = clinicMapper.selectById(id);
|
|
// 调用微赞创建门店
|
Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码");
|
Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码");
|
Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码");
|
log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}",
|
clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
|
JsonNode vzanStoreResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
|
log.info("微赞创建门店成功,出参={}", vzanStoreResponse);
|
|
// 查询代理信息获取parentMobile
|
ProvincialAgent agent = provincialAgentMapper.selectById(request.getAgentId());
|
|
String parentMobile = agent.getContactInfo();
|
if (parentMobile == null || parentMobile.trim().isEmpty()) {
|
throw new IllegalArgumentException("代理联系方式为空,agentId=" + request.getAgentId());
|
}
|
|
// 调用微赞创建代理
|
log.info("调用微赞创建代理,入参 agentMobile={}, agentName={}, levelCode=1, storeName={}, parentMobile={}",
|
clinic.getContactPhone(), clinic.getName(), clinic.getName(), parentMobile);
|
JsonNode vzanAgentResponse = vzanHttpUtil.createAgent(
|
clinic.getContactPhone(), // agentMobile传门店的手机号
|
clinic.getName(), // agentName传门店的名称
|
1, // levelCode传1
|
parentMobile, // parentMobile传代理的contact_info
|
clinic.getName() // storeName传门店的name
|
);
|
log.info("微赞创建代理成功,出参={}", vzanAgentResponse);
|
|
} catch (Exception e) {
|
// 捕获微赞接口异常,不回滚本地事务
|
log.error("调用微赞接口失败,但本地数据已保存成功,clinicId={}", id, e);
|
throw new IllegalStateException("调用微赞失败", e);
|
}
|
|
return detail(id);
|
}
|
|
private Integer parseRegionCode(String code, String fieldName) {
|
if (code == null || code.trim().isEmpty()) {
|
throw new IllegalArgumentException(fieldName + "不能为空");
|
}
|
try {
|
return Integer.parseInt(code);
|
} catch (NumberFormatException e) {
|
throw new IllegalArgumentException(fieldName + "格式错误: " + code);
|
}
|
}
|
|
private void addCode(Set<String> codes, String code) {
|
String value = trimToNull(code);
|
if (value != null) {
|
codes.add(value);
|
}
|
}
|
|
private String regionName(String code, String fallback, Map<String, SysRegion> regions) {
|
String value = trimToNull(code);
|
SysRegion region = value == null ? null : regions.get(value);
|
return region == null ? trimToNull(fallback) : region.getName();
|
}
|
|
public PageResponse<ClinicBoundUserItemVO> clinicUsers(Long clinicId,
|
int page,
|
int pageSize,
|
String nickname,
|
String phone,
|
Integer status,
|
Integer isMember,
|
LocalDateTime registeredFrom,
|
LocalDateTime registeredTo) {
|
requireClinic(clinicId);
|
IPage<ClinicBoundUserRow> result = clinicQueryMapper.selectClinicUserPage(
|
new Page<ClinicBoundUserRow>(page, pageSize),
|
clinicId,
|
trimToNull(nickname),
|
trimToNull(phone),
|
status,
|
isMember,
|
registeredFrom,
|
registeredTo
|
);
|
List<ClinicBoundUserItemVO> list = new ArrayList<ClinicBoundUserItemVO>();
|
for (ClinicBoundUserRow row : result.getRecords()) {
|
ClinicBoundUserItemVO item = new ClinicBoundUserItemVO();
|
item.setId(row.getId());
|
item.setNickname(defaultText(row.getNickname()));
|
item.setPhone(row.getPhone() == null ? "-" : PhoneMasker.mask(row.getPhone()));
|
item.setAvatar(trimToNull(row.getAvatar()));
|
item.setLivePoints(row.getLivePoints() == null ? 0 : row.getLivePoints());
|
item.setIsMember(row.getIsMember() != null && row.getIsMember() == 1);
|
item.setStatus(row.getStatus());
|
item.setCreatedAt(row.getCreatedAt());
|
list.add(item);
|
}
|
return PageResponse.of(list, result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
private String trimToNull(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() ? null : trimmed;
|
}
|
|
private String defaultText(String value) {
|
return value == null || value.trim().isEmpty() ? "-" : value;
|
}
|
|
private ClinicListItemVO toListItem(ClinicListRow row) {
|
ClinicListItemVO item = new ClinicListItemVO();
|
item.setId(row.getId());
|
item.setName(defaultText(row.getName()));
|
item.setPhone(row.getPhone() == null ? "-" : PhoneMasker.mask(row.getPhone()));
|
item.setRegionName(defaultText(row.getRegionName()));
|
item.setStatus(row.getStatus());
|
item.setAgentName(defaultText(row.getAgentName()));
|
item.setUserCount(row.getUserCount() == null ? 0 : row.getUserCount());
|
item.setCreatedAt(row.getCreatedAt());
|
item.setBusinessLicenseUrl(trimToNull(row.getBusinessLicenseUrl()));
|
return item;
|
}
|
|
private void requireClinic(Long clinicId) {
|
if (clinicQueryMapper.selectClinicDetail(clinicId) == null) {
|
throw new IllegalArgumentException("诊所不存在");
|
}
|
}
|
|
private String statusText(Integer status) {
|
if (status != null && status == 1) {
|
return "启用";
|
}
|
if (status != null && status == 2) {
|
return "注销";
|
}
|
return "禁用";
|
}
|
}
|