package com.tcm.training.admin.points.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.tcm.training.admin.common.DateTimes;
|
import com.tcm.training.admin.common.PhoneMasker;
|
import com.tcm.training.admin.points.entity.PointsLedger;
|
import com.tcm.training.admin.points.excel.PointsLedgerExcelRow;
|
import com.tcm.training.admin.points.mapper.PointsLedgerMapper;
|
import com.tcm.training.admin.points.row.PointsLedgerRow;
|
import com.tcm.training.admin.points.row.PointsLedgerSummaryRow;
|
import com.tcm.training.admin.points.row.PointsLedgerSyncExchangeRow;
|
import com.tcm.training.admin.points.row.PointsLedgerSyncQuizRow;
|
import com.tcm.training.admin.points.row.PointsRegionOptionRow;
|
import com.tcm.training.admin.points.vo.PointsLedgerItemVO;
|
import com.tcm.training.admin.points.vo.PointsLedgerPageVO;
|
import com.tcm.training.admin.points.vo.PointsLedgerSummaryVO;
|
import com.tcm.training.admin.points.vo.PointsRegionOptionVO;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.net.URLEncoder;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.List;
|
|
@Service
|
public class PointsLedgerAppService {
|
|
public static final String TYPE_INCOME = "INCOME";
|
public static final String TYPE_EXPENSE = "EXPENSE";
|
public static final String BUSINESS_LIVE_WATCH = "LIVE_WATCH";
|
public static final String BUSINESS_POINTS_EXCHANGE = "POINTS_EXCHANGE";
|
public static final String BUSINESS_POINTS_SETTLEMENT = "POINTS_SETTLEMENT";
|
public static final String SOURCE_LIVE_QUIZ = "LIVE_QUIZ";
|
public static final String SOURCE_EXCHANGE_ITEM = "EXCHANGE_ITEM";
|
public static final String SOURCE_POINTS_SETTLEMENT = "POINTS_SETTLEMENT";
|
public static final String REF_LIVE_COURSE = "LIVE_COURSE";
|
public static final String REF_EXCHANGE_ITEM = "EXCHANGE_ITEM";
|
public static final String REF_POINTS_CONFIG = "POINTS_CONFIG";
|
|
private final PointsLedgerMapper pointsLedgerMapper;
|
|
public PointsLedgerAppService(PointsLedgerMapper pointsLedgerMapper) {
|
this.pointsLedgerMapper = pointsLedgerMapper;
|
}
|
|
public PointsLedgerPageVO page(int page,
|
int pageSize,
|
String userPhone,
|
String regionCode,
|
String clinicName,
|
String incomeExpenseType,
|
String businessType,
|
String contentKeyword,
|
String referenceNo,
|
LocalDateTime operatedFrom,
|
LocalDateTime operatedTo) {
|
syncLedger();
|
IPage<PointsLedgerRow> result = pointsLedgerMapper.selectLedgerPage(
|
new Page<PointsLedgerRow>(page, pageSize),
|
trimToNull(userPhone),
|
trimToNull(regionCode),
|
trimToNull(clinicName),
|
trimToNull(incomeExpenseType),
|
trimToNull(businessType),
|
trimToNull(contentKeyword),
|
trimToNull(referenceNo),
|
operatedFrom,
|
operatedTo
|
);
|
PointsLedgerPageVO vo = new PointsLedgerPageVO();
|
vo.setList(toItems(result.getRecords()));
|
vo.setTotal(result.getTotal());
|
vo.setPage(result.getCurrent());
|
vo.setPageSize(result.getSize());
|
vo.setSummary(buildSummary(userPhone, regionCode, clinicName, incomeExpenseType, businessType, contentKeyword, referenceNo, operatedFrom, operatedTo));
|
return vo;
|
}
|
|
public void export(HttpServletResponse response,
|
String userPhone,
|
String regionCode,
|
String clinicName,
|
String incomeExpenseType,
|
String businessType,
|
String contentKeyword,
|
String referenceNo,
|
LocalDateTime operatedFrom,
|
LocalDateTime operatedTo) throws IOException {
|
syncLedger();
|
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<PointsLedgerRow> rows = pointsLedgerMapper.selectLedgerList(
|
trimToNull(userPhone),
|
trimToNull(regionCode),
|
trimToNull(clinicName),
|
trimToNull(incomeExpenseType),
|
trimToNull(businessType),
|
trimToNull(contentKeyword),
|
trimToNull(referenceNo),
|
operatedFrom,
|
operatedTo
|
);
|
List<PointsLedgerExcelRow> exports = new ArrayList<PointsLedgerExcelRow>();
|
for (int i = 0; i < rows.size(); i++) {
|
PointsLedgerRow row = rows.get(i);
|
PointsLedgerExcelRow export = new PointsLedgerExcelRow();
|
export.setSerialNo(i + 1);
|
export.setUserNickname(defaultText(row.getUserNickname()));
|
export.setUserPhone(PhoneMasker.mask(row.getUserPhone()));
|
export.setClinicRegion(defaultText(row.getClinicRegion()));
|
export.setClinicName(defaultText(row.getClinicName()));
|
export.setIncomeExpenseType(incomeExpenseText(row.getIncomeExpenseType()));
|
export.setBusinessType(businessTypeText(row.getBusinessType()));
|
export.setContentName(defaultText(row.getContentName()));
|
export.setReferenceNo(defaultText(row.getReferenceNo()));
|
export.setPointsDelta(formatSignedPoints(row.getPointsDelta()));
|
export.setOperatedAt(formatMinute(row.getOperatedAt()));
|
exports.add(export);
|
}
|
FastExcel.write(response.getOutputStream(), PointsLedgerExcelRow.class)
|
.sheet("积分收支明细")
|
.doWrite(exports);
|
}
|
|
public List<PointsRegionOptionVO> regionOptions() {
|
List<PointsRegionOptionRow> rows = pointsLedgerMapper.selectRegionOptions();
|
List<PointsRegionOptionVO> list = new ArrayList<PointsRegionOptionVO>();
|
for (PointsRegionOptionRow row : rows) {
|
list.add(new PointsRegionOptionVO(row.getRegionCode(), row.getRegionName()));
|
}
|
return list;
|
}
|
|
@Transactional
|
public void syncLedger() {
|
syncQuizLedgers();
|
syncExchangeLedgers();
|
}
|
|
@Transactional
|
public void createExchangeExpenseLedger(Long sourceId,
|
Long appUserId,
|
String userNickname,
|
String userPhone,
|
Long clinicId,
|
String clinicName,
|
String provinceCode,
|
String provinceName,
|
String cityCode,
|
String cityName,
|
String districtCode,
|
String districtName,
|
String contentName,
|
Long referenceId,
|
String referenceNo,
|
Integer pointsDelta,
|
LocalDateTime operatedAt) {
|
if (sourceId == null) {
|
return;
|
}
|
PointsLedger exists = pointsLedgerMapper.selectOne(new LambdaQueryWrapper<PointsLedger>()
|
.eq(PointsLedger::getDeleted, 0)
|
.eq(PointsLedger::getSourceType, SOURCE_EXCHANGE_ITEM)
|
.eq(PointsLedger::getSourceId, sourceId)
|
.last("limit 1"));
|
if (exists != null) {
|
return;
|
}
|
PointsLedger ledger = buildBaseLedger(appUserId, userNickname, userPhone, clinicId, clinicName,
|
provinceCode, provinceName, cityCode, cityName, districtCode, districtName);
|
ledger.setIncomeExpenseType(TYPE_EXPENSE);
|
ledger.setBusinessType(BUSINESS_POINTS_EXCHANGE);
|
ledger.setContentName(contentName);
|
ledger.setReferenceType(REF_EXCHANGE_ITEM);
|
ledger.setReferenceId(referenceId);
|
ledger.setReferenceNo(referenceNo);
|
ledger.setPointsDelta(pointsDelta == null ? 0 : -Math.abs(pointsDelta));
|
ledger.setOperatedAt(operatedAt == null ? LocalDateTime.now() : operatedAt);
|
ledger.setSourceType(SOURCE_EXCHANGE_ITEM);
|
ledger.setSourceId(sourceId);
|
pointsLedgerMapper.insert(ledger);
|
}
|
|
/**
|
* 记录积分活动结束后未达标用户被系统清零的支出流水。
|
*/
|
@Transactional
|
public void createSettlementClearLedger(Long sourceId,
|
Long appUserId,
|
String userNickname,
|
String userPhone,
|
Long clinicId,
|
String clinicName,
|
String provinceCode,
|
String provinceName,
|
String cityCode,
|
String cityName,
|
String districtCode,
|
String districtName,
|
String contentName,
|
Long referenceId,
|
String referenceNo,
|
Integer pointsDelta,
|
LocalDateTime operatedAt) {
|
if (sourceId == null) {
|
return;
|
}
|
PointsLedger exists = pointsLedgerMapper.selectOne(new LambdaQueryWrapper<PointsLedger>()
|
.eq(PointsLedger::getDeleted, 0)
|
.eq(PointsLedger::getSourceType, SOURCE_POINTS_SETTLEMENT)
|
.eq(PointsLedger::getSourceId, sourceId)
|
.last("limit 1"));
|
if (exists != null) {
|
return;
|
}
|
PointsLedger ledger = buildBaseLedger(appUserId, userNickname, userPhone, clinicId, clinicName,
|
provinceCode, provinceName, cityCode, cityName, districtCode, districtName);
|
ledger.setIncomeExpenseType(TYPE_EXPENSE);
|
ledger.setBusinessType(BUSINESS_POINTS_SETTLEMENT);
|
ledger.setContentName(contentName);
|
ledger.setReferenceType(REF_POINTS_CONFIG);
|
ledger.setReferenceId(referenceId);
|
ledger.setReferenceNo(referenceNo);
|
ledger.setPointsDelta(pointsDelta == null ? 0 : -Math.abs(pointsDelta));
|
ledger.setOperatedAt(operatedAt == null ? LocalDateTime.now() : operatedAt);
|
ledger.setSourceType(SOURCE_POINTS_SETTLEMENT);
|
ledger.setSourceId(sourceId);
|
pointsLedgerMapper.insert(ledger);
|
}
|
|
private void syncQuizLedgers() {
|
List<PointsLedgerSyncQuizRow> rows = pointsLedgerMapper.selectMissingQuizLedgerRows();
|
for (PointsLedgerSyncQuizRow row : rows) {
|
PointsLedger ledger = buildBaseLedger(row.getAppUserId(), row.getUserNickname(), row.getUserPhone(),
|
row.getClinicId(), row.getClinicName(), row.getProvinceCode(), row.getProvinceName(),
|
row.getCityCode(), row.getCityName(), row.getDistrictCode(), row.getDistrictName());
|
ledger.setIncomeExpenseType(TYPE_INCOME);
|
ledger.setBusinessType(BUSINESS_LIVE_WATCH);
|
ledger.setContentName(row.getContentName());
|
ledger.setReferenceType(REF_LIVE_COURSE);
|
ledger.setReferenceId(row.getReferenceId());
|
ledger.setReferenceNo(row.getReferenceNo());
|
ledger.setPointsDelta(Math.abs(row.getPointsDelta() == null ? 0 : row.getPointsDelta()));
|
ledger.setOperatedAt(row.getOperatedAt());
|
ledger.setSourceType(SOURCE_LIVE_QUIZ);
|
ledger.setSourceId(row.getSourceId());
|
pointsLedgerMapper.insert(ledger);
|
}
|
}
|
|
private void syncExchangeLedgers() {
|
List<PointsLedgerSyncExchangeRow> rows = pointsLedgerMapper.selectMissingExchangeLedgerRows();
|
for (PointsLedgerSyncExchangeRow row : rows) {
|
createExchangeExpenseLedger(
|
row.getSourceId(),
|
row.getAppUserId(),
|
row.getUserNickname(),
|
row.getUserPhone(),
|
row.getClinicId(),
|
row.getClinicName(),
|
row.getProvinceCode(),
|
row.getProvinceName(),
|
row.getCityCode(),
|
row.getCityName(),
|
row.getDistrictCode(),
|
row.getDistrictName(),
|
row.getContentName(),
|
row.getReferenceId(),
|
row.getReferenceNo(),
|
row.getPointsDelta(),
|
row.getOperatedAt()
|
);
|
}
|
}
|
|
private PointsLedgerSummaryVO buildSummary(String userPhone,
|
String regionCode,
|
String clinicName,
|
String incomeExpenseType,
|
String businessType,
|
String contentKeyword,
|
String referenceNo,
|
LocalDateTime operatedFrom,
|
LocalDateTime operatedTo) {
|
PointsLedgerSummaryRow row = pointsLedgerMapper.selectLedgerSummary(
|
trimToNull(userPhone),
|
trimToNull(regionCode),
|
trimToNull(clinicName),
|
trimToNull(incomeExpenseType),
|
trimToNull(businessType),
|
trimToNull(contentKeyword),
|
trimToNull(referenceNo),
|
operatedFrom,
|
operatedTo
|
);
|
PointsLedgerSummaryVO vo = new PointsLedgerSummaryVO();
|
vo.setIncomeTotal(row == null || row.getIncomeTotal() == null ? 0 : row.getIncomeTotal());
|
vo.setExpenseTotal(row == null || row.getExpenseTotal() == null ? 0 : row.getExpenseTotal());
|
vo.setLiveWatchIncomeTotal(row == null || row.getLiveWatchIncomeTotal() == null ? 0 : row.getLiveWatchIncomeTotal());
|
vo.setExchangeExpenseTotal(row == null || row.getExchangeExpenseTotal() == null ? 0 : row.getExchangeExpenseTotal());
|
return vo;
|
}
|
|
private List<PointsLedgerItemVO> toItems(List<PointsLedgerRow> rows) {
|
List<PointsLedgerItemVO> list = new ArrayList<PointsLedgerItemVO>();
|
for (PointsLedgerRow row : rows) {
|
PointsLedgerItemVO item = new PointsLedgerItemVO();
|
item.setId(row.getId());
|
item.setUserNickname(row.getUserNickname());
|
item.setUserPhone(PhoneMasker.mask(row.getUserPhone()));
|
item.setClinicRegion(row.getClinicRegion());
|
item.setClinicName(row.getClinicName());
|
item.setIncomeExpenseType(incomeExpenseText(row.getIncomeExpenseType()));
|
item.setBusinessType(businessTypeText(row.getBusinessType()));
|
item.setContentName(row.getContentName());
|
item.setReferenceNo(row.getReferenceNo());
|
item.setPointsDelta(row.getPointsDelta());
|
item.setOperatedAt(row.getOperatedAt());
|
item.setReferencePath(row.getReferencePath());
|
item.setCanJump(row.getCanJump() != null && row.getCanJump() == 1);
|
list.add(item);
|
}
|
return list;
|
}
|
|
private PointsLedger buildBaseLedger(Long appUserId,
|
String userNickname,
|
String userPhone,
|
Long clinicId,
|
String clinicName,
|
String provinceCode,
|
String provinceName,
|
String cityCode,
|
String cityName,
|
String districtCode,
|
String districtName) {
|
PointsLedger ledger = new PointsLedger();
|
ledger.setAppUserId(appUserId);
|
ledger.setUserNickname(userNickname);
|
ledger.setUserPhone(userPhone);
|
ledger.setClinicId(clinicId);
|
ledger.setClinicName(clinicName);
|
ledger.setProvinceCode(provinceCode);
|
ledger.setProvinceName(provinceName);
|
ledger.setCityCode(cityCode);
|
ledger.setCityName(cityName);
|
ledger.setDistrictCode(districtCode);
|
ledger.setDistrictName(districtName);
|
return ledger;
|
}
|
|
private String incomeExpenseText(String value) {
|
if (TYPE_INCOME.equals(value)) {
|
return "收入";
|
}
|
if (TYPE_EXPENSE.equals(value)) {
|
return "支出";
|
}
|
return "-";
|
}
|
|
private String businessTypeText(String value) {
|
if (BUSINESS_LIVE_WATCH.equals(value)) {
|
return "观看直播";
|
}
|
if (BUSINESS_POINTS_EXCHANGE.equals(value)) {
|
return "积分兑换";
|
}
|
if (BUSINESS_POINTS_SETTLEMENT.equals(value)) {
|
return "活动结算";
|
}
|
return "-";
|
}
|
|
private String formatSignedPoints(Integer value) {
|
int points = value == null ? 0 : value;
|
return points > 0 ? "+" + points : String.valueOf(points);
|
}
|
|
private String formatMinute(LocalDateTime value) {
|
if (value == null) {
|
return "-";
|
}
|
return value.format(DateTimes.localDateTimeFormatter()).substring(0, 16);
|
}
|
|
private String trimToNull(String text) {
|
if (text == null) {
|
return null;
|
}
|
String value = text.trim();
|
return value.isEmpty() ? null : value;
|
}
|
|
private String defaultText(String text) {
|
if (text == null || text.trim().isEmpty()) {
|
return "-";
|
}
|
return text;
|
}
|
}
|