GroupOrderController.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package com.train.controller;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.train.common.Result;
  4. import com.train.cfc.entity.CfcActivity;
  5. import com.train.cfc.mapper.CfcActivityMapper;
  6. import com.train.entity.TrainClass;
  7. import com.train.entity.TrainEnrollment;
  8. import com.train.entity.TrainGroupOrder;
  9. import com.train.entity.TrainGroupOrderMember;
  10. import com.train.mapper.TrainClassMapper;
  11. import com.train.mapper.TrainEnrollmentMapper;
  12. import com.train.mapper.TrainGroupOrderMapper;
  13. import com.train.mapper.TrainGroupOrderMemberMapper;
  14. import com.train.service.PayService;
  15. import io.swagger.v3.oas.annotations.Operation;
  16. import io.swagger.v3.oas.annotations.tags.Tag;
  17. import lombok.extern.slf4j.Slf4j;
  18. import org.springframework.util.StringUtils;
  19. import org.springframework.web.bind.annotation.PostMapping;
  20. import org.springframework.web.bind.annotation.RequestAttribute;
  21. import org.springframework.web.bind.annotation.RequestBody;
  22. import org.springframework.web.bind.annotation.RequestMapping;
  23. import org.springframework.web.bind.annotation.RestController;
  24. import javax.annotation.Resource;
  25. import java.text.SimpleDateFormat;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.concurrent.ThreadLocalRandom;
  33. /**
  34. * 团报接口(FR-PAY-03):多人一单 + 企业开票信息采集。
  35. * <p>流程:create 创建团报单(含成员名单与发票信息)→ pay 下单支付(test-mode 直付)→ 入账时逐人生成报名单。</p>
  36. */
  37. @Slf4j
  38. @Tag(name = "团报", description = "团报下单与支付")
  39. @RestController
  40. @RequestMapping("/api/group-order")
  41. public class GroupOrderController {
  42. @Resource
  43. private TrainGroupOrderMapper trainGroupOrderMapper;
  44. @Resource
  45. private TrainGroupOrderMemberMapper trainGroupOrderMemberMapper;
  46. @Resource
  47. private TrainClassMapper trainClassMapper;
  48. @Resource
  49. private TrainEnrollmentMapper trainEnrollmentMapper;
  50. @Resource
  51. private CfcActivityMapper cfcActivityMapper;
  52. @Resource
  53. private PayService payService;
  54. /** 占用名额的报名状态 */
  55. private static final List<String> OCCUPY_STATUS = Arrays.asList("pending", "paid", "confirmed");
  56. /**
  57. * 创建团报单(含成员名单与开票信息),单价取 cfc activities 会员价。
  58. */
  59. @Operation(summary = "创建团报单")
  60. @PostMapping("/create")
  61. public Result<Map<String, Object>> create(@RequestBody Map<String, Object> body,
  62. @RequestAttribute("userId") Long userId) {
  63. Object classIdObj = body.get("classId");
  64. if (classIdObj == null) {
  65. return Result.error("请选择班次");
  66. }
  67. Long classId = Long.valueOf(classIdObj.toString());
  68. TrainClass trainClass = trainClassMapper.selectById(classId);
  69. if (trainClass == null || !"active".equals(trainClass.getStatus())) {
  70. return Result.error("班次不存在或不可报名");
  71. }
  72. // 成员名单
  73. Object membersObj = body.get("members");
  74. if (!(membersObj instanceof List) || ((List<?>) membersObj).isEmpty()) {
  75. return Result.error("请填写团报成员名单");
  76. }
  77. List<Map<String, Object>> memberList = new ArrayList<>();
  78. @SuppressWarnings("unchecked")
  79. List<Object> rawMembers = (List<Object>) membersObj;
  80. for (Object m : rawMembers) {
  81. if (!(m instanceof Map)) {
  82. continue;
  83. }
  84. @SuppressWarnings("unchecked")
  85. Map<String, Object> mm = (Map<String, Object>) m;
  86. String name = mm.get("name") == null ? "" : mm.get("name").toString().trim();
  87. String phone = mm.get("phone") == null ? "" : mm.get("phone").toString().trim();
  88. if (!StringUtils.hasText(name) || !StringUtils.hasText(phone)) {
  89. return Result.error("成员姓名和手机号均不能为空");
  90. }
  91. Map<String, Object> row = new HashMap<>();
  92. row.put("name", name);
  93. row.put("phone", phone);
  94. memberList.add(row);
  95. }
  96. if (memberList.isEmpty()) {
  97. return Result.error("请填写团报成员名单");
  98. }
  99. // 容量校验
  100. Long occupied = trainEnrollmentMapper.selectCount(
  101. new LambdaQueryWrapper<TrainEnrollment>()
  102. .eq(TrainEnrollment::getClassId, classId)
  103. .in(TrainEnrollment::getStatus, OCCUPY_STATUS));
  104. long newCount = memberList.size();
  105. if (trainClass.getCapacity() != null && occupied != null
  106. && occupied + newCount > trainClass.getCapacity()) {
  107. return Result.error("该班次剩余名额不足,无法按当前人数团报");
  108. }
  109. // 单价(会员价优先,与报名接口一致)
  110. int price = 0;
  111. if (trainClass.getActivityId() != null) {
  112. CfcActivity activity = cfcActivityMapper.selectById(trainClass.getActivityId());
  113. if (activity != null) {
  114. int raw = activity.getPrice() == null ? 0 : activity.getPrice();
  115. price = activity.getMemberPrice() == null ? raw : activity.getMemberPrice();
  116. }
  117. }
  118. // 开票信息
  119. String invoiceTitle = body.get("invoiceTitle") == null ? "" : body.get("invoiceTitle").toString().trim();
  120. String invoiceType = body.get("invoiceType") == null ? "普票" : body.get("invoiceType").toString().trim();
  121. if (StringUtils.hasText(invoiceTitle)) {
  122. if (!StringUtils.hasText(body.get("invoiceTaxNo") == null ? "" : body.get("invoiceTaxNo").toString())) {
  123. return Result.error("填写发票抬头后请一并填写税号");
  124. }
  125. }
  126. TrainGroupOrder go = new TrainGroupOrder();
  127. go.setGroupNo(genGroupNo());
  128. go.setClassId(classId);
  129. go.setLeaderUid(userId);
  130. go.setMemberCount(memberList.size());
  131. go.setUnitAmount(price);
  132. go.setTotalAmount(price * memberList.size());
  133. go.setStatus("pending");
  134. go.setInvoiceTitle(invoiceTitle);
  135. go.setInvoiceTaxNo(body.get("invoiceTaxNo") == null ? null : body.get("invoiceTaxNo").toString().trim());
  136. go.setInvoiceEmail(body.get("invoiceEmail") == null ? null : body.get("invoiceEmail").toString().trim());
  137. go.setInvoicePhone(body.get("invoicePhone") == null ? null : body.get("invoicePhone").toString().trim());
  138. go.setInvoiceType(invoiceType);
  139. go.setCreatedAt(new Date());
  140. go.setUpdatedAt(new Date());
  141. trainGroupOrderMapper.insert(go);
  142. for (Map<String, Object> m : memberList) {
  143. TrainGroupOrderMember mem = new TrainGroupOrderMember();
  144. mem.setGroupOrderId(go.getId());
  145. mem.setName(m.get("name").toString());
  146. mem.setPhone(m.get("phone").toString());
  147. mem.setStatus("pending");
  148. mem.setCreatedAt(new Date());
  149. trainGroupOrderMemberMapper.insert(mem);
  150. }
  151. Map<String, Object> row = new HashMap<>();
  152. row.put("id", go.getId());
  153. row.put("groupNo", go.getGroupNo());
  154. row.put("classId", classId);
  155. row.put("className", trainClass.getName());
  156. row.put("memberCount", memberList.size());
  157. row.put("unitAmount", price);
  158. row.put("totalAmount", go.getTotalAmount());
  159. row.put("status", go.getStatus());
  160. log.info("创建团报单: id={}, groupNo={}, classId={}, members={}, total={}",
  161. go.getId(), go.getGroupNo(), classId, memberList.size(), go.getTotalAmount());
  162. return Result.success(row);
  163. }
  164. /**
  165. * 我的团报单列表(团长视角)
  166. */
  167. @Operation(summary = "我的团报单列表")
  168. @PostMapping("/mine")
  169. public Result<List<Map<String, Object>>> mine(@RequestAttribute("userId") Long userId) {
  170. List<TrainGroupOrder> list = trainGroupOrderMapper.selectList(
  171. new LambdaQueryWrapper<TrainGroupOrder>()
  172. .eq(TrainGroupOrder::getLeaderUid, userId)
  173. .orderByDesc(TrainGroupOrder::getId));
  174. List<Map<String, Object>> result = new ArrayList<>();
  175. for (TrainGroupOrder go : list) {
  176. result.add(toRow(go));
  177. }
  178. return Result.success(result);
  179. }
  180. /**
  181. * 团报单详情(含成员名单;须为团长本人)
  182. */
  183. @Operation(summary = "团报单详情")
  184. @PostMapping("/detail")
  185. public Result<Map<String, Object>> detail(@RequestBody Map<String, Object> body,
  186. @RequestAttribute("userId") Long userId) {
  187. Object idObj = body.get("id");
  188. if (idObj == null) {
  189. return Result.error("缺少团报单ID");
  190. }
  191. TrainGroupOrder go = trainGroupOrderMapper.selectById(Long.valueOf(idObj.toString()));
  192. if (go == null || !userId.equals(go.getLeaderUid())) {
  193. return Result.error("团报单不存在");
  194. }
  195. Map<String, Object> row = toRow(go);
  196. List<TrainGroupOrderMember> members = trainGroupOrderMemberMapper.selectList(
  197. new LambdaQueryWrapper<TrainGroupOrderMember>()
  198. .eq(TrainGroupOrderMember::getGroupOrderId, go.getId())
  199. .orderByAsc(TrainGroupOrderMember::getId));
  200. List<Map<String, Object>> memberRows = new ArrayList<>();
  201. for (TrainGroupOrderMember m : members) {
  202. Map<String, Object> mm = new HashMap<>();
  203. mm.put("id", m.getId());
  204. mm.put("name", m.getName());
  205. mm.put("phone", m.getPhone());
  206. mm.put("status", m.getStatus());
  207. mm.put("enrollmentId", m.getEnrollmentId());
  208. memberRows.add(mm);
  209. }
  210. row.put("members", memberRows);
  211. return Result.success(row);
  212. }
  213. /**
  214. * 团报支付(test-mode 直接入账:订单置 paid + 逐成员生成报名单)
  215. */
  216. @Operation(summary = "团报支付")
  217. @PostMapping("/pay")
  218. public Result<Map<String, Object>> pay(@RequestBody Map<String, Object> body,
  219. @RequestAttribute("userId") Long userId) {
  220. Object idObj = body.get("id");
  221. if (idObj == null) {
  222. return Result.error("缺少团报单ID");
  223. }
  224. try {
  225. return Result.success(payService.createGroupOrderPay(Long.valueOf(idObj.toString()), userId));
  226. } catch (RuntimeException e) {
  227. return Result.error(e.getMessage());
  228. }
  229. }
  230. private Map<String, Object> toRow(TrainGroupOrder go) {
  231. Map<String, Object> row = new HashMap<>();
  232. row.put("id", go.getId());
  233. row.put("groupNo", go.getGroupNo());
  234. row.put("classId", go.getClassId());
  235. TrainClass c = go.getClassId() == null ? null : trainClassMapper.selectById(go.getClassId());
  236. row.put("className", c != null ? c.getName() : null);
  237. row.put("leaderUid", go.getLeaderUid());
  238. row.put("memberCount", go.getMemberCount());
  239. row.put("unitAmount", go.getUnitAmount());
  240. row.put("totalAmount", go.getTotalAmount());
  241. row.put("orderId", go.getOrderId());
  242. row.put("status", go.getStatus());
  243. row.put("invoiceTitle", go.getInvoiceTitle());
  244. row.put("invoiceType", go.getInvoiceType());
  245. row.put("payTime", go.getPayTime());
  246. row.put("createdAt", go.getCreatedAt());
  247. return row;
  248. }
  249. /** 生成团报单号:GR + yyyyMMddHHmmss + 4位随机 */
  250. private String genGroupNo() {
  251. String ts = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
  252. int rand = ThreadLocalRandom.current().nextInt(1000, 10000);
  253. return "GR" + ts + rand;
  254. }
  255. }