| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- package com.train.controller;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.train.common.Result;
- import com.train.cfc.entity.CfcActivity;
- import com.train.cfc.mapper.CfcActivityMapper;
- import com.train.entity.TrainClass;
- import com.train.entity.TrainEnrollment;
- import com.train.entity.TrainGroupOrder;
- import com.train.entity.TrainGroupOrderMember;
- import com.train.mapper.TrainClassMapper;
- import com.train.mapper.TrainEnrollmentMapper;
- import com.train.mapper.TrainGroupOrderMapper;
- import com.train.mapper.TrainGroupOrderMemberMapper;
- import com.train.service.PayService;
- import io.swagger.v3.oas.annotations.Operation;
- import io.swagger.v3.oas.annotations.tags.Tag;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.util.StringUtils;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestAttribute;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import javax.annotation.Resource;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ThreadLocalRandom;
- /**
- * 团报接口(FR-PAY-03):多人一单 + 企业开票信息采集。
- * <p>流程:create 创建团报单(含成员名单与发票信息)→ pay 下单支付(test-mode 直付)→ 入账时逐人生成报名单。</p>
- */
- @Slf4j
- @Tag(name = "团报", description = "团报下单与支付")
- @RestController
- @RequestMapping("/api/group-order")
- public class GroupOrderController {
- @Resource
- private TrainGroupOrderMapper trainGroupOrderMapper;
- @Resource
- private TrainGroupOrderMemberMapper trainGroupOrderMemberMapper;
- @Resource
- private TrainClassMapper trainClassMapper;
- @Resource
- private TrainEnrollmentMapper trainEnrollmentMapper;
- @Resource
- private CfcActivityMapper cfcActivityMapper;
- @Resource
- private PayService payService;
- /** 占用名额的报名状态 */
- private static final List<String> OCCUPY_STATUS = Arrays.asList("pending", "paid", "confirmed");
- /**
- * 创建团报单(含成员名单与开票信息),单价取 cfc activities 会员价。
- */
- @Operation(summary = "创建团报单")
- @PostMapping("/create")
- public Result<Map<String, Object>> create(@RequestBody Map<String, Object> body,
- @RequestAttribute("userId") Long userId) {
- Object classIdObj = body.get("classId");
- if (classIdObj == null) {
- return Result.error("请选择班次");
- }
- Long classId = Long.valueOf(classIdObj.toString());
- TrainClass trainClass = trainClassMapper.selectById(classId);
- if (trainClass == null || !"active".equals(trainClass.getStatus())) {
- return Result.error("班次不存在或不可报名");
- }
- // 成员名单
- Object membersObj = body.get("members");
- if (!(membersObj instanceof List) || ((List<?>) membersObj).isEmpty()) {
- return Result.error("请填写团报成员名单");
- }
- List<Map<String, Object>> memberList = new ArrayList<>();
- @SuppressWarnings("unchecked")
- List<Object> rawMembers = (List<Object>) membersObj;
- for (Object m : rawMembers) {
- if (!(m instanceof Map)) {
- continue;
- }
- @SuppressWarnings("unchecked")
- Map<String, Object> mm = (Map<String, Object>) m;
- String name = mm.get("name") == null ? "" : mm.get("name").toString().trim();
- String phone = mm.get("phone") == null ? "" : mm.get("phone").toString().trim();
- if (!StringUtils.hasText(name) || !StringUtils.hasText(phone)) {
- return Result.error("成员姓名和手机号均不能为空");
- }
- Map<String, Object> row = new HashMap<>();
- row.put("name", name);
- row.put("phone", phone);
- memberList.add(row);
- }
- if (memberList.isEmpty()) {
- return Result.error("请填写团报成员名单");
- }
- // 容量校验
- Long occupied = trainEnrollmentMapper.selectCount(
- new LambdaQueryWrapper<TrainEnrollment>()
- .eq(TrainEnrollment::getClassId, classId)
- .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
- long newCount = memberList.size();
- if (trainClass.getCapacity() != null && occupied != null
- && occupied + newCount > trainClass.getCapacity()) {
- return Result.error("该班次剩余名额不足,无法按当前人数团报");
- }
- // 单价(会员价优先,与报名接口一致)
- int price = 0;
- if (trainClass.getActivityId() != null) {
- CfcActivity activity = cfcActivityMapper.selectById(trainClass.getActivityId());
- if (activity != null) {
- int raw = activity.getPrice() == null ? 0 : activity.getPrice();
- price = activity.getMemberPrice() == null ? raw : activity.getMemberPrice();
- }
- }
- // 开票信息
- String invoiceTitle = body.get("invoiceTitle") == null ? "" : body.get("invoiceTitle").toString().trim();
- String invoiceType = body.get("invoiceType") == null ? "普票" : body.get("invoiceType").toString().trim();
- if (StringUtils.hasText(invoiceTitle)) {
- if (!StringUtils.hasText(body.get("invoiceTaxNo") == null ? "" : body.get("invoiceTaxNo").toString())) {
- return Result.error("填写发票抬头后请一并填写税号");
- }
- }
- TrainGroupOrder go = new TrainGroupOrder();
- go.setGroupNo(genGroupNo());
- go.setClassId(classId);
- go.setLeaderUid(userId);
- go.setMemberCount(memberList.size());
- go.setUnitAmount(price);
- go.setTotalAmount(price * memberList.size());
- go.setStatus("pending");
- go.setInvoiceTitle(invoiceTitle);
- go.setInvoiceTaxNo(body.get("invoiceTaxNo") == null ? null : body.get("invoiceTaxNo").toString().trim());
- go.setInvoiceEmail(body.get("invoiceEmail") == null ? null : body.get("invoiceEmail").toString().trim());
- go.setInvoicePhone(body.get("invoicePhone") == null ? null : body.get("invoicePhone").toString().trim());
- go.setInvoiceType(invoiceType);
- go.setCreatedAt(new Date());
- go.setUpdatedAt(new Date());
- trainGroupOrderMapper.insert(go);
- for (Map<String, Object> m : memberList) {
- TrainGroupOrderMember mem = new TrainGroupOrderMember();
- mem.setGroupOrderId(go.getId());
- mem.setName(m.get("name").toString());
- mem.setPhone(m.get("phone").toString());
- mem.setStatus("pending");
- mem.setCreatedAt(new Date());
- trainGroupOrderMemberMapper.insert(mem);
- }
- Map<String, Object> row = new HashMap<>();
- row.put("id", go.getId());
- row.put("groupNo", go.getGroupNo());
- row.put("classId", classId);
- row.put("className", trainClass.getName());
- row.put("memberCount", memberList.size());
- row.put("unitAmount", price);
- row.put("totalAmount", go.getTotalAmount());
- row.put("status", go.getStatus());
- log.info("创建团报单: id={}, groupNo={}, classId={}, members={}, total={}",
- go.getId(), go.getGroupNo(), classId, memberList.size(), go.getTotalAmount());
- return Result.success(row);
- }
- /**
- * 我的团报单列表(团长视角)
- */
- @Operation(summary = "我的团报单列表")
- @PostMapping("/mine")
- public Result<List<Map<String, Object>>> mine(@RequestAttribute("userId") Long userId) {
- List<TrainGroupOrder> list = trainGroupOrderMapper.selectList(
- new LambdaQueryWrapper<TrainGroupOrder>()
- .eq(TrainGroupOrder::getLeaderUid, userId)
- .orderByDesc(TrainGroupOrder::getId));
- List<Map<String, Object>> result = new ArrayList<>();
- for (TrainGroupOrder go : list) {
- result.add(toRow(go));
- }
- return Result.success(result);
- }
- /**
- * 团报单详情(含成员名单;须为团长本人)
- */
- @Operation(summary = "团报单详情")
- @PostMapping("/detail")
- public Result<Map<String, Object>> detail(@RequestBody Map<String, Object> body,
- @RequestAttribute("userId") Long userId) {
- Object idObj = body.get("id");
- if (idObj == null) {
- return Result.error("缺少团报单ID");
- }
- TrainGroupOrder go = trainGroupOrderMapper.selectById(Long.valueOf(idObj.toString()));
- if (go == null || !userId.equals(go.getLeaderUid())) {
- return Result.error("团报单不存在");
- }
- Map<String, Object> row = toRow(go);
- List<TrainGroupOrderMember> members = trainGroupOrderMemberMapper.selectList(
- new LambdaQueryWrapper<TrainGroupOrderMember>()
- .eq(TrainGroupOrderMember::getGroupOrderId, go.getId())
- .orderByAsc(TrainGroupOrderMember::getId));
- List<Map<String, Object>> memberRows = new ArrayList<>();
- for (TrainGroupOrderMember m : members) {
- Map<String, Object> mm = new HashMap<>();
- mm.put("id", m.getId());
- mm.put("name", m.getName());
- mm.put("phone", m.getPhone());
- mm.put("status", m.getStatus());
- mm.put("enrollmentId", m.getEnrollmentId());
- memberRows.add(mm);
- }
- row.put("members", memberRows);
- return Result.success(row);
- }
- /**
- * 团报支付(test-mode 直接入账:订单置 paid + 逐成员生成报名单)
- */
- @Operation(summary = "团报支付")
- @PostMapping("/pay")
- public Result<Map<String, Object>> pay(@RequestBody Map<String, Object> body,
- @RequestAttribute("userId") Long userId) {
- Object idObj = body.get("id");
- if (idObj == null) {
- return Result.error("缺少团报单ID");
- }
- try {
- return Result.success(payService.createGroupOrderPay(Long.valueOf(idObj.toString()), userId));
- } catch (RuntimeException e) {
- return Result.error(e.getMessage());
- }
- }
- private Map<String, Object> toRow(TrainGroupOrder go) {
- Map<String, Object> row = new HashMap<>();
- row.put("id", go.getId());
- row.put("groupNo", go.getGroupNo());
- row.put("classId", go.getClassId());
- TrainClass c = go.getClassId() == null ? null : trainClassMapper.selectById(go.getClassId());
- row.put("className", c != null ? c.getName() : null);
- row.put("leaderUid", go.getLeaderUid());
- row.put("memberCount", go.getMemberCount());
- row.put("unitAmount", go.getUnitAmount());
- row.put("totalAmount", go.getTotalAmount());
- row.put("orderId", go.getOrderId());
- row.put("status", go.getStatus());
- row.put("invoiceTitle", go.getInvoiceTitle());
- row.put("invoiceType", go.getInvoiceType());
- row.put("payTime", go.getPayTime());
- row.put("createdAt", go.getCreatedAt());
- return row;
- }
- /** 生成团报单号:GR + yyyyMMddHHmmss + 4位随机 */
- private String genGroupNo() {
- String ts = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
- int rand = ThreadLocalRandom.current().nextInt(1000, 10000);
- return "GR" + ts + rand;
- }
- }
|