package com.tcm.training.admin.app.patient.service;
|
|
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.tcm.training.admin.app.auth.service.AppAuthSessionService;
|
import com.tcm.training.admin.app.patient.mapper.AppMyPatientQueryMapper;
|
import com.tcm.training.admin.app.patient.row.AppMyPatientExchangeRow;
|
import com.tcm.training.admin.app.patient.row.AppMyPatientExchangeOrderItemRow;
|
import com.tcm.training.admin.app.patient.row.AppMyPatientLiveRow;
|
import com.tcm.training.admin.app.patient.row.AppMyPatientRow;
|
import com.tcm.training.admin.app.patient.vo.AppMyPatientExchangeItemVO;
|
import com.tcm.training.admin.app.patient.vo.AppMyPatientExchangeOrderItemVO;
|
import com.tcm.training.admin.app.patient.vo.AppMyPatientItemVO;
|
import com.tcm.training.admin.app.patient.vo.AppMyPatientLiveItemVO;
|
import com.tcm.training.admin.common.PageResponse;
|
import com.tcm.training.admin.member.entity.AppUser;
|
import com.tcm.training.admin.member.mapper.UserMapper;
|
import com.tcm.training.admin.points.entity.PointsExchangeOrder;
|
import com.tcm.training.admin.points.entity.PointsExchangeOrderItem;
|
import com.tcm.training.admin.points.mapper.PointsExchangeOrderItemMapper;
|
import com.tcm.training.admin.points.mapper.PointsExchangeOrderMapper;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeParseException;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
/**
|
* APP 我的患者聚合服务。
|
*/
|
@Service
|
public class AppMyPatientService {
|
|
private static final int USER_TYPE_PERSONAL = 0;
|
private static final int USER_TYPE_DOCTOR = 1;
|
private static final int USER_STATUS_ENABLED = 1;
|
private static final String BUSINESS_TYPE_LIVE_WATCH = "LIVE_WATCH";
|
private static final String INCOME_EXPENSE_TYPE_INCOME = "INCOME";
|
private static final int EXCHANGE_STATUS_PENDING = 0;
|
private static final int EXCHANGE_STATUS_SHIPPED = 1;
|
private static final int EXCHANGE_STATUS_COMPLETED = 2;
|
private static final int EXCHANGE_ITEM_STATUS_PENDING = 0;
|
private static final int EXCHANGE_ITEM_STATUS_PICKUP = 1;
|
private static final int EXCHANGE_ITEM_STATUS_COMPLETED = 2;
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
private final AppAuthSessionService appAuthSessionService;
|
private final UserMapper userMapper;
|
private final AppMyPatientQueryMapper appMyPatientQueryMapper;
|
private final PointsExchangeOrderMapper pointsExchangeOrderMapper;
|
private final PointsExchangeOrderItemMapper pointsExchangeOrderItemMapper;
|
|
/**
|
* 注入 APP 我的患者聚合所需依赖。
|
*/
|
public AppMyPatientService(AppAuthSessionService appAuthSessionService,
|
UserMapper userMapper,
|
AppMyPatientQueryMapper appMyPatientQueryMapper,
|
PointsExchangeOrderMapper pointsExchangeOrderMapper,
|
PointsExchangeOrderItemMapper pointsExchangeOrderItemMapper) {
|
this.appAuthSessionService = appAuthSessionService;
|
this.userMapper = userMapper;
|
this.appMyPatientQueryMapper = appMyPatientQueryMapper;
|
this.pointsExchangeOrderMapper = pointsExchangeOrderMapper;
|
this.pointsExchangeOrderItemMapper = pointsExchangeOrderItemMapper;
|
}
|
|
/**
|
* 分页查询直属患者列表。
|
*/
|
public PageResponse<AppMyPatientItemVO> pagePatients(int page, int pageSize, String keyword) {
|
Long doctorUserId = requireDoctorUserId();
|
IPage<AppMyPatientRow> result = appMyPatientQueryMapper.selectPatientPage(
|
new Page<AppMyPatientRow>(normalizePage(page), normalizePageSize(pageSize)),
|
doctorUserId,
|
trimToNull(keyword)
|
);
|
return PageResponse.of(toPatientItems(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
/**
|
* 分页查询直属患者直播积分记录。
|
*/
|
public PageResponse<AppMyPatientLiveItemVO> pageLiveRecords(int page,
|
int pageSize,
|
String keyword,
|
String startDate,
|
String endDate) {
|
Long doctorUserId = requireDoctorUserId();
|
IPage<AppMyPatientLiveRow> result = appMyPatientQueryMapper.selectLiveRecordPage(
|
new Page<AppMyPatientLiveRow>(normalizePage(page), normalizePageSize(pageSize)),
|
doctorUserId,
|
trimToNull(keyword),
|
parseStartDate(startDate),
|
parseEndDate(endDate),
|
BUSINESS_TYPE_LIVE_WATCH,
|
INCOME_EXPENSE_TYPE_INCOME
|
);
|
return PageResponse.of(toLiveItems(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
/**
|
* 分页查询直属患者兑换记录。
|
*/
|
public PageResponse<AppMyPatientExchangeItemVO> pageExchangeRecords(int page,
|
int pageSize,
|
String keyword,
|
String status) {
|
Long doctorUserId = requireDoctorUserId();
|
IPage<AppMyPatientExchangeRow> result = appMyPatientQueryMapper.selectExchangeRecordPage(
|
new Page<AppMyPatientExchangeRow>(normalizePage(page), normalizePageSize(pageSize)),
|
doctorUserId,
|
trimToNull(keyword),
|
normalizeExchangeStatus(status)
|
);
|
return PageResponse.of(toExchangeItems(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
/**
|
* 分页查询直属患者所属主订单下的子订单列表。
|
*/
|
public PageResponse<AppMyPatientExchangeOrderItemVO> pageExchangeOrderItems(Long orderId, int page, int pageSize) {
|
Long doctorUserId = requireDoctorUserId();
|
requireDoctorOrder(orderId, doctorUserId);
|
IPage<AppMyPatientExchangeOrderItemRow> result = appMyPatientQueryMapper.selectExchangeOrderItemPage(
|
new Page<AppMyPatientExchangeOrderItemRow>(normalizePage(page), normalizePageSize(pageSize)),
|
doctorUserId,
|
orderId
|
);
|
return PageResponse.of(toExchangeOrderItemList(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize());
|
}
|
|
/**
|
* 确认当前医生可查看的兑换主订单已收货。
|
*/
|
@Transactional
|
public void confirmExchangeOrderReceived(Long orderId) {
|
Long doctorUserId = requireDoctorUserId();
|
PointsExchangeOrder order = requireDoctorOrder(orderId, doctorUserId);
|
if (order.getStatus() == null || order.getStatus().intValue() != EXCHANGE_STATUS_SHIPPED) {
|
throw new IllegalArgumentException("当前订单不允许确认收货");
|
}
|
LocalDateTime now = LocalDateTime.now();
|
order.setStatus(EXCHANGE_STATUS_COMPLETED);
|
order.setCompletedAt(now);
|
pointsExchangeOrderMapper.updateById(order);
|
|
List<PointsExchangeOrderItem> items = pointsExchangeOrderItemMapper.selectList(new LambdaQueryWrapper<PointsExchangeOrderItem>()
|
.eq(PointsExchangeOrderItem::getDeleted, 0)
|
.eq(PointsExchangeOrderItem::getOrderId, orderId)
|
.isNull(PointsExchangeOrderItem::getCancelledAt));
|
for (PointsExchangeOrderItem item : items) {
|
if (item.getStatus() == null || item.getStatus().intValue() == EXCHANGE_ITEM_STATUS_COMPLETED) {
|
continue;
|
}
|
item.setStatus(EXCHANGE_ITEM_STATUS_PICKUP);
|
pointsExchangeOrderItemMapper.updateById(item);
|
}
|
}
|
|
/**
|
* 校验当前登录用户是启用中的医生账号。
|
*/
|
private Long requireDoctorUserId() {
|
Long userId = appAuthSessionService.requireLoginUserId();
|
AppUser user = userMapper.selectById(userId);
|
if (user == null || user.getDeleted() == null || user.getDeleted().intValue() != 0) {
|
throw new IllegalArgumentException("用户不存在");
|
}
|
if (user.getStatus() == null || user.getStatus().intValue() != USER_STATUS_ENABLED) {
|
throw new IllegalArgumentException("用户已禁用");
|
}
|
if (user.getType() == null || user.getType().intValue() != USER_TYPE_DOCTOR) {
|
throw new IllegalArgumentException("当前用户无权查看患者数据");
|
}
|
return userId;
|
}
|
|
/**
|
* 将患者列表查询结果转换为 APP 页面展示结构。
|
*/
|
private List<AppMyPatientItemVO> toPatientItems(List<AppMyPatientRow> rows) {
|
List<AppMyPatientItemVO> items = new ArrayList<AppMyPatientItemVO>();
|
if (rows == null) {
|
return items;
|
}
|
for (AppMyPatientRow row : rows) {
|
AppMyPatientItemVO item = new AppMyPatientItemVO();
|
item.setId(row.getId());
|
item.setNickname(resolveNickname(row.getNickname(), row.getPhone()));
|
item.setAvatar(trimToEmpty(row.getAvatar()));
|
item.setCreatedAt(row.getCreatedAt());
|
items.add(item);
|
}
|
return items;
|
}
|
|
/**
|
* 将直播积分记录转换为 APP 页面展示结构。
|
*/
|
private List<AppMyPatientLiveItemVO> toLiveItems(List<AppMyPatientLiveRow> rows) {
|
List<AppMyPatientLiveItemVO> items = new ArrayList<AppMyPatientLiveItemVO>();
|
if (rows == null) {
|
return items;
|
}
|
for (AppMyPatientLiveRow row : rows) {
|
AppMyPatientLiveItemVO item = new AppMyPatientLiveItemVO();
|
item.setId(row.getId());
|
item.setPatientId(row.getPatientId());
|
item.setNickname(resolveNickname(row.getPatientNickname(), row.getPatientPhone()));
|
item.setAvatar(trimToEmpty(row.getPatientAvatar()));
|
item.setCourseTitle(trimToEmpty(row.getCourseTitle()));
|
item.setOperatedAt(row.getOperatedAt());
|
item.setPoints(normalizeNumber(row.getPoints()));
|
items.add(item);
|
}
|
return items;
|
}
|
|
/**
|
* 将兑换数据查询结果转换为 APP 页面展示结构。
|
*/
|
private List<AppMyPatientExchangeItemVO> toExchangeItems(List<AppMyPatientExchangeRow> rows) {
|
List<AppMyPatientExchangeItemVO> items = new ArrayList<AppMyPatientExchangeItemVO>();
|
if (rows == null) {
|
return items;
|
}
|
for (AppMyPatientExchangeRow row : rows) {
|
AppMyPatientExchangeItemVO item = new AppMyPatientExchangeItemVO();
|
item.setId(row.getId());
|
item.setOrderNo(trimToEmpty(row.getOrderNo()));
|
item.setGiftName(trimToEmpty(row.getGiftName()));
|
item.setGiftCover(trimToEmpty(row.getGiftCover()));
|
item.setNickname(trimToEmpty(row.getNickname()));
|
item.setActivityPeriodText(buildActivityPeriodText(row.getActivityStartAt(), row.getActivityEndAt()));
|
item.setStatus(row.getStatus());
|
item.setStatusText(resolveExchangeStatusText(row.getStatus()));
|
item.setCount(normalizeNumber(row.getCount()));
|
item.setCanConfirmReceived(Boolean.valueOf(Boolean.TRUE.equals(row.getCanConfirmReceived())));
|
items.add(item);
|
}
|
return items;
|
}
|
|
/**
|
* 将主订单下子订单列表转换为 APP 页面展示结构。
|
*/
|
private List<AppMyPatientExchangeOrderItemVO> toExchangeOrderItemList(List<AppMyPatientExchangeOrderItemRow> rows) {
|
List<AppMyPatientExchangeOrderItemVO> items = new ArrayList<AppMyPatientExchangeOrderItemVO>();
|
if (rows == null) {
|
return items;
|
}
|
for (AppMyPatientExchangeOrderItemRow row : rows) {
|
AppMyPatientExchangeOrderItemVO item = new AppMyPatientExchangeOrderItemVO();
|
item.setId(row.getId());
|
item.setOrderId(row.getOrderId());
|
item.setPatientId(row.getPatientId());
|
item.setPatientName(trimToEmpty(row.getPatientName()));
|
item.setPatientPhone(trimToEmpty(row.getPatientPhone()));
|
item.setSettledPoints(normalizeNumber(row.getSettledPoints()));
|
item.setStatus(row.getStatus());
|
item.setStatusText(resolveExchangeItemStatusText(row.getStatus()));
|
item.setCreatedAt(row.getCreatedAt());
|
items.add(item);
|
}
|
return items;
|
}
|
|
/**
|
* 将兑换页状态筛选值转换为数据库状态码。
|
*/
|
private Integer normalizeExchangeStatus(String status) {
|
String normalized = trimToNull(status);
|
if (normalized == null || "all".equalsIgnoreCase(normalized)) {
|
return null;
|
}
|
if ("pending".equalsIgnoreCase(normalized)) {
|
return Integer.valueOf(EXCHANGE_STATUS_PENDING);
|
}
|
if ("received".equalsIgnoreCase(normalized)) {
|
return Integer.valueOf(EXCHANGE_STATUS_SHIPPED);
|
}
|
throw new IllegalArgumentException("兑换状态不合法");
|
}
|
|
/**
|
* 解析列表日期筛选的开始时间。
|
*/
|
private LocalDateTime parseStartDate(String value) {
|
String normalized = trimToNull(value);
|
if (normalized == null) {
|
return null;
|
}
|
try {
|
return LocalDate.parse(normalized, DATE_FORMATTER).atStartOfDay();
|
} catch (DateTimeParseException ex) {
|
throw new IllegalArgumentException("开始日期格式不正确");
|
}
|
}
|
|
/**
|
* 解析列表日期筛选的结束时间,并自动补到当天结束。
|
*/
|
private LocalDateTime parseEndDate(String value) {
|
String normalized = trimToNull(value);
|
if (normalized == null) {
|
return null;
|
}
|
try {
|
return LocalDate.parse(normalized, DATE_FORMATTER).plusDays(1).atStartOfDay().minusSeconds(1);
|
} catch (DateTimeParseException ex) {
|
throw new IllegalArgumentException("结束日期格式不正确");
|
}
|
}
|
|
/**
|
* 拼接兑换活动期数字段展示文案。
|
*/
|
private String buildActivityPeriodText(LocalDateTime activityStartAt, LocalDateTime activityEndAt) {
|
String startText = formatDate(activityStartAt);
|
String endText = formatDate(activityEndAt);
|
if (startText.isEmpty() && endText.isEmpty()) {
|
return "";
|
}
|
return startText + "-" + endText;
|
}
|
|
/**
|
* 将日期时间格式化为页面期数字段所需的 yyyy.MM.dd。
|
*/
|
private String formatDate(LocalDateTime value) {
|
if (value == null) {
|
return "";
|
}
|
return value.toLocalDate().toString().replace('-', '.');
|
}
|
|
/**
|
* 解析兑换记录状态文案。
|
*/
|
private String resolveExchangeStatusText(Integer status) {
|
if (status != null && status.intValue() == EXCHANGE_STATUS_PENDING) {
|
return "待发货";
|
}
|
if (status != null && status.intValue() == EXCHANGE_STATUS_SHIPPED) {
|
return "待收货";
|
}
|
return "已收货";
|
}
|
|
/**
|
* 解析兑换子订单状态文案。
|
*/
|
private String resolveExchangeItemStatusText(Integer status) {
|
if (status != null && status.intValue() == EXCHANGE_ITEM_STATUS_PENDING) {
|
return "待发货";
|
}
|
if (status != null && status.intValue() == EXCHANGE_ITEM_STATUS_PICKUP) {
|
return "待取货";
|
}
|
return "兑换完成";
|
}
|
|
/**
|
* 校验并返回当前医生可操作的兑换主订单。
|
*/
|
private PointsExchangeOrder requireDoctorOrder(Long orderId, Long doctorUserId) {
|
if (orderId == null) {
|
throw new IllegalArgumentException("兑换订单不存在");
|
}
|
PointsExchangeOrder order = pointsExchangeOrderMapper.selectOne(new LambdaQueryWrapper<PointsExchangeOrder>()
|
.eq(PointsExchangeOrder::getId, orderId)
|
.eq(PointsExchangeOrder::getDeleted, 0)
|
.last("limit 1"));
|
if (order == null) {
|
throw new IllegalArgumentException("兑换订单不存在");
|
}
|
Long count = pointsExchangeOrderItemMapper.selectCount(new LambdaQueryWrapper<PointsExchangeOrderItem>()
|
.eq(PointsExchangeOrderItem::getDeleted, 0)
|
.eq(PointsExchangeOrderItem::getOrderId, orderId)
|
.isNull(PointsExchangeOrderItem::getCancelledAt)
|
.inSql(PointsExchangeOrderItem::getAppUserId,
|
"select id from tcm_app_user where deleted = 0 and type = 0 and superior_clinic_user_id = " + doctorUserId));
|
if (count == null || count.longValue() <= 0L) {
|
throw new IllegalArgumentException("兑换订单不存在");
|
}
|
return order;
|
}
|
|
/**
|
* 用昵称优先、手机号兜底的策略统一展示患者名称。
|
*/
|
private String resolveNickname(String nickname, String phone) {
|
String normalizedNickname = trimToEmpty(nickname);
|
if (!normalizedNickname.isEmpty()) {
|
return normalizedNickname;
|
}
|
return trimToEmpty(phone);
|
}
|
|
/**
|
* 将空白文本统一转换为 null。
|
*/
|
private String trimToNull(String value) {
|
String text = trimToEmpty(value);
|
return text.isEmpty() ? null : text;
|
}
|
|
/**
|
* 将可空文本归一化为空字符串。
|
*/
|
private String trimToEmpty(String value) {
|
if (value == null) {
|
return "";
|
}
|
return value.trim();
|
}
|
|
/**
|
* 将分页页码归一化到合法范围。
|
*/
|
private int normalizePage(int page) {
|
return page <= 0 ? 1 : page;
|
}
|
|
/**
|
* 将分页大小归一化到合法范围。
|
*/
|
private int normalizePageSize(int pageSize) {
|
return pageSize <= 0 ? 10 : pageSize;
|
}
|
|
/**
|
* 将积分数值归一化,避免返回空值影响 APP 展示。
|
*/
|
private Integer normalizeNumber(Integer value) {
|
return value == null ? Integer.valueOf(0) : value;
|
}
|
}
|