package com.talentcard.server.service;
|
|
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeParseException;
|
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.talentcard.server.business.PageResult;
|
import com.talentcard.server.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import com.talentcard.server.entity.TransportParkingPlateRegistration;
|
import com.talentcard.server.mapper.TransportParkingPlateRegistrationMapper;
|
import com.talentcard.server.request.ParkingPlateStatusUpdateRequest;
|
import org.apache.poi.ss.usermodel.CellStyle;
|
import org.apache.poi.ss.usermodel.FillPatternType;
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class ParkingPlateSubmissionAdminService {
|
private static final String STATUS_SUBMITTED = "SUBMITTED";
|
private static final String STATUS_ACTIVE = "ACTIVE";
|
private static final String STATUS_REJECTED = "REJECTED";
|
private static final String STATUS_CANCELLED = "CANCELLED";
|
|
private final TransportParkingPlateRegistrationMapper registrationMapper;
|
|
public ParkingPlateSubmissionAdminService(TransportParkingPlateRegistrationMapper registrationMapper) {
|
this.registrationMapper = registrationMapper;
|
}
|
|
@Transactional(readOnly = true)
|
public PageResult<Map<String, Object>> page(Long merchantId,
|
Long parkingLotId,
|
String keyword,
|
String serviceKeyword,
|
String cardLevel,
|
String status,
|
String submissionType,
|
String submittedStart,
|
String submittedEnd,
|
int page,
|
int size) {
|
int safePage = Math.max(page, 1);
|
int safeSize = Math.min(Math.max(size, 1), 100);
|
LocalDateTime start = startTime(submittedStart, "提交开始日期格式不正确");
|
LocalDateTime end = endTime(submittedEnd, "提交结束日期格式不正确");
|
String normalizedStatus = normalizeFilterStatus(status);
|
String normalizedSubmissionType = normalizeSubmissionType(submissionType, false);
|
Long total = registrationMapper.countAdminPage(merchantId, parkingLotId, clean(keyword), clean(serviceKeyword), clean(cardLevel), normalizedStatus, normalizedSubmissionType, start, end);
|
List<Map<String, Object>> records = registrationMapper.selectAdminPage(
|
merchantId, parkingLotId, clean(keyword), clean(serviceKeyword), clean(cardLevel), normalizedStatus,
|
normalizedSubmissionType, start, end, safeSize, (safePage - 1) * safeSize)
|
.stream()
|
.map(this::listView)
|
.toList();
|
return new PageResult<>(records, safePage, safeSize, total == null ? 0 : total);
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long id) {
|
Map<String, Object> row = detailRow(id);
|
Map<String, Object> result = listView(row);
|
|
Map<String, Object> talent = new LinkedHashMap<>();
|
talent.put("talentId", value(row, "talentId", "talent_id"));
|
talent.put("talentName", text(row, "talentName", "talent_name"));
|
talent.put("talentMobile", text(row, "talentMobile", "talent_mobile"));
|
talent.put("cardId", value(row, "cardId", "card_id"));
|
talent.put("cardNo", text(row, "cardNo", "card_no"));
|
talent.put("cardLevel", text(row, "cardLevel", "card_level"));
|
result.put("talentInfo", talent);
|
|
Map<String, Object> submission = new LinkedHashMap<>();
|
submission.put("plateNumber", text(row, "plateNumber", "plate_number"));
|
submission.put("merchantId", value(row, "merchantId", "merchant_id"));
|
submission.put("merchantName", text(row, "merchantName", "merchant_name"));
|
submission.put("parkingLotId", value(row, "parkingLotId", "parking_lot_id"));
|
submission.put("parkingLotName", text(row, "parkingLotName", "parking_lot_name"));
|
submission.put("parkingLotAddress", text(row, "parkingLotAddress", "parking_lot_address"));
|
submission.put("submissionType", text(row, "submissionType", "submission_type"));
|
submission.put("submittedAt", value(row, "submittedAt", "submitted_at"));
|
result.put("submissionInfo", submission);
|
|
Map<String, Object> statusInfo = new LinkedHashMap<>();
|
statusInfo.put("status", text(row, "status"));
|
statusInfo.put("statusName", statusName(text(row, "status")));
|
statusInfo.put("handledAt", value(row, "handledAt", "handled_at"));
|
statusInfo.put("handledBy", value(row, "handledBy", "handled_by"));
|
statusInfo.put("handledByName", first(text(row, "handledByName", "handled_by_name"), handlerFallback(value(row, "handledBy", "handled_by"))));
|
statusInfo.put("handleRemark", text(row, "handleRemark", "handle_remark"));
|
result.put("statusInfo", statusInfo);
|
return result;
|
}
|
|
@Transactional
|
public Map<String, Object> updateStatus(Long id, ParkingPlateStatusUpdateRequest request, Long operatorId) {
|
Long registrationId = requireId(id);
|
TransportParkingPlateRegistration current = registrationMapper.selectByIdForUpdate(registrationId);
|
if (current == null) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "车牌提交记录不存在");
|
}
|
String nextStatus = normalizeStatus(request.status());
|
String remark = clean(request.remark());
|
LocalDateTime handledAt = STATUS_SUBMITTED.equals(nextStatus) ? null : LocalDateTime.now();
|
UpdateWrapper<TransportParkingPlateRegistration> update = new UpdateWrapper<TransportParkingPlateRegistration>()
|
.set("status", nextStatus)
|
.set("handled_by", handledAt == null ? null : operatorId)
|
.set("handled_at", handledAt)
|
.set("handle_remark", remark.isBlank() ? null : remark)
|
.eq("id", registrationId)
|
.eq("deleted", 0);
|
int updated = registrationMapper.update(null, update);
|
if (updated != 1) {
|
throw new BusinessException(ErrorCode.CONFLICT, "车牌提交记录状态已变化,请刷新后重试");
|
}
|
return detail(registrationId);
|
}
|
|
@Transactional(readOnly = true)
|
public byte[] exportExcel(Long merchantId,
|
Long parkingLotId,
|
String keyword,
|
String serviceKeyword,
|
String cardLevel,
|
String status,
|
String submissionType,
|
String submittedStart,
|
String submittedEnd) {
|
LocalDateTime start = startTime(submittedStart, "提交开始日期格式不正确");
|
LocalDateTime end = endTime(submittedEnd, "提交结束日期格式不正确");
|
List<Map<String, Object>> records = registrationMapper.selectAdminPage(
|
merchantId, parkingLotId, clean(keyword), clean(serviceKeyword), clean(cardLevel), normalizeFilterStatus(status),
|
normalizeSubmissionType(submissionType, false), start, end, 200000, 0);
|
String[] headers = {
|
"序号", "提交单号", "持卡人名称", "持卡人电话", "人才等级", "人才卡号", "车牌号", "停车商家", "停车场",
|
"提交时间", "提交类型", "状态", "处理人", "处理时间", "处理备注"
|
};
|
try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
Sheet sheet = workbook.createSheet("车牌提交");
|
sheet.createFreezePane(0, 1);
|
CellStyle headerStyle = headerStyle(workbook);
|
Row headerRow = sheet.createRow(0);
|
for (int index = 0; index < headers.length; index++) {
|
headerRow.createCell(index).setCellValue(headers[index]);
|
headerRow.getCell(index).setCellStyle(headerStyle);
|
}
|
int rowIndex = 1;
|
for (Map<String, Object> record : records) {
|
Row row = sheet.createRow(rowIndex);
|
List<String> values = exportValues(rowIndex, record);
|
for (int index = 0; index < values.size(); index++) {
|
row.createCell(index).setCellValue(values.get(index));
|
}
|
rowIndex++;
|
}
|
int[] widths = { 8, 24, 16, 18, 16, 20, 16, 28, 28, 24, 12, 12, 16, 24, 36 };
|
for (int index = 0; index < widths.length; index++) {
|
sheet.setColumnWidth(index, widths[index] * 256);
|
}
|
workbook.write(output);
|
return output.toByteArray();
|
} catch (IOException exception) {
|
throw new BusinessException(ErrorCode.INTERNAL_ERROR, "车牌提交导出失败");
|
}
|
}
|
|
private Map<String, Object> listView(Map<String, Object> row) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
String status = text(row, "status");
|
result.put("id", value(row, "id"));
|
result.put("registrationNo", text(row, "registrationNo", "registration_no"));
|
result.put("talentName", text(row, "talentName", "talent_name"));
|
result.put("talentMobile", text(row, "talentMobile", "talent_mobile"));
|
result.put("cardLevel", text(row, "cardLevel", "card_level"));
|
result.put("cardNo", text(row, "cardNo", "card_no"));
|
result.put("plateNumber", text(row, "plateNumber", "plate_number"));
|
result.put("merchantId", value(row, "merchantId", "merchant_id"));
|
result.put("merchantName", text(row, "merchantName", "merchant_name"));
|
result.put("parkingLotId", value(row, "parkingLotId", "parking_lot_id"));
|
result.put("parkingLotName", text(row, "parkingLotName", "parking_lot_name"));
|
result.put("submittedAt", value(row, "submittedAt", "submitted_at"));
|
result.put("submissionType", text(row, "submissionType", "submission_type"));
|
result.put("status", status);
|
result.put("statusName", statusName(status));
|
result.put("handledAt", value(row, "handledAt", "handled_at"));
|
result.put("handledByName", first(text(row, "handledByName", "handled_by_name"), handlerFallback(value(row, "handledBy", "handled_by"))));
|
return result;
|
}
|
|
private List<String> exportValues(int index, Map<String, Object> row) {
|
List<String> values = new ArrayList<>();
|
values.add(String.valueOf(index));
|
values.add(text(row, "registrationNo", "registration_no"));
|
values.add(text(row, "talentName", "talent_name"));
|
values.add(text(row, "talentMobile", "talent_mobile"));
|
values.add(text(row, "cardLevel", "card_level"));
|
values.add(text(row, "cardNo", "card_no"));
|
values.add(text(row, "plateNumber", "plate_number"));
|
values.add(text(row, "merchantName", "merchant_name"));
|
values.add(text(row, "parkingLotName", "parking_lot_name"));
|
values.add(string(value(row, "submittedAt", "submitted_at")));
|
values.add(text(row, "submissionType", "submission_type"));
|
values.add(statusName(text(row, "status")));
|
values.add(first(text(row, "handledByName", "handled_by_name"), handlerFallback(value(row, "handledBy", "handled_by"))));
|
values.add(string(value(row, "handledAt", "handled_at")));
|
values.add(text(row, "handleRemark", "handle_remark"));
|
return values;
|
}
|
|
private Map<String, Object> detailRow(Long id) {
|
Long registrationId = requireId(id);
|
Map<String, Object> row = registrationMapper.selectAdminDetail(registrationId);
|
if (row == null || row.isEmpty()) {
|
throw new BusinessException(ErrorCode.NOT_FOUND, "车牌提交记录不存在");
|
}
|
return row;
|
}
|
|
private String normalizeFilterStatus(String raw) {
|
String value = clean(raw);
|
return value.isBlank() ? "" : normalizeStatus(value);
|
}
|
|
private String normalizeStatus(String raw) {
|
String value = clean(raw).toUpperCase(Locale.ROOT).replace("-", "_");
|
return switch (value) {
|
case "SUBMITTED", "PENDING", "待处理", "待审核" -> STATUS_SUBMITTED;
|
case "ACTIVE", "PROCESSED", "PASSED", "APPROVED", "已处理", "处理完成", "通过", "已通过" -> STATUS_ACTIVE;
|
case "REJECTED", "REFUSED", "DENIED", "已拒绝", "拒绝", "驳回", "已驳回" -> STATUS_REJECTED;
|
case "CANCELLED", "CANCELED", "已取消", "取消" -> STATUS_CANCELLED;
|
default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "处理状态不正确");
|
};
|
}
|
|
private String normalizeSubmissionType(String raw, boolean required) {
|
String value = clean(raw);
|
if (value.isBlank()) {
|
if (required) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "提交类型不能为空");
|
}
|
return "";
|
}
|
String upper = value.toUpperCase(Locale.ROOT);
|
return switch (upper) {
|
case "CREATE", "ADD", "NEW", "新增" -> "新增";
|
case "UPDATE", "MODIFY", "EDIT", "修改" -> "修改";
|
default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "提交类型不正确");
|
};
|
}
|
|
private String statusName(String status) {
|
return switch (clean(status).toUpperCase(Locale.ROOT)) {
|
case STATUS_ACTIVE -> "已处理";
|
case STATUS_REJECTED -> "已拒绝";
|
case STATUS_CANCELLED -> "已取消";
|
default -> "待处理";
|
};
|
}
|
|
private LocalDateTime startTime(String value, String message) {
|
LocalDate date = localDate(value, message);
|
return date == null ? null : date.atStartOfDay();
|
}
|
|
private LocalDateTime endTime(String value, String message) {
|
LocalDate date = localDate(value, message);
|
return date == null ? null : date.plusDays(1).atStartOfDay();
|
}
|
|
private LocalDate localDate(String value, String message) {
|
String text = clean(value);
|
if (text.isBlank()) {
|
return null;
|
}
|
try {
|
return LocalDate.parse(text);
|
} catch (DateTimeParseException exception) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, message);
|
}
|
}
|
|
private CellStyle headerStyle(Workbook workbook) {
|
CellStyle style = workbook.createCellStyle();
|
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
return style;
|
}
|
|
private Long requireId(Long id) {
|
if (id == null || id <= 0) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "车牌提交记录ID不能为空");
|
}
|
return id;
|
}
|
|
private Object value(Map<String, Object> row, String... keys) {
|
for (String key : keys) {
|
if (row.containsKey(key)) {
|
return row.get(key);
|
}
|
}
|
return null;
|
}
|
|
private String text(Map<String, Object> row, String... keys) {
|
Object value = value(row, keys);
|
return value == null ? "" : String.valueOf(value).trim();
|
}
|
|
private String first(String... values) {
|
for (String value : values) {
|
String text = clean(value);
|
if (!text.isBlank()) {
|
return text;
|
}
|
}
|
return "";
|
}
|
|
private String handlerFallback(Object value) {
|
return value instanceof Number number ? "管理员" + number.longValue() : "";
|
}
|
|
private String string(Object value) {
|
return value == null ? "" : String.valueOf(value);
|
}
|
|
private String clean(String value) {
|
return value == null ? "" : value.trim();
|
}
|
}
|