AdminLicenseController.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package com.xuekairui.user.controller;
  2. import com.baomidou.mybatisplus.core.metadata.IPage;
  3. import com.xuekairui.common.Result;
  4. import com.xuekairui.user.dto.BusinessLicenseReviewResponse;
  5. import com.xuekairui.user.enums.LicenseStatus;
  6. import com.xuekairui.user.service.BusinessLicenseService;
  7. import jakarta.servlet.http.HttpServletRequest;
  8. import jakarta.validation.Valid;
  9. import jakarta.validation.constraints.NotBlank;
  10. import jakarta.validation.constraints.NotNull;
  11. import lombok.Data;
  12. import lombok.RequiredArgsConstructor;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.springframework.web.bind.annotation.GetMapping;
  15. import org.springframework.web.bind.annotation.PathVariable;
  16. import org.springframework.web.bind.annotation.PostMapping;
  17. import org.springframework.web.bind.annotation.PutMapping;
  18. import org.springframework.web.bind.annotation.RequestBody;
  19. import org.springframework.web.bind.annotation.RequestMapping;
  20. import org.springframework.web.bind.annotation.RequestParam;
  21. import org.springframework.web.bind.annotation.RestController;
  22. import java.util.List;
  23. /**
  24. * 入驻信息审核管理接口(运营端)
  25. */
  26. @Slf4j
  27. @RestController
  28. @RequestMapping("/api/admin/license")
  29. @RequiredArgsConstructor
  30. public class AdminLicenseController {
  31. private final BusinessLicenseService businessLicenseService;
  32. /**
  33. * 审核列表查询
  34. * @param reviewStatus 审核状态筛选(可选):PENDING/APPROVED/REJECTED,不传则查全部
  35. * @param keyword 手机号关键词(可选):匹配用户手机号或入驻联系电话
  36. */
  37. @GetMapping("/review")
  38. public Result<IPage<BusinessLicenseReviewResponse>> listPendingReviews(
  39. @RequestParam(required = false) String reviewStatus,
  40. @RequestParam(required = false) String keyword,
  41. @RequestParam(defaultValue = "1") int page,
  42. @RequestParam(defaultValue = "20") int size) {
  43. return Result.success(businessLicenseService.listByReviewStatus(reviewStatus, keyword, page, size));
  44. }
  45. /**
  46. * 管理员新增入驻信息(代客户填写,创建后状态为待审核,需再操作一次审核确认)
  47. * <p>
  48. * 必须选择一个未提交过入驻信息的客户,必填基础信息,
  49. * 必须上传营业执照、药品经营许可、(二类/三类医疗器械二选一)。
  50. * POST /api/admin/license
  51. */
  52. @PostMapping
  53. public Result<Void> createByAdmin(
  54. @Valid @RequestBody AdminCreateRequest request,
  55. HttpServletRequest httpRequest) {
  56. Long reviewerId = (Long) httpRequest.getAttribute("userId");
  57. businessLicenseService.createByAdmin(request.toSaveRequest(), reviewerId);
  58. return Result.success();
  59. }
  60. /**
  61. * 管理员编辑入驻信息(可修改客户信息、替换资质图片,保存后即时生效)
  62. * <p>
  63. * 可编辑全部入驻数据(含已审核),必填项约束同样生效。
  64. * PUT /api/admin/license/{licenseId}
  65. */
  66. @PutMapping("/{licenseId}")
  67. public Result<Void> editByAdmin(
  68. @PathVariable Long licenseId,
  69. @Valid @RequestBody AdminEditRequest request,
  70. HttpServletRequest httpRequest) {
  71. Long reviewerId = (Long) httpRequest.getAttribute("userId");
  72. businessLicenseService.editByAdmin(licenseId, request.toSaveRequest(), reviewerId);
  73. return Result.success();
  74. }
  75. @PostMapping("/review")
  76. public Result<Void> reviewLicense(
  77. @Valid @RequestBody ReviewRequest request,
  78. HttpServletRequest httpRequest) {
  79. Long reviewerId = (Long) httpRequest.getAttribute("userId");
  80. if (LicenseStatus.APPROVED.name().equals(request.getAction())) {
  81. businessLicenseService.approve(request.getLicenseId(), reviewerId);
  82. } else if (LicenseStatus.REJECTED.name().equals(request.getAction())) {
  83. businessLicenseService.reject(request.getLicenseId(), reviewerId, request.getRejectReason());
  84. } else {
  85. return Result.error(400, "审核动作无效,请使用 APPROVED 或 REJECTED");
  86. }
  87. return Result.success();
  88. }
  89. /**
  90. * 运营端手动同步入驻信息到第三方(智价云药店版)
  91. * <p>
  92. * 运营在后台选择某条入驻记录,点击"同步到第三方"按钮时调用。
  93. * 同步执行(非异步),便于运营立即看到同步结果。
  94. * POST /api/admin/license/{licenseId}/sync-third
  95. */
  96. @PostMapping("/{licenseId}/sync-third")
  97. public Result<String> syncToThirdParty(
  98. @PathVariable Long licenseId,
  99. HttpServletRequest httpRequest) {
  100. Long reviewerId = (Long) httpRequest.getAttribute("userId");
  101. String result = businessLicenseService.manualSyncToThirdParty(licenseId, reviewerId);
  102. return Result.success(result);
  103. }
  104. /**
  105. * 查询入驻信息同步差异列表(运营端)
  106. * <p>
  107. * 当我方与第三方都已审核通过但字段不一致时,差异记录保存到 t_license_sync_diff。
  108. * 运营在此查看待处理差异,决定采纳第三方值、保留本地值或手动调整。
  109. * GET /api/admin/license/sync-diff?status=0
  110. */
  111. @GetMapping("/sync-diff")
  112. public Result<List<com.xuekairui.user.entity.LicenseSyncDiff>> listSyncDiff(
  113. @RequestParam(required = false) Integer status) {
  114. return Result.success(businessLicenseService.listSyncDiff(status));
  115. }
  116. /**
  117. * 处理入驻信息同步差异(运营端)
  118. * <p>
  119. * 运营对每条差异记录决定处理方式:
  120. * <ul>
  121. * <li>ADOPT_THIRD:采纳第三方值,将本地字段更新为第三方值</li>
  122. * <li>KEEP_LOCAL:保留本地值,不修改数据</li>
  123. * <li>MANUAL:运营已通过编辑接口手动修改入驻信息</li>
  124. * </ul>
  125. *
  126. * POST /api/admin/license/sync-diff/{diffId}/handle
  127. */
  128. @PostMapping("/sync-diff/{diffId}/handle")
  129. public Result<Void> handleSyncDiff(
  130. @PathVariable Long diffId,
  131. @Valid @RequestBody HandleDiffRequest request,
  132. HttpServletRequest httpRequest) {
  133. Long reviewerId = (Long) httpRequest.getAttribute("userId");
  134. businessLicenseService.handleSyncDiff(diffId, request.getAction(), request.getRemark(), reviewerId);
  135. return Result.success();
  136. }
  137. @Data
  138. public static class ReviewRequest {
  139. @NotNull(message = "入驻信息ID不能为空")
  140. private Long licenseId;
  141. @NotBlank(message = "审核动作不能为空")
  142. private String action;
  143. private String rejectReason;
  144. }
  145. /**
  146. * 处理入驻信息同步差异请求
  147. */
  148. @Data
  149. public static class HandleDiffRequest {
  150. /** 处理动作:ADOPT_THIRD-采纳第三方 / KEEP_LOCAL-保留本地 / MANUAL-手动调整 */
  151. @NotBlank(message = "处理动作不能为空")
  152. private String action;
  153. /** 处理备注(可选) */
  154. private String remark;
  155. }
  156. /**
  157. * 管理员新增入驻信息请求
  158. */
  159. @Data
  160. public static class AdminCreateRequest {
  161. /** 客户用户ID(必须选择未提交过入驻信息的客户) */
  162. @NotNull(message = "必须选择一个客户")
  163. private Long userId;
  164. /** 店铺名称 */
  165. @NotBlank(message = "店铺名称不能为空")
  166. private String storeName;
  167. /** 终端类型:SINGLE-单店, CHAIN-连锁, CLINIC-诊所, COMMUNITY_HEALTH-社康 */
  168. @NotBlank(message = "终端类型不能为空")
  169. private String terminalType;
  170. /** 省 */
  171. @NotBlank(message = "所在省不能为空")
  172. private String province;
  173. /** 市 */
  174. @NotBlank(message = "所在市不能为空")
  175. private String city;
  176. /** 区 */
  177. @NotBlank(message = "所在区不能为空")
  178. private String district;
  179. /** 店铺详细地址 */
  180. @NotBlank(message = "店铺地址不能为空")
  181. private String storeAddress;
  182. /** 联系人姓名 */
  183. @NotBlank(message = "联系人姓名不能为空")
  184. private String contactPerson;
  185. /** 联系电话 */
  186. @NotBlank(message = "联系电话不能为空")
  187. private String contactPhone;
  188. /** 营业执照图片URL */
  189. @NotBlank(message = "营业执照图片不能为空")
  190. private String businessLicenseUrl;
  191. /** 药品经营许可证图片URL */
  192. @NotBlank(message = "药品经营许可证不能为空")
  193. private String drugLicenseUrl;
  194. /** 统一社会信用代码 */
  195. private String creditCode;
  196. /** 二类医疗器械备案图片URL */
  197. private String medicalDeviceClass2Url;
  198. /** 三类医疗器械备案图片URL */
  199. private String medicalDeviceClass3Url;
  200. public BusinessLicenseService.BusinessLicenseSaveRequest toSaveRequest() {
  201. return BusinessLicenseService.BusinessLicenseSaveRequest.builder()
  202. .userId(userId)
  203. .storeName(storeName)
  204. .terminalType(terminalType)
  205. .province(province)
  206. .city(city)
  207. .district(district)
  208. .storeAddress(storeAddress)
  209. .contactPerson(contactPerson)
  210. .contactPhone(contactPhone)
  211. .businessLicenseUrl(businessLicenseUrl)
  212. .drugLicenseUrl(drugLicenseUrl)
  213. .creditCode(creditCode)
  214. .medicalDeviceClass2Url(medicalDeviceClass2Url)
  215. .medicalDeviceClass3Url(medicalDeviceClass3Url)
  216. .build();
  217. }
  218. }
  219. /**
  220. * 管理员编辑入驻信息请求(不含 userId,编辑时客户绑定关系不可更改)
  221. */
  222. @Data
  223. public static class AdminEditRequest {
  224. /** 店铺名称 */
  225. @NotBlank(message = "店铺名称不能为空")
  226. private String storeName;
  227. /** 终端类型:SINGLE-单店, CHAIN-连锁, CLINIC-诊所, COMMUNITY_HEALTH-社康 */
  228. @NotBlank(message = "终端类型不能为空")
  229. private String terminalType;
  230. /** 省 */
  231. @NotBlank(message = "所在省不能为空")
  232. private String province;
  233. /** 市 */
  234. @NotBlank(message = "所在市不能为空")
  235. private String city;
  236. /** 区 */
  237. @NotBlank(message = "所在区不能为空")
  238. private String district;
  239. /** 店铺详细地址 */
  240. @NotBlank(message = "店铺地址不能为空")
  241. private String storeAddress;
  242. /** 联系人姓名 */
  243. @NotBlank(message = "联系人姓名不能为空")
  244. private String contactPerson;
  245. /** 联系电话 */
  246. @NotBlank(message = "联系电话不能为空")
  247. private String contactPhone;
  248. /** 营业执照图片URL */
  249. @NotBlank(message = "营业执照图片不能为空")
  250. private String businessLicenseUrl;
  251. /** 药品经营许可证图片URL */
  252. @NotBlank(message = "药品经营许可证不能为空")
  253. private String drugLicenseUrl;
  254. /** 统一社会信用代码 */
  255. private String creditCode;
  256. /** 二类医疗器械备案图片URL */
  257. private String medicalDeviceClass2Url;
  258. /** 三类医疗器械备案图片URL */
  259. private String medicalDeviceClass3Url;
  260. public BusinessLicenseService.BusinessLicenseSaveRequest toSaveRequest() {
  261. return BusinessLicenseService.BusinessLicenseSaveRequest.builder()
  262. .storeName(storeName)
  263. .terminalType(terminalType)
  264. .province(province)
  265. .city(city)
  266. .district(district)
  267. .storeAddress(storeAddress)
  268. .contactPerson(contactPerson)
  269. .contactPhone(contactPhone)
  270. .businessLicenseUrl(businessLicenseUrl)
  271. .drugLicenseUrl(drugLicenseUrl)
  272. .creditCode(creditCode)
  273. .medicalDeviceClass2Url(medicalDeviceClass2Url)
  274. .medicalDeviceClass3Url(medicalDeviceClass3Url)
  275. .build();
  276. }
  277. }
  278. }