MaTechnicianServiceImpl.java 100 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413
  1. package com.ylx.massage.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  5. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  6. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  7. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  9. import com.ylx.attendanceconfig.domain.AttendanceRule;
  10. import com.ylx.attendanceconfig.mapper.AttendanceRuleMapper;
  11. import com.ylx.collect.service.CollectService;
  12. import com.ylx.common.core.domain.model.LoginUser;
  13. import com.ylx.common.exception.ServiceException;
  14. import com.ylx.common.utils.DateUtils;
  15. import com.ylx.common.utils.DistanceUtil;
  16. import com.ylx.common.utils.ServletUtils;
  17. import com.ylx.common.utils.StringUtils;
  18. import com.ylx.fareSetting.service.IMaProjectFareSettingService;
  19. import com.ylx.massage.domain.*;
  20. import com.ylx.massage.domain.dto.*;
  21. import com.ylx.massage.domain.vo.*;
  22. import com.ylx.massage.enums.*;
  23. import com.ylx.massage.mapper.*;
  24. import com.ylx.massage.service.IMaTechnicianService;
  25. import com.ylx.massage.service.TAddressService;
  26. import com.ylx.merchant.domain.dto.MerchantDetailDTO;
  27. import com.ylx.merchant.domain.dto.MerchantListDTO;
  28. import com.ylx.merchant.domain.dto.MerchantProjectDTO;
  29. import com.ylx.merchant.domain.vo.MerchantDetailVO;
  30. import com.ylx.merchant.domain.vo.MerchantListVO;
  31. import com.ylx.order.domain.TOrder;
  32. import com.ylx.order.mapper.TOrderMapper;
  33. import com.ylx.project.domain.Project;
  34. import com.ylx.project.domain.bookMerchant.vo.ProjectInfoVO;
  35. import com.ylx.project.mapper.ProjectMapper;
  36. import org.springframework.beans.BeanUtils;
  37. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  38. import org.springframework.stereotype.Service;
  39. import org.springframework.transaction.annotation.Transactional;
  40. import org.springframework.util.CollectionUtils;
  41. import javax.annotation.Resource;
  42. import java.math.BigDecimal;
  43. import java.time.Duration;
  44. import java.time.LocalDate;
  45. import java.time.LocalDateTime;
  46. import java.time.ZoneId;
  47. import java.time.format.DateTimeFormatter;
  48. import java.util.*;
  49. import java.util.function.Function;
  50. import java.util.stream.Collectors;
  51. import static com.ylx.massage.enums.FileTypeEnum.*;
  52. /**
  53. * 技师Service业务层处理
  54. *
  55. * @author ylx
  56. * @date 2024-03-22
  57. */
  58. @Service
  59. public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaTechnician> implements IMaTechnicianService {
  60. private static final int SERVICE_STATE_AVAILABLE = 1;
  61. private static final int POST_STATE_OFFLINE = 0;
  62. private static final int ENABLED = 1;
  63. private static final int AUDIT_APPROVED = 2;
  64. private static final int AUDIT_WAIT_ENTER = 0;
  65. private static final int AUDIT_WAIT_REVIEW = 1;
  66. private static final int AUDIT_REJECTED = 3;
  67. private static final int AUDIT_REMARK_MAX_LENGTH = 500;
  68. private static final int NS_STATUS_NOT_ON_DUTY = -1;
  69. private static final int DEFAULT_STAT_VALUE = 0;
  70. private static final Integer NOT_DELETED = 0;
  71. private static final Integer MERCHANT_STATUS_NORMAL = 0;
  72. private static final String PASSWORD = "123456";
  73. private static final int PROFILE_AUDIT_PENDING = 0;
  74. private static final int PROFILE_AUDIT_APPROVED = 1;
  75. private static final int PROFILE_AUDIT_REJECTED = 2;
  76. private static final Set<String> PROFILE_FILE_TYPES = new LinkedHashSet<>(Arrays.asList(
  77. PORTRAIT.getCode(),
  78. LIFE_PHOTO.getCode(),
  79. PROMOTION_VIDEO.getCode(),
  80. ID_CARD_FRONT.getCode(),
  81. ID_CARD_BACK.getCode(),
  82. ID_CARD_HANDHELD.getCode(),
  83. HEALTH_CERT.getCode(),
  84. QUALIFICATION_CERT.getCode(),
  85. NO_CRIME_RECORD.getCode(),
  86. COMMITMENT_LETTER.getCode(),
  87. COMMITMENT_AUDIO.getCode(),
  88. COMMITMENT_VIDEO.getCode()
  89. ));
  90. @Resource
  91. private MaTechnicianMapper maTechnicianMapper;
  92. @Resource
  93. private TWxUserMapper tWxUserMapper;
  94. @Resource
  95. private MaTeProjectMapper maTeProjectMapper;
  96. @Resource
  97. private MaProjectMapper maProjectMapper;
  98. @Resource
  99. private ProjectMapper projectMapper;
  100. @Resource
  101. private ContractRecordMapper contractRecordMapper;
  102. @Resource
  103. private MerchantDailyAttendanceMapper merchantDailyAttendanceMapper;
  104. @Resource
  105. private AttendanceRuleMapper attendanceRuleMapper;
  106. @Resource
  107. private TAddressMapper addressMapper;
  108. @Resource
  109. private MerchantApplyFileMapper merchantApplyFileMapper;
  110. @Resource
  111. private TOrderMapper orderMapper;
  112. @Resource
  113. private CityOperationApplicationMapper cityOperationApplicationMapper;
  114. @Resource
  115. private IMaProjectFareSettingService maProjectFareSettingService;
  116. @Resource
  117. private TAddressService addressService;
  118. @Resource
  119. private CollectService collectService;
  120. /**
  121. * 商户入驻申请注册
  122. *
  123. * @param req 申请参数
  124. */
  125. @Override
  126. @Transactional(rollbackFor = Exception.class)
  127. public void apply(MaTechnicianAppAddVo req) {
  128. String phone = req.getTePhone();
  129. //商户入住前置条件校验
  130. getMaTechnician(req, phone);
  131. MaTechnician maTechnician = new MaTechnician();
  132. BeanUtils.copyProperties(req, maTechnician);
  133. //通过openid校验商户是否存在
  134. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  135. queryWrapper.eq(MaTechnician::getCOpenid, req.getCOpenid());
  136. MaTechnician maTechnician1 = maTechnicianMapper.selectOne(queryWrapper);
  137. if (maTechnician1 == null) {
  138. throw new RuntimeException("商户不存在");
  139. }
  140. //添加城市管理地址
  141. insertCity(req, maTechnician1, maTechnician);
  142. }
  143. /**
  144. * 添加城市管理地址
  145. *
  146. * @param req
  147. * @param maTechnician1
  148. * @param maTechnician
  149. */
  150. private void insertCity(MaTechnicianAppAddVo req, MaTechnician maTechnician1, MaTechnician maTechnician) {
  151. //商户类型默认为真实商户
  152. maTechnician.setTechType(0);
  153. maTechnician.setCreateBy("admin");
  154. // 审核状态:待入驻
  155. maTechnician.setAuditStatus(AUDIT_WAIT_ENTER);
  156. maTechnicianMapper.insert(maTechnician);
  157. TWxUser wxUser = new TWxUser();
  158. // 初始化加密工具
  159. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  160. //密码默认123456
  161. wxUser.setCPassword(encoder.encode(PASSWORD));
  162. wxUser.setRole(1);
  163. LambdaUpdateWrapper<TWxUser> updateWrapper = new LambdaUpdateWrapper<>();
  164. updateWrapper.eq(TWxUser::getcOpenid, req.getCOpenid());
  165. tWxUserMapper.update(wxUser, updateWrapper);
  166. /*LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();
  167. updateWrapper.eq(MaTechnician::getId, maTechnician1.getId());
  168. maTechnicianMapper.update(maTechnician, updateWrapper);*/
  169. CityOperationApplication cityOperationApplication = new CityOperationApplication();
  170. cityOperationApplication.setMerchantId(maTechnician1.getId());
  171. cityOperationApplication.setProvinceCode(req.getProvinceCode());
  172. cityOperationApplication.setProvinceName(req.getProvinceName());
  173. cityOperationApplication.setCityCode(req.getCityCode());
  174. cityOperationApplication.setCityName(req.getCityName());
  175. cityOperationApplication.setOperationCenterId(req.getOperationCenterId());
  176. cityOperationApplication.setOperationCenterName(req.getOperationCenterName());
  177. cityOperationApplication.setCreateBy(maTechnician1.getId().toString());
  178. cityOperationApplication.setCreateTime(LocalDateTime.now());
  179. cityOperationApplicationMapper.insert(cityOperationApplication);
  180. }
  181. /**
  182. * 商户申请入驻
  183. *
  184. * @param req
  185. */
  186. @Override
  187. @Transactional(rollbackFor = Exception.class)
  188. public void applyFile(MerchantApplyFileRequestDto req) {
  189. if (req == null) {
  190. throw new ServiceException("上传参数不能为空");
  191. }
  192. Integer merchantId = resolveMerchantId(req.getTechnician());
  193. List<MerchantApplyFileDto> files = resolveApplyFiles(req.getReq());
  194. if (CollectionUtils.isEmpty(files)) {
  195. throw new ServiceException("请上传申请入驻文件");
  196. }
  197. replaceApplyFiles(merchantId, files);
  198. updateTechnicianBaseInfo(req.getTechnician());
  199. }
  200. /**
  201. * 将文件分组格式转换为单文件记录。
  202. *
  203. * @param reqFiles 入驻资料文件
  204. * @return List<MerchantApplyFileDto> 转换后的文件记录
  205. */
  206. private List<MerchantApplyFileDto> resolveApplyFiles(List<MerchantApplyFileDto> reqFiles) {
  207. if (CollectionUtils.isEmpty(reqFiles)) {
  208. return Collections.emptyList();
  209. }
  210. List<MerchantApplyFileDto> files = new ArrayList<>();
  211. for (MerchantApplyFileDto item : reqFiles) {
  212. if (item == null) {
  213. throw new ServiceException("入驻资料文件不能为空");
  214. }
  215. checkApplyFileGroupParam(item);
  216. for (MerchantApplyFileDto child : item.getFiles()) {
  217. files.add(buildApplyFileFromGroup(item, child));
  218. }
  219. }
  220. return files;
  221. }
  222. /**
  223. * 校验文件组参数是否符合要求。
  224. *
  225. * @param group
  226. */
  227. private void checkApplyFileGroupParam(MerchantApplyFileDto group) {
  228. if (StringUtils.isBlank(group.getFileType())) {
  229. throw new ServiceException("文件类型不能为空");
  230. }
  231. if (CollectionUtils.isEmpty(group.getFiles())) {
  232. throw new ServiceException("文件列表不能为空");
  233. }
  234. }
  235. private MerchantApplyFileDto buildApplyFileFromGroup(MerchantApplyFileDto group, MerchantApplyFileDto child) {
  236. if (child == null) {
  237. throw new ServiceException("入驻资料文件不能为空");
  238. }
  239. MerchantApplyFileDto file = new MerchantApplyFileDto();
  240. BeanUtils.copyProperties(child, file);
  241. file.setFiles(null);
  242. if (StringUtils.isBlank(file.getFileType())) {
  243. file.setFileType(group.getFileType());
  244. } else if (!group.getFileType().equals(file.getFileType())) {
  245. throw new ServiceException("同一组文件类型必须一致");
  246. }
  247. checkApplyFileParam(file);
  248. return file;
  249. }
  250. /**
  251. * 按本次提交的文件类型整组替换旧文件。
  252. *
  253. * @param merchantId 商户ID
  254. * @param files 入驻资料文件
  255. */
  256. private void replaceApplyFiles(Integer merchantId, List<MerchantApplyFileDto> files) {
  257. Map<String, List<MerchantApplyFileDto>> filesByType = files.stream()
  258. .collect(Collectors.groupingBy(MerchantApplyFileDto::getFileType, LinkedHashMap::new, Collectors.toList()));
  259. for (Map.Entry<String, List<MerchantApplyFileDto>> entry : filesByType.entrySet()) {
  260. LambdaQueryWrapper<MerchantApplyFile> deleteWrapper = new LambdaQueryWrapper<>();
  261. deleteWrapper.eq(MerchantApplyFile::getMerchantId, merchantId).eq(MerchantApplyFile::getFileType, entry.getKey());
  262. merchantApplyFileMapper.delete(deleteWrapper);
  263. for (MerchantApplyFileDto file : entry.getValue()) {
  264. MerchantApplyFile applyFile = new MerchantApplyFile();
  265. BeanUtils.copyProperties(file, applyFile);
  266. applyFile.setMerchantId(merchantId);
  267. applyFile.setCreateBy(merchantId.toString());
  268. applyFile.setUpdateBy(merchantId.toString());
  269. applyFile.setIsDelete(NOT_DELETED);
  270. merchantApplyFileMapper.insert(applyFile);
  271. }
  272. }
  273. }
  274. /**
  275. * 修改商户资料。
  276. * <p>
  277. * 基础信息只允许修改昵称和简介;入驻资料文件按本次提交的文件类型整组替换。
  278. * </p>
  279. *
  280. * @param req 修改参数
  281. */
  282. @Override
  283. @Transactional(rollbackFor = Exception.class)
  284. public void updateTechnician(MerchantApplyFileRequestDto req) {
  285. if (req == null) {
  286. throw new ServiceException("修改参数不能为空");
  287. }
  288. MaTechnician technician = req.getTechnician();
  289. MerchantProfileSubmitDTO submitDTO = new MerchantProfileSubmitDTO();
  290. submitDTO.setMerchantId(resolveMerchantId(technician));
  291. if (technician != null) {
  292. submitDTO.setNickName(technician.getTeNickName());
  293. submitDTO.setBrief(technician.getTeBrief());
  294. }
  295. // 处理入驻资料文件
  296. submitDTO.setFiles(req.getReq());
  297. submitMerchantProfile(submitDTO);
  298. }
  299. @Override
  300. public MerchantProfileVO getMerchantProfile(String openid) {
  301. MaTechnician merchant = getExistingMerchant(openid);
  302. List<MerchantApplyFile> files = listMerchantApplyFiles(merchant.getId());
  303. MerchantProfileVO profile = new MerchantProfileVO();
  304. profile.setMerchantId(merchant.getId());
  305. profile.setName(merchant.getTeName());
  306. profile.setSex(merchant.getTeSex());
  307. profile.setPhone(merchant.getTePhone());
  308. profile.setAddress(merchant.getTeAddress());
  309. profile.setAreaCode(merchant.getTeAreaCode());
  310. profile.setAvatar(merchant.getAvatar());
  311. profile.setServiceTag(merchant.getServiceTag());
  312. profile.setAuditStatus(merchant.getAuditStatus());
  313. LambdaQueryWrapper<CityOperationApplication> query1 = new LambdaQueryWrapper<>();
  314. query1.eq(CityOperationApplication::getMerchantId, merchant.getId());
  315. //query1.eq(CityOperationApplication::getStatus,1);
  316. query1.orderByDesc(CityOperationApplication::getCreateTime).last("limit 1");
  317. CityOperationApplication application = cityOperationApplicationMapper.selectOne(query1);
  318. profile.setProvinceCode(application.getProvinceCode());
  319. profile.setProvinceName(application.getProvinceName());
  320. profile.setCityCode(application.getCityCode());
  321. profile.setCityName(application.getCityName());
  322. profile.setOperationCenterId(application.getOperationCenterId());
  323. profile.setOperationCenterName(application.getOperationCenterName());
  324. profile.setNickName(buildTextItem(merchant.getTeNickName(), merchant.getPendingTeNickName(),
  325. merchant.getTeNickNameAuditStatus(), merchant.getTeNickNameAuditRemark()));
  326. profile.setBrief(buildTextItem(merchant.getTeBrief(), merchant.getPendingTeBrief(),
  327. merchant.getTeBriefAuditStatus(), merchant.getTeBriefAuditRemark()));
  328. profile.setFileGroups(buildFileGroups(files));
  329. return profile;
  330. }
  331. @Override
  332. @Transactional(rollbackFor = Exception.class)
  333. public void submitMerchantProfile(MerchantProfileSubmitDTO dto) {
  334. if (dto == null) {
  335. throw new ServiceException("修改参数不能为空");
  336. }
  337. checkProfileSubmitParam(dto);
  338. MaTechnician merchant = getExistingMerchant1(dto.getMerchantId());
  339. boolean changed = submitProfileTextFields(merchant, dto);
  340. List<MerchantApplyFileDto> files = resolveApplyFiles(dto.getFiles());
  341. if (!CollectionUtils.isEmpty(files)) {
  342. submitProfileFiles(merchant.getId(), files);
  343. changed = true;
  344. }
  345. if (!changed) {
  346. throw new ServiceException("请至少提交一项资料修改");
  347. }
  348. }
  349. private void checkProfileSubmitParam(MerchantProfileSubmitDTO dto) {
  350. if (dto.getNickName() != null) {
  351. checkProfileTextValue(dto.getNickName(), "昵称");
  352. }
  353. if (dto.getBrief() != null) {
  354. checkProfileTextValue(dto.getBrief(), "简介");
  355. }
  356. resolveApplyFiles(dto.getFiles());
  357. }
  358. @Override
  359. @Transactional(rollbackFor = Exception.class)
  360. public int auditMerchantProfile(Integer merchantId, MerchantProfileAuditDTO dto, LoginUser loginUser) {
  361. // 检查merchantId是否为空
  362. if (merchantId == null) {
  363. throw new ServiceException("商户ID不能为空");
  364. }
  365. // 检查dto参数是否为空
  366. if (dto == null) {
  367. throw new ServiceException("审核参数不能为空");
  368. }
  369. // 检查商户是否存在
  370. MaTechnician merchant = getExistingMerchant1(merchantId);
  371. if (merchant == null) {
  372. throw new ServiceException("商户不存在");
  373. }
  374. if ((AUDIT_APPROVED == dto.getAuditStatus() || AUDIT_REJECTED == dto.getAuditStatus())) {
  375. String profileAuditRemark = dto.getAuditRemark() == null ? "" : dto.getAuditRemark().trim();
  376. if (AUDIT_REJECTED == dto.getAuditStatus() && StringUtils.isBlank(profileAuditRemark)) {
  377. throw new ServiceException("审核驳回时审核备注不能为空");
  378. }
  379. if (AUDIT_APPROVED == dto.getAuditStatus()) {
  380. checkProfileAuditExpirationDates(dto);
  381. // 审核通过所有待审核资料
  382. return approveAllPendingProfile(merchant, dto, profileAuditRemark, loginUser);
  383. }
  384. // 审核驳回所有待审核资料
  385. return rejectAllPendingProfile(merchant, profileAuditRemark, loginUser);
  386. }
  387. return 0;
  388. }
  389. /**
  390. * 检查审核通过时,是否填写了身份证到期日期、健康证到期日期、从业资格证到期日期
  391. * @param dto
  392. */
  393. private void checkProfileAuditExpirationDates(MerchantProfileAuditDTO dto) {
  394. if (dto.getIdCardExpirationDate() == null
  395. || dto.getHealthCertificateExpirationDate() == null
  396. || dto.getQualificationCertificateExpirationDate() == null) {
  397. throw new ServiceException("审核通过时,身份证到期日期、健康证到期日期、从业资格证到期日期不能为空");
  398. }
  399. }
  400. /**
  401. * 审核通过所有待审核资料
  402. *
  403. * @param merchant
  404. * @param dto
  405. * @param auditRemark
  406. * @param loginUser
  407. * @return int
  408. */
  409. private int approveAllPendingProfile(MaTechnician merchant, MerchantProfileAuditDTO dto, String auditRemark, LoginUser loginUser) {
  410. List<MerchantApplyFile> pendingFiles = listPendingProfileFiles(merchant.getId());
  411. boolean hasPendingText = hasPendingProfileText(merchant);
  412. if (!hasPendingText && CollectionUtils.isEmpty(pendingFiles)) {
  413. throw new ServiceException("当前商户没有待审核资料");
  414. }
  415. int rows = approveProfileTechnicianInfo(merchant, dto, auditRemark, loginUser);
  416. if (!CollectionUtils.isEmpty(pendingFiles)) {
  417. rows += approvePendingProfileFiles(merchant.getId(), pendingFiles, auditRemark, loginUser);
  418. }
  419. //同时修改ma_technician表的审核状态
  420. LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<MaTechnician>();
  421. updateWrapper.eq(MaTechnician::getId, merchant.getId()).set(MaTechnician::getAuditStatus, dto.getAuditStatus());
  422. rows += maTechnicianMapper.update(null, updateWrapper);
  423. return rows;
  424. }
  425. /**
  426. * 审核驳回所有待审核资料
  427. *
  428. * @param merchant
  429. * @param auditRemark
  430. * @param loginUser
  431. * @return int
  432. */
  433. private int rejectAllPendingProfile(MaTechnician merchant, String auditRemark, LoginUser loginUser) {
  434. List<MerchantApplyFile> pendingFiles = listPendingProfileFiles(merchant.getId());
  435. boolean hasPendingText = hasPendingProfileText(merchant);
  436. if (!hasPendingText && CollectionUtils.isEmpty(pendingFiles)) {
  437. throw new ServiceException("当前商户没有待审核资料");
  438. }
  439. int rows = 0;
  440. if (hasPendingText) {
  441. rows += rejectProfileTextInfo(merchant, auditRemark, loginUser);
  442. }
  443. if (!CollectionUtils.isEmpty(pendingFiles)) {
  444. rows += rejectPendingProfileFiles(merchant.getId(), auditRemark, loginUser);
  445. }
  446. //同时修改ma_technician表的审核状态
  447. LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<MaTechnician>();
  448. updateWrapper.eq(MaTechnician::getId, merchant.getId()).set(MaTechnician::getAuditStatus, AUDIT_REJECTED);
  449. rows += maTechnicianMapper.update(null, updateWrapper);
  450. return rows;
  451. }
  452. /**
  453. * 检查商户是否有待审核资料(昵称或简介)
  454. *
  455. * @param merchant
  456. * @return boolean
  457. */
  458. private boolean hasPendingProfileText(MaTechnician merchant) {
  459. return Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())
  460. || Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus());
  461. }
  462. /**
  463. * 获取待审核的申请入驻文件资料
  464. *
  465. * @param merchantId
  466. * @return List<MerchantApplyFile>
  467. */
  468. private List<MerchantApplyFile> listPendingProfileFiles(Integer merchantId) {
  469. LambdaQueryWrapper<MerchantApplyFile> queryWrapper = Wrappers.lambdaQuery();
  470. queryWrapper.eq(MerchantApplyFile::getMerchantId, merchantId).eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING);
  471. return merchantApplyFileMapper.selectList(queryWrapper);
  472. }
  473. /**
  474. * 审核通过所有待审核资料(昵称或简介)
  475. *
  476. * @param merchant
  477. * @param dto
  478. * @param auditRemark
  479. * @param loginUser
  480. * @return int
  481. */
  482. private int approveProfileTechnicianInfo(MaTechnician merchant, MerchantProfileAuditDTO dto, String auditRemark, LoginUser loginUser) {
  483. LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
  484. updateWrapper.eq(MaTechnician::getId, merchant.getId())
  485. .set(MaTechnician::getIdCardExpirationDate, dto.getIdCardExpirationDate())
  486. .set(MaTechnician::getHealthCertificateExpirationDate, dto.getHealthCertificateExpirationDate())
  487. .set(MaTechnician::getQualificationCertificateExpirationDate, dto.getQualificationCertificateExpirationDate())
  488. .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
  489. .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
  490. // 审核通过昵称
  491. if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())) {
  492. updateWrapper.set(MaTechnician::getTeNickName, merchant.getPendingTeNickName())
  493. .set(MaTechnician::getPendingTeNickName, null)
  494. .set(MaTechnician::getTeNickNameAuditStatus, PROFILE_AUDIT_APPROVED)
  495. .set(MaTechnician::getTeNickNameAuditRemark, auditRemark);
  496. }
  497. // 审核通过简介
  498. if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus())) {
  499. updateWrapper.set(MaTechnician::getTeBrief, merchant.getPendingTeBrief())
  500. .set(MaTechnician::getPendingTeBrief, null)
  501. .set(MaTechnician::getTeBriefAuditStatus, PROFILE_AUDIT_APPROVED)
  502. .set(MaTechnician::getTeBriefAuditRemark, auditRemark);
  503. }
  504. return maTechnicianMapper.update(null, updateWrapper);
  505. }
  506. private int rejectProfileTextInfo(MaTechnician merchant, String auditRemark, LoginUser loginUser) {
  507. LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
  508. updateWrapper.eq(MaTechnician::getId, merchant.getId())
  509. .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
  510. .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
  511. if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())) {
  512. updateWrapper.set(MaTechnician::getTeNickNameAuditStatus, PROFILE_AUDIT_REJECTED)
  513. .set(MaTechnician::getTeNickNameAuditRemark, auditRemark);
  514. }
  515. if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus())) {
  516. updateWrapper.set(MaTechnician::getTeBriefAuditStatus, PROFILE_AUDIT_REJECTED)
  517. .set(MaTechnician::getTeBriefAuditRemark, auditRemark);
  518. }
  519. return maTechnicianMapper.update(null, updateWrapper);
  520. }
  521. /**
  522. * 审核通过所有待审核文件资料
  523. *
  524. * @param merchantId
  525. * @param pendingFiles
  526. * @param auditRemark
  527. * @param loginUser
  528. * @return int
  529. */
  530. private int approvePendingProfileFiles(Integer merchantId, List<MerchantApplyFile> pendingFiles, String auditRemark, LoginUser loginUser) {
  531. Set<String> fileTypes = pendingFiles.stream()
  532. .map(MerchantApplyFile::getFileType)
  533. .filter(StringUtils::isNotBlank)
  534. .collect(Collectors.toCollection(LinkedHashSet::new));
  535. if (fileTypes.isEmpty()) {
  536. return 0;
  537. }
  538. String updateBy = getAuditUserName(loginUser);
  539. LambdaUpdateWrapper<MerchantApplyFile> deleteOldWrapper = Wrappers.lambdaUpdate();
  540. deleteOldWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  541. .in(MerchantApplyFile::getFileType, fileTypes)
  542. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_APPROVED)
  543. .set(MerchantApplyFile::getIsDelete, 1)
  544. .set(MerchantApplyFile::getUpdateBy, updateBy)
  545. .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
  546. int rows = merchantApplyFileMapper.update(null, deleteOldWrapper);
  547. LambdaUpdateWrapper<MerchantApplyFile> auditWrapper = Wrappers.lambdaUpdate();
  548. auditWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  549. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING)
  550. .set(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_APPROVED)
  551. .set(MerchantApplyFile::getAuditRemark, auditRemark)
  552. .set(MerchantApplyFile::getUpdateBy, updateBy)
  553. .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
  554. return rows + merchantApplyFileMapper.update(null, auditWrapper);
  555. }
  556. private int rejectPendingProfileFiles(Integer merchantId, String auditRemark, LoginUser loginUser) {
  557. LambdaUpdateWrapper<MerchantApplyFile> auditWrapper = Wrappers.lambdaUpdate();
  558. auditWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  559. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING)
  560. .set(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_REJECTED)
  561. .set(MerchantApplyFile::getAuditRemark, auditRemark)
  562. .set(MerchantApplyFile::getUpdateBy, getAuditUserName(loginUser))
  563. .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
  564. return merchantApplyFileMapper.update(null, auditWrapper);
  565. }
  566. /**
  567. * 获取存在的商户信息
  568. *
  569. * @param merchantId
  570. * @return MaTechnician
  571. */
  572. private MaTechnician getExistingMerchant(String openid) {
  573. MaTechnician merchant = maTechnicianMapper.selectOne(Wrappers.lambdaQuery(MaTechnician.class)
  574. .eq(MaTechnician::getCOpenid, openid));
  575. if (merchant == null || (merchant.getIsDelete() != null && !NOT_DELETED.equals(merchant.getIsDelete()))) {
  576. throw new ServiceException("商户不存在或已删除");
  577. }
  578. return merchant;
  579. }
  580. /**
  581. *
  582. * @param merchantId
  583. * @return
  584. */
  585. private MaTechnician getExistingMerchant1(Integer merchantId) {
  586. MaTechnician merchant = maTechnicianMapper.selectById(merchantId);
  587. if (merchant == null || (merchant.getIsDelete() != null && !NOT_DELETED.equals(merchant.getIsDelete()))) {
  588. throw new ServiceException("商户不存在或已删除");
  589. }
  590. return merchant;
  591. }
  592. /**
  593. * 提交文本资料
  594. *
  595. * @param merchant
  596. * @param dto
  597. * @return boolean
  598. */
  599. private boolean submitProfileTextFields(MaTechnician merchant, MerchantProfileSubmitDTO dto) {
  600. boolean changed = false;
  601. LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
  602. updateWrapper.eq(MaTechnician::getId, merchant.getId());
  603. // 处理昵称
  604. if (dto.getNickName() != null) {
  605. String nickName = checkProfileTextValue(dto.getNickName(), "昵称");
  606. if (PROFILE_AUDIT_PENDING == valueOrApproved(merchant.getTeNickNameAuditStatus())) {
  607. throw new ServiceException("该资料正在审核中,请勿重复提交");
  608. }
  609. if (!nickName.equals(merchant.getTeNickName()) || PROFILE_AUDIT_REJECTED == valueOrApproved(merchant.getTeNickNameAuditStatus())) {
  610. updateWrapper.set(MaTechnician::getPendingTeNickName, nickName)
  611. .set(MaTechnician::getTeNickNameAuditStatus, PROFILE_AUDIT_PENDING)
  612. .set(MaTechnician::getTeNickNameAuditRemark, null);
  613. changed = true;
  614. }
  615. }
  616. // 处理简介
  617. if (dto.getBrief() != null) {
  618. String brief = checkProfileTextValue(dto.getBrief(), "简介");
  619. if (PROFILE_AUDIT_PENDING == valueOrApproved(merchant.getTeBriefAuditStatus())) {
  620. throw new ServiceException("该资料正在审核中,请勿重复提交");
  621. }
  622. if (!brief.equals(merchant.getTeBrief()) || PROFILE_AUDIT_REJECTED == valueOrApproved(merchant.getTeBriefAuditStatus())) {
  623. updateWrapper.set(MaTechnician::getPendingTeBrief, brief)
  624. .set(MaTechnician::getTeBriefAuditStatus, PROFILE_AUDIT_PENDING)
  625. .set(MaTechnician::getTeBriefAuditRemark, null);
  626. changed = true;
  627. }
  628. }
  629. if (changed) {
  630. updateWrapper.set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
  631. // 同时修改audit_status字段的值
  632. updateWrapper.set(MaTechnician::getAuditStatus, 1);
  633. int rows = maTechnicianMapper.update(null, updateWrapper);
  634. if (rows <= 0) {
  635. throw new ServiceException("提交商户资料审核失败");
  636. }
  637. }
  638. return changed;
  639. }
  640. /**
  641. * 检查文本资料是否为空
  642. *
  643. * @param value
  644. * @param fieldName
  645. * @return String
  646. */
  647. private String checkProfileTextValue(String value, String fieldName) {
  648. if (StringUtils.isBlank(value)) {
  649. throw new ServiceException(fieldName + "不能为空");
  650. }
  651. return value.trim();
  652. }
  653. private int valueOrApproved(Integer status) {
  654. return status == null ? PROFILE_AUDIT_APPROVED : status;
  655. }
  656. private void submitProfileFiles(Integer merchantId, List<MerchantApplyFileDto> files) {
  657. Map<String, List<MerchantApplyFileDto>> filesByType = files.stream()
  658. .collect(Collectors.groupingBy(MerchantApplyFileDto::getFileType, LinkedHashMap::new, Collectors.toList()));
  659. for (Map.Entry<String, List<MerchantApplyFileDto>> entry : filesByType.entrySet()) {
  660. String fileType = entry.getKey();
  661. checkProfileFileType(fileType);
  662. if (hasPendingProfileFile(merchantId, fileType)) {
  663. throw new ServiceException("该资料正在审核中,请勿重复提交");
  664. }
  665. deleteRejectedProfileFiles(merchantId, fileType);
  666. String batchNo = UUID.randomUUID().toString().replace("-", "");
  667. for (MerchantApplyFileDto file : entry.getValue()) {
  668. MerchantApplyFile applyFile = new MerchantApplyFile();
  669. BeanUtils.copyProperties(file, applyFile);
  670. applyFile.setMerchantId(merchantId);
  671. applyFile.setApplyBatchNo(batchNo);
  672. applyFile.setAuditStatus(PROFILE_AUDIT_PENDING);
  673. applyFile.setAuditRemark(null);
  674. applyFile.setCreateBy(merchantId.toString());
  675. applyFile.setUpdateBy(merchantId.toString());
  676. applyFile.setIsDelete(NOT_DELETED);
  677. merchantApplyFileMapper.insert(applyFile);
  678. }
  679. }
  680. }
  681. private void checkProfileFileType(String fileType) {
  682. if (!PROFILE_FILE_TYPES.contains(fileType)) {
  683. throw new ServiceException("资料文件类型不支持修改");
  684. }
  685. }
  686. private boolean hasPendingProfileFile(Integer merchantId, String fileType) {
  687. LambdaQueryWrapper<MerchantApplyFile> queryWrapper = Wrappers.lambdaQuery();
  688. queryWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  689. .eq(MerchantApplyFile::getFileType, fileType)
  690. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING);
  691. return merchantApplyFileMapper.selectCount(queryWrapper) > 0;
  692. }
  693. private void deleteRejectedProfileFiles(Integer merchantId, String fileType) {
  694. LambdaUpdateWrapper<MerchantApplyFile> updateWrapper = Wrappers.lambdaUpdate();
  695. updateWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  696. .eq(MerchantApplyFile::getFileType, fileType)
  697. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_REJECTED)
  698. .set(MerchantApplyFile::getIsDelete, 1)
  699. .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
  700. merchantApplyFileMapper.update(null, updateWrapper);
  701. }
  702. private int auditProfileNickName(Integer merchantId, Integer auditStatus, String auditRemark, LoginUser loginUser) {
  703. MaTechnician merchant = getExistingMerchant1(merchantId);
  704. if (!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeNickNameAuditStatus())) {
  705. throw new ServiceException("昵称资料不是审核中状态");
  706. }
  707. LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
  708. updateWrapper.eq(MaTechnician::getId, merchantId)
  709. .set(MaTechnician::getTeNickNameAuditStatus, auditStatus)
  710. .set(MaTechnician::getTeNickNameAuditRemark, auditRemark)
  711. .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
  712. .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
  713. if (PROFILE_AUDIT_APPROVED == auditStatus) {
  714. updateWrapper.set(MaTechnician::getTeNickName, merchant.getPendingTeNickName())
  715. .set(MaTechnician::getPendingTeNickName, null);
  716. }
  717. return maTechnicianMapper.update(null, updateWrapper);
  718. }
  719. private int auditProfileBrief(Integer merchantId, Integer auditStatus, String auditRemark, LoginUser loginUser) {
  720. MaTechnician merchant = getExistingMerchant1(merchantId);
  721. if (!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(merchant.getTeBriefAuditStatus())) {
  722. throw new ServiceException("简介资料不是审核中状态");
  723. }
  724. LambdaUpdateWrapper<MaTechnician> updateWrapper = Wrappers.lambdaUpdate();
  725. updateWrapper.eq(MaTechnician::getId, merchantId)
  726. .set(MaTechnician::getTeBriefAuditStatus, auditStatus)
  727. .set(MaTechnician::getTeBriefAuditRemark, auditRemark)
  728. .set(MaTechnician::getUpdateBy, getAuditUserName(loginUser))
  729. .set(MaTechnician::getUpdateTime, DateUtils.getNowDate());
  730. if (PROFILE_AUDIT_APPROVED == auditStatus) {
  731. updateWrapper.set(MaTechnician::getTeBrief, merchant.getPendingTeBrief())
  732. .set(MaTechnician::getPendingTeBrief, null);
  733. }
  734. return maTechnicianMapper.update(null, updateWrapper);
  735. }
  736. private int auditProfileFile(Integer merchantId, String fileType, Integer auditStatus, String auditRemark, LoginUser loginUser) {
  737. if (StringUtils.isBlank(fileType)) {
  738. throw new ServiceException("文件类型不能为空");
  739. }
  740. checkProfileFileType(fileType);
  741. if (!hasPendingProfileFile(merchantId, fileType)) {
  742. throw new ServiceException("该文件资料不是审核中状态");
  743. }
  744. String updateBy = getAuditUserName(loginUser);
  745. if (PROFILE_AUDIT_APPROVED == auditStatus) {
  746. LambdaUpdateWrapper<MerchantApplyFile> deleteOldWrapper = Wrappers.lambdaUpdate();
  747. deleteOldWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  748. .eq(MerchantApplyFile::getFileType, fileType)
  749. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_APPROVED)
  750. .set(MerchantApplyFile::getIsDelete, 1)
  751. .set(MerchantApplyFile::getUpdateBy, updateBy)
  752. .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
  753. merchantApplyFileMapper.update(null, deleteOldWrapper);
  754. }
  755. LambdaUpdateWrapper<MerchantApplyFile> auditWrapper = Wrappers.lambdaUpdate();
  756. auditWrapper.eq(MerchantApplyFile::getMerchantId, merchantId)
  757. .eq(MerchantApplyFile::getFileType, fileType)
  758. .eq(MerchantApplyFile::getAuditStatus, PROFILE_AUDIT_PENDING)
  759. .set(MerchantApplyFile::getAuditStatus, auditStatus)
  760. .set(MerchantApplyFile::getAuditRemark, auditRemark)
  761. .set(MerchantApplyFile::getUpdateBy, updateBy)
  762. .set(MerchantApplyFile::getUpdateTime, LocalDateTime.now());
  763. return merchantApplyFileMapper.update(null, auditWrapper);
  764. }
  765. private String getAuditUserName(LoginUser loginUser) {
  766. if (loginUser != null && loginUser.getUser() != null) {
  767. return loginUser.getUser().getUserName();
  768. }
  769. return null;
  770. }
  771. private MerchantProfileTextItemVO buildTextItem(String value, String pendingValue, Integer auditStatus, String auditRemark) {
  772. MerchantProfileTextItemVO item = new MerchantProfileTextItemVO();
  773. item.setValue(value);
  774. item.setPendingValue(pendingValue);
  775. item.setAuditStatus(shouldShowAuditStatus(auditStatus) ? auditStatus : null);
  776. item.setAuditStatusText(shouldShowAuditStatus(auditStatus) ? getProfileAuditStatusText(auditStatus) : null);
  777. item.setAuditRemark(shouldShowAuditStatus(auditStatus) ? auditRemark : null);
  778. item.setEditable(!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(auditStatus));
  779. return item;
  780. }
  781. private List<MerchantProfileFileGroupVO> buildFileGroups(List<MerchantApplyFile> files) {
  782. Map<String, List<MerchantApplyFile>> filesByType = CollectionUtils.isEmpty(files)
  783. ? Collections.emptyMap()
  784. : files.stream()
  785. .filter(Objects::nonNull)
  786. .filter(file -> PROFILE_FILE_TYPES.contains(file.getFileType()))
  787. .collect(Collectors.groupingBy(MerchantApplyFile::getFileType, LinkedHashMap::new, Collectors.toList()));
  788. List<MerchantProfileFileGroupVO> groups = new ArrayList<>();
  789. for (String fileType : PROFILE_FILE_TYPES) {
  790. List<MerchantApplyFile> typeFiles = filesByType.getOrDefault(fileType, Collections.emptyList());
  791. List<MerchantApplyFile> officialFiles = typeFiles.stream()
  792. .filter(file -> Integer.valueOf(PROFILE_AUDIT_APPROVED).equals(file.getAuditStatus()))
  793. .collect(Collectors.toList());
  794. List<MerchantApplyFile> pendingFiles = typeFiles.stream()
  795. .filter(file -> !Integer.valueOf(PROFILE_AUDIT_APPROVED).equals(file.getAuditStatus()))
  796. .collect(Collectors.toList());
  797. Integer auditStatus = resolveFileGroupAuditStatus(pendingFiles);
  798. MerchantProfileFileGroupVO group = new MerchantProfileFileGroupVO();
  799. group.setFileType(fileType);
  800. group.setFileTypeName(FileTypeEnum.getDescByCode(fileType));
  801. group.setOfficialFiles(toProfileFileVOList(officialFiles));
  802. group.setPendingFiles(toProfileFileVOList(pendingFiles));
  803. group.setAuditStatus(shouldShowAuditStatus(auditStatus) ? auditStatus : null);
  804. group.setAuditStatusText(shouldShowAuditStatus(auditStatus) ? getProfileAuditStatusText(auditStatus) : null);
  805. group.setAuditRemark(shouldShowAuditStatus(auditStatus) ? resolveFileGroupAuditRemark(pendingFiles) : null);
  806. group.setEditable(!Integer.valueOf(PROFILE_AUDIT_PENDING).equals(auditStatus));
  807. groups.add(group);
  808. }
  809. return groups;
  810. }
  811. private Integer resolveFileGroupAuditStatus(List<MerchantApplyFile> pendingFiles) {
  812. if (CollectionUtils.isEmpty(pendingFiles)) {
  813. return null;
  814. }
  815. if (pendingFiles.stream().anyMatch(file -> Integer.valueOf(PROFILE_AUDIT_PENDING).equals(file.getAuditStatus()))) {
  816. return PROFILE_AUDIT_PENDING;
  817. }
  818. return pendingFiles.get(0).getAuditStatus();
  819. }
  820. private String resolveFileGroupAuditRemark(List<MerchantApplyFile> pendingFiles) {
  821. if (CollectionUtils.isEmpty(pendingFiles)) {
  822. return null;
  823. }
  824. return pendingFiles.stream()
  825. .map(MerchantApplyFile::getAuditRemark)
  826. .filter(StringUtils::isNotBlank)
  827. .findFirst()
  828. .orElse(null);
  829. }
  830. private List<MerchantProfileFileVO> toProfileFileVOList(List<MerchantApplyFile> files) {
  831. if (CollectionUtils.isEmpty(files)) {
  832. return Collections.emptyList();
  833. }
  834. return files.stream().map(this::toProfileFileVO).collect(Collectors.toList());
  835. }
  836. private MerchantProfileFileVO toProfileFileVO(MerchantApplyFile file) {
  837. MerchantProfileFileVO vo = new MerchantProfileFileVO();
  838. vo.setId(file.getId());
  839. vo.setFileName(file.getFileName());
  840. vo.setFileUrl(file.getFileUrl());
  841. vo.setFileSize(file.getFileSize());
  842. vo.setContentType(file.getContentType());
  843. vo.setApplyBatchNo(file.getApplyBatchNo());
  844. vo.setAuditStatus(file.getAuditStatus());
  845. vo.setAuditRemark(file.getAuditRemark());
  846. return vo;
  847. }
  848. private boolean shouldShowAuditStatus(Integer auditStatus) {
  849. return auditStatus != null && !Integer.valueOf(PROFILE_AUDIT_APPROVED).equals(auditStatus);
  850. }
  851. private String getProfileAuditStatusText(Integer auditStatus) {
  852. if (Integer.valueOf(PROFILE_AUDIT_PENDING).equals(auditStatus)) {
  853. return "审核中";
  854. }
  855. if (Integer.valueOf(PROFILE_AUDIT_REJECTED).equals(auditStatus)) {
  856. return "审核驳回";
  857. }
  858. return null;
  859. }
  860. /**
  861. * 修改商户基础信息,仅允许修改昵称和简介。
  862. *
  863. * @param technician 商户基础信息
  864. */
  865. private void updateTechnicianBaseInfo(MaTechnician technician) {
  866. Integer merchantId = technician.getId();
  867. if (merchantId == null) {
  868. throw new ServiceException("商户ID不能为空");
  869. }
  870. if (StringUtils.isBlank(technician.getTeNickName())) {
  871. throw new ServiceException("昵称不能为空");
  872. }
  873. if (StringUtils.isBlank(technician.getTeBrief())) {
  874. throw new ServiceException("简介不能为空");
  875. }
  876. LambdaUpdateWrapper<MaTechnician> updateWrapper = new LambdaUpdateWrapper<>();
  877. updateWrapper.eq(MaTechnician::getId, merchantId);
  878. updateWrapper.set(MaTechnician::getTeNickName, technician.getTeNickName().trim());
  879. updateWrapper.set(MaTechnician::getTeBrief, technician.getTeBrief().trim());
  880. // 审核状态默认设为1 待审核
  881. updateWrapper.set(MaTechnician::getAuditStatus, 1);
  882. int rows = maTechnicianMapper.update(null, updateWrapper);
  883. if (rows <= 0) {
  884. throw new ServiceException("修改商户基础信息失败");
  885. }
  886. }
  887. /**
  888. * 按本次提交的文件类型整组替换商户入驻资料文件。
  889. *
  890. * @param merchantId 商户ID
  891. * @param files 入驻资料文件
  892. */
  893. private void replaceApplyFilesBySubmittedTypes(Integer merchantId, List<MerchantApplyFileDto> files) {
  894. if (CollectionUtils.isEmpty(files)) {
  895. return;
  896. }
  897. replaceApplyFiles(merchantId, files);
  898. }
  899. /**
  900. * 从商户基础信息中解析商户ID。
  901. *
  902. * @param technician 商户基础信息
  903. * @return Integer 商户ID
  904. */
  905. private Integer resolveMerchantId(MaTechnician technician) {
  906. if (technician != null && technician.getId() != null) {
  907. return technician.getId();
  908. }
  909. throw new ServiceException("商户ID不能为空");
  910. }
  911. private void checkApplyFileParam(MerchantApplyFileDto file) {
  912. if (file == null) {
  913. throw new ServiceException("入驻资料文件不能为空");
  914. }
  915. if (StringUtils.isBlank(file.getFileType())) {
  916. throw new ServiceException("文件类型不能为空");
  917. }
  918. if (StringUtils.isBlank(file.getFileUrl())) {
  919. throw new ServiceException("文件访问地址不能为空");
  920. }
  921. }
  922. /**
  923. * 商户入住前置条件校验
  924. *
  925. * @param req
  926. * @param phone
  927. */
  928. private void getMaTechnician(MaTechnicianAppAddVo req, String phone) {
  929. // 1. 判断当前用户是否已入驻
  930. MaTechnician userProfile = getMaTechnician(req);
  931. if (userProfile != null) {
  932. throw new RuntimeException("当前用户已入驻,请勿重复提交");
  933. }
  934. // 2. 判断手机号是否已存在
  935. LambdaQueryWrapper<MaTechnician> queryPhoneWrapper = new LambdaQueryWrapper<>();
  936. queryPhoneWrapper.eq(MaTechnician::getTePhone, phone);
  937. MaTechnician maTechnicianPhone = maTechnicianMapper.selectOne(queryPhoneWrapper);
  938. if (maTechnicianPhone != null) {
  939. throw new RuntimeException("手机号已存在,请更换手机号");
  940. }
  941. //3、判断手机号是否已绑定其他用户
  942. LambdaQueryWrapper<MaTechnician> queryTePhoneWrapper = new LambdaQueryWrapper<>();
  943. queryTePhoneWrapper.eq(MaTechnician::getTePhone, phone);
  944. queryTePhoneWrapper.eq(MaTechnician::getAuditStatus, 2);
  945. MaTechnician maTechnicianTePhone = maTechnicianMapper.selectOne(queryTePhoneWrapper);
  946. if (maTechnicianTePhone != null) {
  947. throw new RuntimeException("手机号已被其他用户绑定,请更换手机号");
  948. }
  949. }
  950. /**
  951. * 判断当前用户是否已入驻
  952. *
  953. * @param req
  954. * @return MaTechnician
  955. */
  956. private MaTechnician getMaTechnician(MaTechnicianAppAddVo req) {
  957. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  958. queryWrapper.eq(MaTechnician::getTePhone, req.getTePhone());
  959. queryWrapper.eq(MaTechnician::getAuditStatus, 2);
  960. queryWrapper.eq(MaTechnician::getServiceTag, req.getServiceTag());
  961. MaTechnician userProfile = maTechnicianMapper.selectOne(queryWrapper);
  962. return userProfile;
  963. }
  964. /**
  965. * 查询商户服务项目列表
  966. *
  967. * @param merchantId 商户id
  968. * @param auditStatus 审核状态
  969. * @return List<MaProject>
  970. */
  971. @Override
  972. public List<MaProject> selectMaTechnicianListBy(Integer merchantId, Integer auditStatus) {
  973. LambdaQueryWrapper<MaProject> queryWrapper = new LambdaQueryWrapper<>();
  974. queryWrapper.eq(MaProject::getMerchantId, merchantId);
  975. queryWrapper.eq(MaProject::getAuditStatus, auditStatus);
  976. return maProjectMapper.selectList(queryWrapper);
  977. }
  978. /**
  979. * 查询服务分类项目列表
  980. *
  981. * @param typeId 技师类型
  982. * @return 技师列表
  983. */
  984. @Override
  985. public List<Project> selectTechnicianListBy(String typeId) {
  986. LambdaQueryWrapper<Project> queryWrapper = new LambdaQueryWrapper<>();
  987. queryWrapper.eq(Project::getType, typeId);
  988. return projectMapper.selectList(queryWrapper);
  989. }
  990. /**
  991. * 查询技师
  992. *
  993. * @param id 技师主键
  994. * @return 技师
  995. */
  996. @Override
  997. public MaTechnician selectMaTechnicianById(Long id) {
  998. return maTechnicianMapper.selectMaTechnicianById(id);
  999. }
  1000. /**
  1001. * 查询技师列表
  1002. *
  1003. * @param maTechnician 技师
  1004. * @return 技师
  1005. */
  1006. @Override
  1007. public List<MaTechnician> selectMaTechnicianList(MaTechnician maTechnician) {
  1008. return maTechnicianMapper.selectMaTechnicianList(maTechnician);
  1009. }
  1010. /**
  1011. * 新增技师
  1012. *
  1013. * @param maTechnicianAppAddVo 技师
  1014. * @return 结果
  1015. */
  1016. @Override
  1017. @Transactional(rollbackFor = Exception.class)
  1018. public int insertMaTechnician(MaTechnicianAppAddVo maTechnicianAppAddVo) {
  1019. MaTechnician maTechnician = new MaTechnician();
  1020. BeanUtils.copyProperties(maTechnicianAppAddVo, maTechnician);
  1021. int rows = maTechnicianMapper.insertMaTechnician(maTechnician);
  1022. if (maTechnicianAppAddVo.getProjectIds() != null && !maTechnicianAppAddVo.getProjectIds().isEmpty()) {
  1023. insertProjectRelations(maTechnician.getId(), new LinkedHashSet<>(maTechnicianAppAddVo.getProjectIds()));
  1024. }
  1025. return rows;
  1026. }
  1027. /**
  1028. * 后台新增商户
  1029. *
  1030. * @param dto 新增商户参数
  1031. * @param loginUser 当前登录用户
  1032. * @return 结果
  1033. */
  1034. @Override
  1035. @Transactional(rollbackFor = Exception.class)
  1036. public int insertMerchant(MaTechnicianMerchantAddDTO dto, LoginUser loginUser) {
  1037. MerchantProjectSelection selection = checkMerchantAddParam(dto);
  1038. String userName = loginUser.getUser().getUserName();
  1039. MaTechnician maTechnician = new MaTechnician();
  1040. maTechnician.setTeName(dto.getTeName().trim());
  1041. maTechnician.setTeNickName(dto.getTeNickName().trim());
  1042. maTechnician.setTeSex(dto.getTeSex());
  1043. maTechnician.setTePhone(dto.getTePhone().trim());
  1044. maTechnician.setOpenService(joinIds(selection.getCategoryIds()));
  1045. maTechnician.setTeProject(joinProjectTitles(selection.getProjectIds(), selection.getProjectMap()));
  1046. maTechnician.setTechType(dto.getTechType());
  1047. maTechnician.setIsRecommend(normalizeSwitchValue(dto.getIsRecommend(), "是否推荐"));
  1048. maTechnician.setServiceState(SERVICE_STATE_AVAILABLE);
  1049. maTechnician.setPostState(POST_STATE_OFFLINE);
  1050. maTechnician.setTeIsEnable(ENABLED);
  1051. //上岗状态:默认-1 未上岗
  1052. maTechnician.setNStatus2(NS_STATUS_NOT_ON_DUTY);
  1053. maTechnician.setMerchantStatus(MERCHANT_STATUS_NORMAL);
  1054. //审核状态
  1055. maTechnician.setAuditStatus(AUDIT_APPROVED);
  1056. maTechnician.setTeAddress("");
  1057. maTechnician.setNStar(DEFAULT_STAT_VALUE);
  1058. maTechnician.setNNum(DEFAULT_STAT_VALUE);
  1059. maTechnician.setCreateBy(userName);
  1060. maTechnician.setUpdateBy(userName);
  1061. maTechnician.setCreateTime(DateUtils.getNowDate());
  1062. maTechnician.setUpdateTime(DateUtils.getNowDate());
  1063. int rows = maTechnicianMapper.insert(maTechnician);
  1064. if (rows <= 0) {
  1065. throw new ServiceException("新增商户失败");
  1066. }
  1067. insertProjectRelations(maTechnician.getId(), selection.getProjectIds());
  1068. return rows;
  1069. }
  1070. /**
  1071. * 后台编辑商户
  1072. *
  1073. * @param id 商户ID
  1074. * @param dto 编辑商户参数
  1075. * @param loginUser 当前登录用户
  1076. * @return 结果
  1077. */
  1078. @Override
  1079. @Transactional(rollbackFor = Exception.class)
  1080. public int updateMerchant(Integer id, MaTechnicianMerchantAddDTO dto, LoginUser loginUser) {
  1081. if (id == null) {
  1082. throw new ServiceException("商户ID不能为空");
  1083. }
  1084. MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id.intValue());
  1085. if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
  1086. throw new ServiceException("商户不存在或已删除");
  1087. }
  1088. MerchantProjectSelection selection = checkMerchantAddParam(dto);
  1089. String userName = loginUser.getUser().getUserName();
  1090. MaTechnician maTechnician = new MaTechnician();
  1091. maTechnician.setId(id);
  1092. maTechnician.setTeName(dto.getTeName().trim());
  1093. maTechnician.setTeNickName(dto.getTeNickName().trim());
  1094. maTechnician.setTeSex(dto.getTeSex());
  1095. maTechnician.setTePhone(dto.getTePhone().trim());
  1096. maTechnician.setOpenService(joinIds(selection.getCategoryIds()));
  1097. maTechnician.setTeProject(joinProjectTitles(selection.getProjectIds(), selection.getProjectMap()));
  1098. maTechnician.setTechType(dto.getTechType());
  1099. maTechnician.setIsRecommend(normalizeSwitchValue(dto.getIsRecommend(), "是否推荐"));
  1100. maTechnician.setUpdateBy(userName);
  1101. maTechnician.setUpdateTime(DateUtils.getNowDate());
  1102. int rows = maTechnicianMapper.updateMerchantById(maTechnician);
  1103. if (rows <= 0) {
  1104. throw new ServiceException("编辑商户失败");
  1105. }
  1106. replaceProjectRelations(id, selection.getProjectIds());
  1107. return rows;
  1108. }
  1109. /**
  1110. * 后台上传商户合同文件
  1111. *
  1112. * @param id 商户ID
  1113. * @param
  1114. * @param loginUser 当前登录用户
  1115. * @return 上传结果
  1116. */
  1117. @Override
  1118. @Transactional(rollbackFor = Exception.class)
  1119. public Integer uploadMerchantContract(Integer id, Map<String, Object> map, LoginUser loginUser) {
  1120. if (id == null) {
  1121. throw new ServiceException("商户ID不能为空");
  1122. }
  1123. MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id);
  1124. if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
  1125. throw new ServiceException("商户不存在或已删除");
  1126. }
  1127. // 合同的名称
  1128. String contractName = String.valueOf(map.get("contractName"));
  1129. // 合同文件的URL
  1130. String url = String.valueOf(map.get("url"));
  1131. if (StringUtils.isBlank(url)) {
  1132. throw new ServiceException("合同文件上传失败,未返回文件地址");
  1133. }
  1134. ContractRecord contractRecord = new ContractRecord();
  1135. contractRecord.setMerchantId(id);
  1136. contractRecord.setContractName(contractName);
  1137. contractRecord.setFileUrl(url);
  1138. contractRecord.setSignTime(DateUtils.getNowDate());
  1139. contractRecord.setSignerName(existsMerchant.getTeName());
  1140. contractRecord.setCreateTime(DateUtils.getNowDate());
  1141. int rows = contractRecordMapper.insert(contractRecord);
  1142. if (rows <= 0) {
  1143. throw new ServiceException("保存合同记录失败");
  1144. }
  1145. return rows;
  1146. }
  1147. /**
  1148. * 商户入驻审核。
  1149. *
  1150. * @param id 商户ID
  1151. * @param dto 审核提交参数
  1152. * @param loginUser 当前登录用户
  1153. * @return int 结果
  1154. */
  1155. @Override
  1156. @Transactional(rollbackFor = Exception.class)
  1157. public int submitMerchantAudit(Integer id, MaTechnicianAuditSubmitDTO dto, LoginUser loginUser) {
  1158. if (id == null) {
  1159. throw new ServiceException("商户ID不能为空");
  1160. }
  1161. if (dto == null) {
  1162. throw new ServiceException("审核参数不能为空");
  1163. }
  1164. checkEnumValue(dto.getAuditStatus(), "审核意见", AUDIT_APPROVED, AUDIT_REJECTED);
  1165. String auditRemark = dto.getAuditRemark() == null ? "" : dto.getAuditRemark().trim();
  1166. if (dto.getAuditStatus() == AUDIT_REJECTED && StringUtils.isBlank(auditRemark)) {
  1167. throw new ServiceException("审核驳回时审核备注不能为空");
  1168. }
  1169. if (auditRemark.length() > AUDIT_REMARK_MAX_LENGTH) {
  1170. throw new ServiceException("审核备注长度不能超过" + AUDIT_REMARK_MAX_LENGTH + "个字符");
  1171. }
  1172. MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id);
  1173. if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
  1174. throw new ServiceException("商户不存在或已删除");
  1175. }
  1176. Integer currentAuditStatus = existsMerchant.getAuditStatus();
  1177. if (currentAuditStatus == null || (currentAuditStatus != AUDIT_WAIT_ENTER)) {
  1178. throw new ServiceException("当前商户待入驻已审核,不用重复审核");
  1179. }
  1180. MaTechnician maTechnician = new MaTechnician();
  1181. maTechnician.setId(id);
  1182. if (dto.getAuditStatus() == AUDIT_APPROVED) {
  1183. maTechnician.setAuditStatus(1);
  1184. } else {
  1185. maTechnician.setAuditStatus(3);
  1186. }
  1187. maTechnician.setAuditRemark(auditRemark);
  1188. maTechnician.setApproveTime(DateUtils.getNowDate());
  1189. if (loginUser != null && loginUser.getUser() != null) {
  1190. maTechnician.setUpdateBy(loginUser.getUser().getUserName());
  1191. }
  1192. maTechnician.setUpdateTime(DateUtils.getNowDate());
  1193. int rows = maTechnicianMapper.submitMerchantAuditById(maTechnician);
  1194. if (rows <= 0) {
  1195. throw new ServiceException("商户待入驻审核失败");
  1196. }
  1197. return rows;
  1198. }
  1199. /**
  1200. * 后台查询商户证照
  1201. *
  1202. * @param id 商户ID
  1203. * @return MaTechnicianCertificateVO 商户证照
  1204. */
  1205. @Override
  1206. public MaTechnicianCertificateVO selectMerchantCertificate(Integer id) {
  1207. if (id == null) {
  1208. throw new ServiceException("商户ID不能为空");
  1209. }
  1210. LambdaQueryWrapper<MerchantApplyFile> queryWrapper = Wrappers.lambdaQuery();
  1211. queryWrapper.eq(MerchantApplyFile::getMerchantId, id);
  1212. List<MerchantApplyFile> merchantApplyFiles = merchantApplyFileMapper.selectList(queryWrapper);
  1213. MaTechnicianCertificateVO certificate = new MaTechnicianCertificateVO();
  1214. certificate.setMerchantId(id);
  1215. Map<String, List<String>> fileUrlsByType = CollectionUtils.isEmpty(merchantApplyFiles)
  1216. ? Collections.emptyMap()
  1217. : merchantApplyFiles.stream()
  1218. .filter(Objects::nonNull)
  1219. .filter(file -> StringUtils.isNotBlank(file.getFileType()) && StringUtils.isNotBlank(file.getFileUrl()))
  1220. .collect(Collectors.groupingBy(MerchantApplyFile::getFileType,
  1221. Collectors.mapping(MerchantApplyFile::getFileUrl, Collectors.toList())));
  1222. certificate.setAvatar(getCertificateFileUrls(fileUrlsByType, PORTRAIT.getCode()));
  1223. certificate.setLifePhotos(getCertificateFileUrls(fileUrlsByType, LIFE_PHOTO.getCode()));
  1224. certificate.setPromotionVideo(getCertificateFileUrls(fileUrlsByType, PROMOTION_VIDEO.getCode()));
  1225. certificate.setIdCardFrout(getCertificateFileUrls(fileUrlsByType, ID_CARD_FRONT.getCode()));
  1226. certificate.setIdCardBack(getCertificateFileUrls(fileUrlsByType, ID_CARD_BACK.getCode()));
  1227. certificate.setIdCardHandheld(getCertificateFileUrls(fileUrlsByType, ID_CARD_HANDHELD.getCode()));
  1228. certificate.setHealthCertificate(getCertificateFileUrls(fileUrlsByType, HEALTH_CERT.getCode()));
  1229. certificate.setQualificationCertificate(getCertificateFileUrls(fileUrlsByType, QUALIFICATION_CERT.getCode()));
  1230. certificate.setNoCrimeRecord(getCertificateFileUrls(fileUrlsByType, NO_CRIME_RECORD.getCode()));
  1231. certificate.setCommitmentPdf(getCertificateFileUrls(fileUrlsByType, COMMITMENT_LETTER.getCode()));
  1232. certificate.setCommitmentAudio(getCertificateFileUrls(fileUrlsByType, COMMITMENT_AUDIO.getCode()));
  1233. certificate.setCommitmentVideo(getCertificateFileUrls(fileUrlsByType, COMMITMENT_VIDEO.getCode()));
  1234. return certificate;
  1235. }
  1236. /**
  1237. * 获取指定证照类型的全部文件URL
  1238. *
  1239. * @param fileUrlsByType 文件类型与URL列表映射
  1240. * @param type 文件类型
  1241. * @return List<String> 文件URL列表
  1242. */
  1243. private List<String> getCertificateFileUrls(Map<String, List<String>> fileUrlsByType, String type) {
  1244. return fileUrlsByType.getOrDefault(type, Collections.emptyList());
  1245. }
  1246. /**
  1247. * 全量替换商户与服务项目关联关系。
  1248. *
  1249. * @param technicianId 商户ID
  1250. * @param projectIds 服务项目ID集合
  1251. */
  1252. private void replaceProjectRelations(Integer technicianId, Set<Integer> projectIds) {
  1253. if (technicianId == null) {
  1254. throw new ServiceException("商户ID不能为空");
  1255. }
  1256. maTeProjectMapper.deleteByTechnicianId(technicianId);
  1257. for (Integer projectId : projectIds) {
  1258. MaTeProject relation = new MaTeProject();
  1259. relation.setTeId(technicianId);
  1260. relation.setProjectId(projectId);
  1261. int rows = maTeProjectMapper.insert(relation);
  1262. if (rows <= 0) {
  1263. throw new ServiceException("编辑商户服务项目失败");
  1264. }
  1265. }
  1266. }
  1267. /**
  1268. * 后台查询商户入驻审核列表
  1269. *
  1270. * @param page 分页参数
  1271. * @param dto 查询条件
  1272. * @return 商户入驻审核分页列表
  1273. */
  1274. @Override
  1275. public Page<MaTechnicianAuditListVO> selectMerchantAuditList(Page<MaTechnicianAuditListVO> page, MaTechnicianAuditQueryDTO dto) {
  1276. if (dto != null && dto.getAuditStatus() != null) {
  1277. checkEnumValue(dto.getAuditStatus(), "审核状态", 0, 1, 2, 3);
  1278. }
  1279. Page<MaTechnicianAuditListVO> pageParam = page == null ? new Page<>(1, 10) : page;
  1280. return maTechnicianMapper.selectMerchantAuditList(pageParam, dto);
  1281. }
  1282. /**
  1283. * 后台查询商户列表
  1284. *
  1285. * @param page 分页参数
  1286. * @param dto 查询条件
  1287. * @return 商户分页列表
  1288. */
  1289. @Override
  1290. public Page<MaTechnicianMerchantListVO> selectMerchantList(Page<MaTechnicianMerchantListVO> page,
  1291. MaTechnicianMerchantQueryDTO dto) {
  1292. Page<MaTechnicianMerchantListVO> pageParam = page == null ? new Page<>(1, 10) : page;
  1293. return maTechnicianMapper.selectMerchantList(pageParam, dto);
  1294. }
  1295. /**
  1296. * 后台查询商户详情
  1297. *
  1298. * @param id 商户ID
  1299. * @return 商户详情
  1300. */
  1301. @Override
  1302. public MaTechnicianMerchantDetailVO selectMerchantDetail(Long id) {
  1303. if (id == null) {
  1304. throw new ServiceException("商户ID不能为空");
  1305. }
  1306. MaTechnicianMerchantDetailVO detail = maTechnicianMapper.selectMerchantDetailById(id);
  1307. if (detail == null) {
  1308. throw new ServiceException("商户不存在或已删除");
  1309. }
  1310. return detail;
  1311. }
  1312. /**
  1313. * 修改技师
  1314. *
  1315. * @param maTechnicianAppAddVo
  1316. * @return 结果
  1317. */
  1318. @Override
  1319. public int updateMaTechnician(MaTechnicianAppAddVo maTechnicianAppAddVo) {
  1320. MaTechnician maTechnician = new MaTechnician();
  1321. BeanUtils.copyProperties(maTechnicianAppAddVo, maTechnician);
  1322. return maTechnicianMapper.updateMaTechnician(maTechnician);
  1323. }
  1324. /**
  1325. * 批量删除技师
  1326. *
  1327. * @param ids 需要删除的技师主键
  1328. * @return 结果
  1329. */
  1330. @Override
  1331. public int deleteMaTechnicianByIds(Long[] ids) {
  1332. return maTechnicianMapper.deleteMaTechnicianByIds(ids);
  1333. }
  1334. /**
  1335. * 删除技师信息
  1336. *
  1337. * @param id 技师主键
  1338. * @return 结果
  1339. */
  1340. @Override
  1341. public int deleteMaTechnicianById(Long id) {
  1342. return maTechnicianMapper.deleteMaTechnicianById(id);
  1343. }
  1344. /**
  1345. * 首页选中的城市是否有开通服务
  1346. *
  1347. * @param areaCode
  1348. * @return
  1349. */
  1350. @Override
  1351. public Boolean isHasMerchantCity(String areaCode) {
  1352. return maTechnicianMapper.isHasMerchantCity(areaCode);
  1353. }
  1354. /**
  1355. * 首页按摩推荐
  1356. *
  1357. * @param dto
  1358. * @return
  1359. */
  1360. @Override
  1361. public List<MerchantVo> getMerchantRecommend(MassageMerchantRecommendDto dto) {
  1362. return maTechnicianMapper.getMerchantRecommend(dto);
  1363. }
  1364. private MerchantProjectSelection checkMerchantAddParam(MaTechnicianMerchantAddDTO dto) {
  1365. if (dto == null) {
  1366. throw new ServiceException("商户参数不能为空");
  1367. }
  1368. checkRequiredText(dto.getTeName(), "姓名", 10);
  1369. checkRequiredText(dto.getTeNickName(), "昵称", 10);
  1370. checkRequiredText(dto.getTePhone(), "电话", 11);
  1371. checkEnumValue(dto.getTeSex(), "性别", 0, 1);
  1372. Set<Integer> categoryIds = checkOpenServiceIds(dto.getOpenService());
  1373. checkEnumValue(dto.getTechType(), "商户类型", 0, 1);
  1374. if (dto.getIsRecommend() != null) {
  1375. checkEnumValue(dto.getIsRecommend(), "是否推荐", 0, 1);
  1376. }
  1377. return checkProjectIds(dto.getProjectIds(), categoryIds);
  1378. }
  1379. private void checkRequiredText(String value, String fieldName, int maxLength) {
  1380. if (StringUtils.isBlank(value)) {
  1381. throw new ServiceException(fieldName + "不能为空");
  1382. }
  1383. if (value.trim().length() > maxLength) {
  1384. throw new ServiceException(fieldName + "长度不能超过" + maxLength + "个字符");
  1385. }
  1386. }
  1387. private void checkEnumValue(Integer value, String fieldName, int... allowedValues) {
  1388. if (value == null) {
  1389. throw new ServiceException(fieldName + "不能为空");
  1390. }
  1391. for (int allowedValue : allowedValues) {
  1392. if (value == allowedValue) {
  1393. return;
  1394. }
  1395. }
  1396. throw new ServiceException(fieldName + "值不正确");
  1397. }
  1398. private Integer normalizeSwitchValue(Integer value, String fieldName) {
  1399. if (value == null) {
  1400. return 0;
  1401. }
  1402. checkEnumValue(value, fieldName, 0, 1);
  1403. return value;
  1404. }
  1405. /**
  1406. * 校验服务项目ID集合
  1407. *
  1408. * @param projectIds 服务项目ID集合
  1409. * @param categoryIds 服务类目ID集合
  1410. * @return 有效服务项目ID集合
  1411. */
  1412. private MerchantProjectSelection checkProjectIds(List<Integer> projectIds, Set<Integer> categoryIds) {
  1413. if (projectIds == null || projectIds.isEmpty()) {
  1414. throw new ServiceException("服务项目不能为空");
  1415. }
  1416. Set<Integer> distinctProjectIds = new LinkedHashSet<>();
  1417. for (Integer projectId : projectIds) {
  1418. if (projectId == null) {
  1419. throw new ServiceException("服务项目ID不能为空");
  1420. }
  1421. distinctProjectIds.add(projectId);
  1422. }
  1423. List<Project> projects = projectMapper.selectList(new LambdaQueryWrapper<Project>()
  1424. .in(Project::getId, distinctProjectIds)
  1425. .eq(Project::getIsDelete, 0));
  1426. if (projects.size() != distinctProjectIds.size()) {
  1427. throw new ServiceException("服务项目不存在或已删除");
  1428. }
  1429. Map<Integer, Project> projectMap = projects.stream()
  1430. .collect(Collectors.toMap(project -> project.getId(), Function.identity(), (left, right) -> left));
  1431. Set<Integer> projectCategoryIds = new LinkedHashSet<>();
  1432. for (Integer projectId : distinctProjectIds) {
  1433. Project project = projectMap.get(projectId);
  1434. if (project == null || project.getCategoryId() == null) {
  1435. throw new ServiceException("服务项目类目不能为空");
  1436. }
  1437. if (!categoryIds.contains(project.getCategoryId())) {
  1438. throw new ServiceException("服务项目不属于所选服务类目");
  1439. }
  1440. projectCategoryIds.add(project.getCategoryId());
  1441. }
  1442. if (!projectCategoryIds.containsAll(categoryIds)) {
  1443. throw new ServiceException("每个服务类目至少选择一个服务项目");
  1444. }
  1445. return new MerchantProjectSelection(categoryIds, distinctProjectIds, projectMap);
  1446. }
  1447. /**
  1448. * 校验服务类目ID集合
  1449. *
  1450. * @param openService 服务类目ID集合
  1451. * @return 去重后的服务类目ID集合
  1452. */
  1453. private Set<Integer> checkOpenServiceIds(List<Integer> openService) {
  1454. if (openService == null || openService.isEmpty()) {
  1455. throw new ServiceException("服务类目不能为空");
  1456. }
  1457. Set<Integer> categoryIds = new LinkedHashSet<>();
  1458. for (Integer categoryId : openService) {
  1459. if (categoryId == null) {
  1460. throw new ServiceException("服务类目ID不能为空");
  1461. }
  1462. categoryIds.add(categoryId);
  1463. }
  1464. return categoryIds;
  1465. }
  1466. private String joinIds(Set<Integer> ids) {
  1467. return ids.stream()
  1468. .map(String::valueOf)
  1469. .collect(Collectors.joining(","));
  1470. }
  1471. private String joinProjectTitles(Set<Integer> projectIds, Map<Integer, Project> projectMap) {
  1472. return projectIds.stream()
  1473. .map(projectMap::get)
  1474. .map(Project::getTitle)
  1475. .filter(StringUtils::isNotBlank)
  1476. .collect(Collectors.joining(","));
  1477. }
  1478. /**
  1479. * 新增商户与服务项目关联关系
  1480. *
  1481. * @param technicianId
  1482. * @param projectIds
  1483. */
  1484. private void insertProjectRelations(Integer technicianId, Set<Integer> projectIds) {
  1485. if (technicianId == null) {
  1486. throw new ServiceException("商户ID不能为空");
  1487. }
  1488. List<MaTeProject> relations = new ArrayList<>();
  1489. for (Integer projectId : projectIds) {
  1490. MaTeProject relation = new MaTeProject();
  1491. relation.setTeId(technicianId);
  1492. relation.setProjectId(projectId);
  1493. relations.add(relation);
  1494. }
  1495. int rows = maTeProjectMapper.insertBatch(relations);
  1496. if (rows != relations.size()) {
  1497. throw new ServiceException("新增商户服务项目失败");
  1498. }
  1499. }
  1500. private static class MerchantProjectSelection {
  1501. private final Set<Integer> categoryIds;
  1502. private final Set<Integer> projectIds;
  1503. private final Map<Integer, Project> projectMap;
  1504. private MerchantProjectSelection(Set<Integer> categoryIds, Set<Integer> projectIds, Map<Integer, Project> projectMap) {
  1505. this.categoryIds = categoryIds;
  1506. this.projectIds = projectIds;
  1507. this.projectMap = projectMap;
  1508. }
  1509. private Set<Integer> getCategoryIds() {
  1510. return categoryIds;
  1511. }
  1512. private Set<Integer> getProjectIds() {
  1513. return projectIds;
  1514. }
  1515. private Map<Integer, Project> getProjectMap() {
  1516. return projectMap;
  1517. }
  1518. }
  1519. /**
  1520. * 获取未申请技能列表
  1521. *
  1522. * @param userId
  1523. * @param typeId
  1524. * @return
  1525. */
  1526. @Override
  1527. public List<Project> getNotApplyList(Integer merchantId, Integer serviceTag) {
  1528. LambdaQueryWrapper<MaProject> query = new LambdaQueryWrapper<>();
  1529. query.eq(MaProject::getMerchantId, merchantId);
  1530. query.eq(MaProject::getServiceTag, serviceTag);
  1531. List<MaProject> maProjectList = maProjectMapper.selectList(query);
  1532. // 获取已申请技能ID集合
  1533. List<Integer> projectIdList = maProjectList.stream().map(MaProject::getProjectId).collect(Collectors.toList());
  1534. if (projectIdList.size() == 0) {
  1535. LambdaQueryWrapper<Project> query1 = new LambdaQueryWrapper<>();
  1536. query1.eq(Project::getType, serviceTag);
  1537. return projectMapper.selectList(query1);
  1538. }
  1539. LambdaQueryWrapper<Project> query2 = new LambdaQueryWrapper<>();
  1540. query2.eq(Project::getType, serviceTag);
  1541. query2.notIn(Project::getId, projectIdList);
  1542. return projectMapper.selectList(query2);
  1543. }
  1544. /**
  1545. * 申请开通新服务
  1546. *
  1547. * @param dto
  1548. * @return
  1549. */
  1550. @Transactional(rollbackFor = Exception.class)
  1551. public int applyForService(MaProjectSaveDto dto) {
  1552. if (Objects.isNull(dto)) {
  1553. return 0;
  1554. }
  1555. if (dto.getProjectIdList().size() > 0) {
  1556. // 插入商户技能
  1557. extracted(dto);
  1558. } else {
  1559. return 0;
  1560. }
  1561. return 1;
  1562. }
  1563. /**
  1564. * 根据微信openid查询商户信息和入驻资料。
  1565. *
  1566. * @param openid 微信openid
  1567. * @return MerchantAuditFile
  1568. */
  1569. @Override
  1570. public MerchantAuditFile getTechnicianInfo(String openid) {
  1571. if (StringUtils.isBlank(openid)) {
  1572. throw new IllegalArgumentException("openid不能为空");
  1573. }
  1574. MaTechnician merchant = findMerchantByOpenid(openid);
  1575. return buildMerchantAuditFile(merchant);
  1576. }
  1577. /**
  1578. * 商户入驻信息
  1579. *
  1580. * @param userId 商户ID
  1581. * @return MerchantAuditFile
  1582. */
  1583. @Override
  1584. public MerchantAuditFile getTechnicianList(Integer userId) {
  1585. if (userId == null) {
  1586. throw new IllegalArgumentException("商户ID不能为空");
  1587. }
  1588. return buildMerchantAuditFile(findMerchantById(userId));
  1589. }
  1590. /**
  1591. * 根据微信openid查询商户信息
  1592. *
  1593. * @param openid
  1594. * @return MaTechnician
  1595. */
  1596. private MaTechnician findMerchantByOpenid(String openid) {
  1597. LambdaQueryWrapper<MaTechnician> query = new LambdaQueryWrapper<>();
  1598. query.eq(MaTechnician::getCOpenid, openid);
  1599. MaTechnician merchant = maTechnicianMapper.selectOne(query);
  1600. // 根据商户ID查询city_operation_application表
  1601. if (merchant != null && merchant.getId() != null) {
  1602. LambdaQueryWrapper<CityOperationApplication> query1 = new LambdaQueryWrapper<>();
  1603. query1.eq(CityOperationApplication::getMerchantId, merchant.getId()).last("limit 1");
  1604. CityOperationApplication application = cityOperationApplicationMapper.selectOne(query1);
  1605. if (application != null) {
  1606. merchant.setProvinceCode(application.getProvinceCode());
  1607. merchant.setProvinceName(application.getProvinceName());
  1608. merchant.setCityCode(application.getCityCode());
  1609. merchant.setCityName(application.getCityName());
  1610. merchant.setOperationCenterId(application.getOperationCenterId());
  1611. merchant.setOperationCenterName(application.getOperationCenterName());
  1612. }
  1613. }
  1614. return merchant;
  1615. }
  1616. private MaTechnician findMerchantById(Integer userId) {
  1617. LambdaQueryWrapper<MaTechnician> query = new LambdaQueryWrapper<>();
  1618. query.eq(MaTechnician::getId, userId);
  1619. return maTechnicianMapper.selectOne(query);
  1620. }
  1621. private MerchantAuditFile buildMerchantAuditFile(MaTechnician merchant) {
  1622. MerchantAuditFile merchantAuditFile = new MerchantAuditFile();
  1623. merchantAuditFile.setMerchant(merchant);
  1624. if (merchant != null && merchant.getId() != null) {
  1625. merchantAuditFile.setMerchantAuditFile(listMerchantApplyFiles(merchant.getId()));
  1626. } else {
  1627. merchantAuditFile.setMerchantAuditFile(Collections.emptyList());
  1628. }
  1629. return merchantAuditFile;
  1630. }
  1631. private List<MerchantApplyFile> listMerchantApplyFiles(Integer userId) {
  1632. LambdaQueryWrapper<MerchantApplyFile> query1 = new LambdaQueryWrapper<>();
  1633. query1.eq(MerchantApplyFile::getMerchantId, userId);
  1634. return merchantApplyFileMapper.selectList(query1);
  1635. }
  1636. /**
  1637. * 查询商户合同记录信息
  1638. *
  1639. * @param userId
  1640. * @return ContractRecordVO
  1641. */
  1642. @Override
  1643. public ContractRecordVO getContractRecords(Long userId) {
  1644. LambdaQueryWrapper<ContractRecord> query = new LambdaQueryWrapper<>();
  1645. query.eq(ContractRecord::getMerchantId, userId);
  1646. List<ContractRecord> contractRecordList = contractRecordMapper.selectList(query);
  1647. ContractRecordVO result = new ContractRecordVO();
  1648. if (CollectionUtils.isEmpty(contractRecordList)) {
  1649. result.setMerchantId(userId == null ? null : userId.intValue());
  1650. result.setFile(Collections.emptyList());
  1651. return result;
  1652. }
  1653. Set<String> seen = new HashSet<>();
  1654. contractRecordList = contractRecordList.stream()
  1655. .filter(record -> record.getContractName() != null && seen.add(record.getContractName()))
  1656. .collect(Collectors.toList());
  1657. ContractRecord firstRecord = contractRecordList.get(0);
  1658. result.setSignTime(firstRecord.getSignTime());
  1659. result.setSignerName(firstRecord.getSignerName());
  1660. result.setMerchantId(firstRecord.getMerchantId());
  1661. result.setFile(contractRecordList.stream()
  1662. .map(this::buildContractFileVO)
  1663. .collect(Collectors.toList()));
  1664. return result;
  1665. }
  1666. /**
  1667. * 构建合同文件VO
  1668. * @param record
  1669. * @return ContractFileVO.Contract
  1670. */
  1671. private ContractRecordVO.ContractFileVO buildContractFileVO(ContractRecord record) {
  1672. ContractRecordVO.ContractFileVO fileVO = new ContractRecordVO.ContractFileVO();
  1673. fileVO.setId(record.getId());
  1674. fileVO.setContractName(record.getContractName());
  1675. // 获取当前项目的访问路径
  1676. String contextPath = ServletUtils.getProjectAccessPath();
  1677. String fileUrl = contextPath + record.getFileUrl();
  1678. fileVO.setFileUrl(fileUrl);
  1679. return fileVO;
  1680. }
  1681. @Override
  1682. public Page<MerchantListVO> getMerchantPage(MerchantListDTO dto) {
  1683. // 1. 执行分页查询 (不带免车费过滤)
  1684. Page<MerchantListVO> page = new Page<>(dto.getCurrent(), dto.getSize());
  1685. page = this.baseMapper.getMerchantPage(page, dto);
  1686. List<MerchantListVO> records = page.getRecords();
  1687. if (CollUtil.isEmpty(records)) {
  1688. return page;
  1689. }
  1690. // 2. 如果用户勾选了“免车费”,则在内存中进行精准过滤
  1691. if (Boolean.TRUE.equals(dto.getFreeCarFee())) {
  1692. boolean isDay = this.maProjectFareSettingService.isDayTimePeriod(LocalDateTime.now());
  1693. // 3. 过滤列表
  1694. Iterator<MerchantListVO> iterator = records.iterator();
  1695. while (iterator.hasNext()) {
  1696. MerchantListVO vo = iterator.next();
  1697. double currentDistance = vo.getDistance(); // 数据库算出的距离(km)
  1698. // 获取该商户的有效免费里程
  1699. BigDecimal freeKm = this.maProjectFareSettingService.getMerchantFreeKm(Long.parseLong(vo.getId()), vo.getProjectId(), isDay);
  1700. // 核心判断:如果没配置(为0) 或者 距离超过了免费里程,则剔除
  1701. if (freeKm == null || freeKm.doubleValue() <= 0 || currentDistance > freeKm.doubleValue()) {
  1702. iterator.remove();
  1703. }
  1704. }
  1705. }
  1706. return page;
  1707. }
  1708. @Override
  1709. public Page<ProjectInfoVO> getByMerchantProject(MerchantProjectDTO dto) {
  1710. Page<ProjectInfoVO> page = new Page<>(dto.getCurrent(), dto.getSize());
  1711. return this.baseMapper.getByMerchantProject(page, dto);
  1712. }
  1713. @Override
  1714. public MerchantDetailVO getDetail(MerchantDetailDTO dto) {
  1715. // 1. 获取商户信息
  1716. Long merchantId = dto.getMerchantId();
  1717. MerchantDetailVO detail = this.baseMapper.getDetail(dto);
  1718. if (ObjectUtil.isNull(detail)) {
  1719. throw new ServiceException("商户不存在");
  1720. }
  1721. // 2. 获取商户的默认地址
  1722. TAddress address = this.addressService.getOne(new LambdaQueryWrapper<TAddress>()
  1723. .eq(TAddress::getMerchantId, merchantId)
  1724. .eq(TAddress::getIsDefault, 1)
  1725. .eq(TAddress::getIsDelete, NOT_DELETED));
  1726. if (ObjectUtil.isNull(address)) {
  1727. throw new ServiceException("无法获取商户的默认地址");
  1728. }
  1729. // 3. 计算当前用户距离商户距离
  1730. BigDecimal distanceStr = DistanceUtil.formatDistanceInKilometers(
  1731. dto.getLatitude(), dto.getLongitude(),
  1732. address.getLatitude(), address.getLongitude()
  1733. );
  1734. detail.setDistance(distanceStr);
  1735. // 4. 获取商户是否被当前用户收藏
  1736. boolean collected = this.collectService.isCollected(merchantId);
  1737. detail.setCollected(collected);
  1738. return detail;
  1739. }
  1740. /**
  1741. * 申请开通新服务
  1742. *
  1743. * @param dto
  1744. */
  1745. private void extracted(MaProjectSaveDto dto) {
  1746. LambdaQueryWrapper<Project> query = new LambdaQueryWrapper<>();
  1747. query.in(Project::getId, dto.getProjectIdList());
  1748. List<Project> projectList = projectMapper.selectList(query);
  1749. for (Project project : projectList) {
  1750. MaProject maProject = new MaProject();
  1751. maProject.setProjectId(project.getId());
  1752. maProject.setProjectName(project.getTitle());
  1753. maProject.setProjectDescribe(project.getDetail());
  1754. maProject.setProjectDuration(project.getStandardDuration());
  1755. maProject.setProjectOriginalPrice(project.getPrice());
  1756. maProject.setProjectMaxPrice(project.getPriceMax());
  1757. maProject.setProjectLowestPrice(project.getPriceMin());
  1758. maProject.setMerchantId(dto.getMerchantId());
  1759. maProject.setApplyTime(DateUtils.getNowDate());
  1760. maProject.setMerchantPhone(dto.getMerchantPhone());
  1761. maProject.setCreateBy(dto.getMerchantId().longValue());
  1762. maProject.setCreateTime(DateUtils.getNowDate());
  1763. maProjectMapper.insert(maProject);
  1764. }
  1765. }
  1766. /**
  1767. * 状态切换
  1768. *
  1769. * @param userId
  1770. * @param forceConfirm
  1771. * @return Result
  1772. */
  1773. @Override
  1774. @Transactional(rollbackFor = Exception.class)
  1775. public Result switchToOffline(Long userId, Boolean forceConfirm) {
  1776. if (userId == null) {
  1777. throw new ServiceException("商户ID不能为空");
  1778. }
  1779. MaTechnician technician = getTechnician(userId);
  1780. if (technician == null) {
  1781. throw new ServiceException("商户不存在");
  1782. }
  1783. if (!hasActiveSkills(userId)) {
  1784. throw new ServiceException("请先申请开通技能");
  1785. }
  1786. if (!hasHomeAddress(userId)) {
  1787. throw new ServiceException("请完善家庭地址");
  1788. }
  1789. // 由在线接单切换为休息状态
  1790. if (TechnicianStatusEnum.ONLINE.getCode().equals(technician.getPostState())) {
  1791. return switchOnlineToResting(userId, technician, Boolean.TRUE.equals(forceConfirm));
  1792. }
  1793. // 切换到在线接单状态
  1794. switchRestingToOnline(userId, technician);
  1795. return Result.ok("状态已切换成功");
  1796. }
  1797. /**
  1798. * 状态切换:下线休息
  1799. *
  1800. * @param userId
  1801. * @param technician
  1802. * @param forceConfirm
  1803. * @return Result
  1804. */
  1805. private Result switchOnlineToResting(Long userId, MaTechnician technician, boolean forceConfirm) {
  1806. if (JsStatusEnum.JS_SERVICE.getCode().equals(technician.getServiceState())) {
  1807. throw new ServiceException("您有服务中的订单,不能下岗");
  1808. }
  1809. MerchantDailyAttendance currentAttendance = getTodayAttendance(userId);
  1810. if (ProjectCategoryEnum.MASSAGE.getCode().equals(technician.getServiceTag())) {
  1811. AttendanceRule rule = getAttendanceRule();
  1812. if (rule != null && rule.getBasicWorkHours() != null) {
  1813. long minutesOnline = calculateTodayOnlineMinutes(userId, currentAttendance);
  1814. long requiredMinutes = rule.getBasicWorkHours().multiply(BigDecimal.valueOf(60)).longValue();
  1815. if (minutesOnline < requiredMinutes && !forceConfirm) {
  1816. return Result.ok(buildOfflineConfirmMessage(requiredMinutes, minutesOnline));
  1817. }
  1818. }
  1819. }
  1820. updateStatus(userId, TechnicianStatusEnum.RESTING);
  1821. closeTodayAttendance(currentAttendance);
  1822. return Result.ok("状态已切换成功");
  1823. }
  1824. private void switchRestingToOnline(Long userId, MaTechnician technician) {
  1825. updateStatus(userId, TechnicianStatusEnum.ONLINE);
  1826. MerchantDailyAttendance merchantDailyAttendance = new MerchantDailyAttendance()
  1827. .setMerchantId(userId.intValue())
  1828. .setAttendanceDate(DateUtils.getNowDate())
  1829. .setMerchantName(technician.getTeName())
  1830. .setAttendanceStartTime(DateUtils.getNowDate())
  1831. .setCreateBy(technician.getTeName())
  1832. .setCreateTime(LocalDateTime.now());
  1833. merchantDailyAttendanceMapper.insert(merchantDailyAttendance);
  1834. }
  1835. /**
  1836. * 计算商户今天在线时间(分钟)
  1837. *
  1838. * @param userId
  1839. * @param currentAttendance
  1840. * @return long
  1841. */
  1842. private long calculateTodayOnlineMinutes(Long userId, MerchantDailyAttendance currentAttendance) {
  1843. long totalMinutes = getWorkDuration(userId, currentAttendance == null ? null : currentAttendance.getId());
  1844. if (currentAttendance == null || currentAttendance.getAttendanceStartTime() == null) {
  1845. return totalMinutes;
  1846. }
  1847. LocalDateTime startTime = toLocalDateTime(currentAttendance.getAttendanceStartTime());
  1848. return totalMinutes + Math.max(0L, Duration.between(startTime, LocalDateTime.now()).toMinutes());
  1849. }
  1850. /**
  1851. * 构建下线确认消息
  1852. *
  1853. * @param requiredMinutes
  1854. * @param minutesOnline
  1855. * @return String
  1856. */
  1857. private String buildOfflineConfirmMessage(long requiredMinutes, long minutesOnline) {
  1858. long remainMinutes = Math.max(0L, requiredMinutes - minutesOnline);
  1859. long remainHours = remainMinutes / 60;
  1860. long remainMinutePart = remainMinutes % 60;
  1861. String remainText = remainMinutePart == 0
  1862. ? remainHours + "小时"
  1863. : remainHours + "小时" + remainMinutePart + "分钟";
  1864. return "平台对您的在线时间做了约定,每日在线需满足"
  1865. + (requiredMinutes / 60) + "小时,距离您下线时间还剩余" + remainText
  1866. + "不满足在线时间将收到平台处罚,是否确认下线?";
  1867. }
  1868. /**
  1869. * 关闭今天商户的考勤记录
  1870. *
  1871. * @param attendance
  1872. */
  1873. private void closeTodayAttendance(MerchantDailyAttendance attendance) {
  1874. if (attendance == null || attendance.getId() == null || attendance.getAttendanceStartTime() == null) {
  1875. return;
  1876. }
  1877. LocalDateTime startTime = toLocalDateTime(attendance.getAttendanceStartTime());
  1878. long workMinutes = Math.max(0L, Duration.between(startTime, LocalDateTime.now()).toMinutes());
  1879. LambdaUpdateWrapper<MerchantDailyAttendance> updateWrapper = new LambdaUpdateWrapper<>();
  1880. updateWrapper.eq(MerchantDailyAttendance::getId, attendance.getId())
  1881. .set(MerchantDailyAttendance::getAttendanceEndTime, DateUtils.getNowDate())
  1882. .set(MerchantDailyAttendance::getTotalWorkMinutes, Math.toIntExact(workMinutes))
  1883. .set(MerchantDailyAttendance::getUpdateTime, DateUtils.getNowDate());
  1884. merchantDailyAttendanceMapper.update(attendance, updateWrapper);
  1885. }
  1886. private LocalDateTime toLocalDateTime(Date date) {
  1887. return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
  1888. }
  1889. /**
  1890. * 获取今天商户的考勤记录
  1891. *
  1892. * @param userId 技师ID
  1893. * @return MerchantDailyAttendance 考勤记录
  1894. */
  1895. private MerchantDailyAttendance getTodayAttendance(Long userId) {
  1896. LambdaQueryWrapper<MerchantDailyAttendance> wrapper = new LambdaQueryWrapper<>();
  1897. wrapper.eq(MerchantDailyAttendance::getMerchantId, userId)
  1898. .eq(MerchantDailyAttendance::getAttendanceDate, DateUtils.getNowDate())
  1899. .orderByDesc(MerchantDailyAttendance::getCreateTime)
  1900. .last("LIMIT 1");
  1901. return merchantDailyAttendanceMapper.selectOne(wrapper);
  1902. }
  1903. /**
  1904. * 获取商户的累计工作时长
  1905. *
  1906. * @param userId 技师ID
  1907. * @param excludeAttendanceId 排除的考勤记录ID
  1908. * @return 工作时长(分钟)
  1909. */
  1910. private long getWorkDuration(Long userId, Long excludeAttendanceId) {
  1911. LambdaQueryWrapper<MerchantDailyAttendance> wrapper = new LambdaQueryWrapper<>();
  1912. wrapper.eq(MerchantDailyAttendance::getMerchantId, userId)
  1913. .eq(MerchantDailyAttendance::getAttendanceDate, DateUtils.getNowDate());
  1914. List<MerchantDailyAttendance> attendances = merchantDailyAttendanceMapper.selectList(wrapper);
  1915. if (attendances == null || attendances.isEmpty()) return 0;
  1916. return attendances.stream()
  1917. .filter(attendance -> excludeAttendanceId == null || !excludeAttendanceId.equals(attendance.getId()))
  1918. .filter(attendance -> attendance.getTotalWorkMinutes() != null)
  1919. .mapToLong(MerchantDailyAttendance::getTotalWorkMinutes)
  1920. .sum();
  1921. }
  1922. /**
  1923. * 获取商户的考勤规则
  1924. *
  1925. * @return 考勤规则
  1926. */
  1927. private AttendanceRule getAttendanceRule() {
  1928. LambdaQueryWrapper<AttendanceRule> wrapper = new LambdaQueryWrapper<>();
  1929. wrapper.eq(AttendanceRule::getIsDelete, 0);
  1930. wrapper.eq(AttendanceRule::getWorkDurationRuleEnabled, 1);
  1931. wrapper.last("LIMIT 1");
  1932. return attendanceRuleMapper.selectOne(wrapper);
  1933. }
  1934. /**
  1935. * 判断商户是否有开通的技能
  1936. *
  1937. * @param userId 商户ID
  1938. * @return boolean true: 有可用技能, false: 无
  1939. */
  1940. public boolean hasActiveSkills(Long userId) {
  1941. LambdaQueryWrapper<MaProject> wrapper = new LambdaQueryWrapper<>();
  1942. wrapper.eq(MaProject::getMerchantId, userId).eq(MaProject::getAuditStatus, 1);
  1943. // 只要查到一条记录即返回 true
  1944. return maProjectMapper.selectCount(wrapper) > 0;
  1945. }
  1946. /**
  1947. * 判断用户是否有家庭地址(通常指设置为默认的地址)
  1948. *
  1949. * @param userId 技师ID
  1950. * @return boolean true: 有地址, false: 无
  1951. */
  1952. public boolean hasHomeAddress(Long userId) {
  1953. LambdaQueryWrapper<TAddress> wrapper = new LambdaQueryWrapper<>();
  1954. wrapper.eq(TAddress::getMerchantId, userId).eq(TAddress::getUserType, 2);
  1955. // 统计数量是否大于0
  1956. long count = addressMapper.selectCount(wrapper);
  1957. return count > 0;
  1958. }
  1959. /**
  1960. * 获取商户信息
  1961. *
  1962. * @param userId 商户ID
  1963. * @return MaTechnician 商户信息
  1964. */
  1965. private MaTechnician getTechnician(Long userId) {
  1966. LambdaQueryWrapper<MaTechnician> query = new LambdaQueryWrapper<>();
  1967. query.eq(MaTechnician::getId, userId);
  1968. return maTechnicianMapper.selectOne(query);
  1969. }
  1970. /**
  1971. * 更新商户接单状态
  1972. *
  1973. * @param userId 商户ID
  1974. * @param status 商户接单状态枚举
  1975. */
  1976. private void updateStatus(Long userId, TechnicianStatusEnum status) {
  1977. LambdaUpdateWrapper<MaTechnician> update = new LambdaUpdateWrapper<>();
  1978. update.eq(MaTechnician::getId, userId);
  1979. update.set(MaTechnician::getPostState, status.getCode());
  1980. maTechnicianMapper.update(null, update);
  1981. }
  1982. /**
  1983. * 后台待审核页面审核通过商户。
  1984. *
  1985. * @param id 商户ID
  1986. * @param dto 待审核通过参数
  1987. * @param loginUser 当前登录用户
  1988. * @return 结果
  1989. */
  1990. @Override
  1991. @Transactional(rollbackFor = Exception.class)
  1992. public int approvePendingMerchantAudit(Integer id, MaTechnicianPendingAuditSubmitDTO dto, LoginUser loginUser) {
  1993. if (id == null) {
  1994. throw new ServiceException("商户ID不能为空");
  1995. }
  1996. if (dto == null) {
  1997. throw new ServiceException("审核参数不能为空");
  1998. }
  1999. String idCardFront = checkRequiredFileUrl(dto.getIdCardFront(), "身份证正面加密图片");
  2000. String idCardBack = checkRequiredFileUrl(dto.getIdCardBack(), "身份证反面加密图片");
  2001. String healthCertificate = checkRequiredFileUrl(dto.getHealthCertificate(), "健康证加密图片");
  2002. String qualificationCertificate = checkRequiredFileUrl(dto.getQualificationCertificate(), "资格证加密图片");
  2003. checkRequiredExpirationDate(dto.getIdCardExpirationDate(), "身份证到期时间");
  2004. checkRequiredExpirationDate(dto.getHealthCertificateExpirationDate(), "健康证到期时间");
  2005. checkRequiredExpirationDate(dto.getQualificationCertificateExpirationDate(), "资格证到期时间");
  2006. String auditRemark = dto.getAuditRemark() == null ? "" : dto.getAuditRemark().trim();
  2007. if (auditRemark.length() > AUDIT_REMARK_MAX_LENGTH) {
  2008. throw new ServiceException("审核备注长度不能超过" + AUDIT_REMARK_MAX_LENGTH + "个字符");
  2009. }
  2010. MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id);
  2011. if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
  2012. throw new ServiceException("商户不存在或已删除");
  2013. }
  2014. if (!Integer.valueOf(AUDIT_WAIT_REVIEW).equals(existsMerchant.getAuditStatus())) {
  2015. throw new ServiceException("当前商户不是待审核状态,不能审核通过");
  2016. }
  2017. MaTechnician maTechnician = new MaTechnician();
  2018. maTechnician.setId(id);
  2019. /*maTechnician.setIdCard(String.join(",", idCardFront, idCardBack));
  2020. maTechnician.setHealthCertificate(healthCertificate);
  2021. maTechnician.setQualificationCertificate(qualificationCertificate);*/
  2022. maTechnician.setIdCardExpirationDate(dto.getIdCardExpirationDate());
  2023. maTechnician.setHealthCertificateExpirationDate(dto.getHealthCertificateExpirationDate());
  2024. maTechnician.setQualificationCertificateExpirationDate(dto.getQualificationCertificateExpirationDate());
  2025. maTechnician.setAuditStatus(AUDIT_APPROVED);
  2026. maTechnician.setAuditRemark(auditRemark);
  2027. maTechnician.setApproveTime(DateUtils.getNowDate());
  2028. if (loginUser != null && loginUser.getUser() != null) {
  2029. maTechnician.setUpdateBy(loginUser.getUser().getUserName());
  2030. }
  2031. maTechnician.setUpdateTime(DateUtils.getNowDate());
  2032. int rows = maTechnicianMapper.approvePendingMerchantAuditById(maTechnician);
  2033. if (rows <= 0) {
  2034. throw new ServiceException("待审核商户审核通过失败");
  2035. }
  2036. return rows;
  2037. }
  2038. private String checkRequiredFileUrl(String value, String fieldName) {
  2039. if (StringUtils.isBlank(value)) {
  2040. throw new ServiceException(fieldName + "不能为空");
  2041. }
  2042. return value.trim();
  2043. }
  2044. private void checkRequiredExpirationDate(LocalDate value, String fieldName) {
  2045. if (value == null) {
  2046. throw new ServiceException(fieldName + "不能为空");
  2047. }
  2048. if (value.isBefore(LocalDate.now())) {
  2049. throw new ServiceException(fieldName + "不能早于当前日期");
  2050. }
  2051. }
  2052. /**
  2053. * 技师待处理订单列表
  2054. *
  2055. * @param query
  2056. * @return
  2057. */
  2058. @Override
  2059. public List<WaitOrderDTO> listWaitOrder(WaitOrderQueryDTO query) {
  2060. LambdaQueryWrapper<TOrder> queryWrapper = new LambdaQueryWrapper<>();
  2061. queryWrapper.eq(TOrder::getStatus, OrderStatusEnum.WAIT_JD.getCode());
  2062. // 1.查询所有待派未接单订单(status=待接单)
  2063. List<TOrder> allWaitOrder = orderMapper.selectList(queryWrapper);
  2064. if (CollectionUtils.isEmpty(allWaitOrder)) {
  2065. return Collections.emptyList();
  2066. }
  2067. BigDecimal techLat = query.getTechLat();
  2068. BigDecimal techLng = query.getTechLng();
  2069. // 2.逐个计算两点距离(Haversine公式)
  2070. List<WaitOrderDTO> dtoList = allWaitOrder.stream().map(order -> {
  2071. WaitOrderDTO dto = new WaitOrderDTO();
  2072. dto.setOrderId(order.getId());
  2073. dto.setProjectName(getShortProjectName(order.getProjectName()));
  2074. dto.setCustomerType("新客户");//无历史绑定默认新客,接单后再统计老客
  2075. dto.setOrderCreateTime(order.getCreateTime());
  2076. dto.setAppointTime(order.getAppointmentStartTime());
  2077. dto.setTargetAddress(order.getContactAddressInfo());
  2078. dto.setOrderLat(order.getUserLatitude());
  2079. dto.setOrderLng(order.getUserLongitude());
  2080. // 计算两点距离 单位:米
  2081. BigDecimal disMeter = calcDistance(techLat, techLng, order.getUserLatitude(), order.getUserLongitude());
  2082. dto.setDistanceMeter(disMeter);
  2083. dto.setDistanceDesc(formatDistance(disMeter));
  2084. return dto;
  2085. }).collect(Collectors.toList());
  2086. // 3.距离升序:近的排在最前面
  2087. return dtoList.stream()
  2088. .sorted(Comparator.comparing(WaitOrderDTO::getDistanceMeter))
  2089. .collect(Collectors.toList());
  2090. }
  2091. /**
  2092. * 接单
  2093. *
  2094. * @param req
  2095. * @return
  2096. */
  2097. @Override
  2098. public String acceptOrder(AcceptOrderReqDTO req) {
  2099. Long techId = req.getTechId();
  2100. Long orderId = req.getOrderId();
  2101. //【校验1:订单是否已被其他技师接单】
  2102. TOrder order = orderMapper.selectById(orderId);
  2103. if (OrderStatusEnum.RECEIVED_ORDER.getCode().equals(order.getStatus())) {
  2104. return OrderTipEnum.REPEAT_ORDER.getTip();
  2105. }
  2106. //【校验2:时间冲突校验(该技师已有已接单订单)】
  2107. boolean isTimeConflict = checkOrderTimeConflict(techId, order);
  2108. if (isTimeConflict) {
  2109. String tip = String.format(OrderTipEnum.TIME_CONFLICT.getTip(),
  2110. order.getStartTime().format(DateTimeFormatter.ofPattern("MM月dd日HH:mm")),
  2111. order.getCompletedTime().format(DateTimeFormatter.ofPattern("MM月dd日HH:mm")));
  2112. return tip;
  2113. }
  2114. //【校验3:技师休息状态】
  2115. MaTechnician tech = maTechnicianMapper.selectById(techId);
  2116. if (TechnicianStatusEnum.RESTING.getCode().equals(tech.getPostState())) {
  2117. return OrderTipEnum.REST_CONFIRM.getTip();
  2118. }
  2119. // 正常接单,绑定技师ID到订单
  2120. doAcceptOrder(techId, orderId);
  2121. return OrderTipEnum.ALREADY_ACCEPT.getTip();
  2122. }
  2123. /**
  2124. * 技师接单确认接单
  2125. *
  2126. * @param techId
  2127. * @param orderId
  2128. * @return
  2129. */
  2130. @Override
  2131. public String confirmRestAccept(Long techId, Long orderId) {
  2132. LambdaUpdateWrapper<MaTechnician> update = new LambdaUpdateWrapper<>();
  2133. update.eq(MaTechnician::getId, techId);
  2134. update.set(MaTechnician::getPostState, TechnicianStatusEnum.ONLINE.getCode());
  2135. maTechnicianMapper.update(null, update);
  2136. doAcceptOrder(techId, orderId);
  2137. return OrderTipEnum.ALREADY_ACCEPT.getTip();
  2138. }
  2139. /**
  2140. * 技师拒绝接单
  2141. *
  2142. * @param req
  2143. * @return
  2144. */
  2145. @Override
  2146. public void refuseOrder(RefuseOrderReqDTO req) {
  2147. LambdaUpdateWrapper<TOrder> update = new LambdaUpdateWrapper<>();
  2148. update.eq(TOrder::getId, req.getOrderId());
  2149. update.set(TOrder::getStatus, OrderStatusEnum.REFUSE.getCode());
  2150. update.set(TOrder::getRejectedReason, req.getRefuseReason());
  2151. orderMapper.update(null, update);
  2152. //拒单后订单重回待接单池,其他技师可刷到
  2153. LambdaUpdateWrapper<TOrder> update2 = new LambdaUpdateWrapper<>();
  2154. update2.eq(TOrder::getId, req.getOrderId());
  2155. update2.set(TOrder::getStatus, OrderStatusEnum.WAIT_JD.getCode());
  2156. update2.set(TOrder::getMerchantId, "");
  2157. update2.set(TOrder::getRejectedReason, "");
  2158. orderMapper.update(null, update2);
  2159. }
  2160. // =================工具方法=================
  2161. /**
  2162. * Haversine 计算经纬度距离 返回米
  2163. */
  2164. private BigDecimal calcDistance(BigDecimal lat1, BigDecimal lng1, BigDecimal lat2, BigDecimal lng2) {
  2165. // 球面距离计算公式,地球半径6371000米
  2166. // 可使用BigDecimal三角函数或数据库函数优化
  2167. double latRad1 = Math.toRadians(lat1.doubleValue());
  2168. double latRad2 = Math.toRadians(lat2.doubleValue());
  2169. double lngRad1 = Math.toRadians(lng1.doubleValue());
  2170. double lngRad2 = Math.toRadians(lng2.doubleValue());
  2171. double dLat = latRad2 - latRad1;
  2172. double dLng = lngRad2 - lngRad1;
  2173. double a = Math.pow(Math.sin(dLat / 2), 2)
  2174. + Math.cos(latRad1) * Math.cos(latRad2)
  2175. * Math.pow(Math.sin(dLng / 2), 2);
  2176. double dis = 2 * 6371000 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  2177. return BigDecimal.valueOf(dis).setScale(2, BigDecimal.ROUND_HALF_UP);
  2178. }
  2179. private String getShortProjectName(String name) {
  2180. if (name != null && name.length() > 10) {
  2181. return name.substring(0, 8) + "...";
  2182. }
  2183. return name;
  2184. }
  2185. private String formatDistance(BigDecimal distanceMeter) {
  2186. BigDecimal km = distanceMeter.divide(new BigDecimal(1000), 2, BigDecimal.ROUND_HALF_UP);
  2187. if (km.compareTo(BigDecimal.ONE) > 0) {
  2188. return km + "km";
  2189. } else {
  2190. return distanceMeter.intValue() + "m";
  2191. }
  2192. }
  2193. /**
  2194. * 校验技师已有订单时间冲突(只查该技师已接单数据)
  2195. */
  2196. private boolean checkOrderTimeConflict(Long techId, TOrder newOrder) {
  2197. LambdaQueryWrapper<TOrder> query = new LambdaQueryWrapper<>();
  2198. query.eq(TOrder::getMerchantId, techId);
  2199. query.eq(TOrder::getStatus, OrderStatusEnum.RECEIVED_ORDER.getCode());
  2200. query.eq(TOrder::getStartTime, newOrder.getStartTime());
  2201. List<TOrder> acceptedOrders = orderMapper.selectList(query);
  2202. LocalDate newOrderDate = newOrder.getStartTime().toLocalDate();
  2203. LocalDateTime newStart = newOrder.getStartTime();
  2204. for (TOrder old : acceptedOrders) {
  2205. if (!newOrderDate.isEqual(old.getCompletedTime().toLocalDate())) {
  2206. continue;
  2207. }
  2208. LocalDateTime oldEnd = old.getCompletedTime();
  2209. if (newStart.isBefore(oldEnd) || newStart.isEqual(oldEnd)) {
  2210. return true;
  2211. }
  2212. }
  2213. return false;
  2214. }
  2215. /**
  2216. * 接单:给订单赋值技师ID
  2217. */
  2218. private void doAcceptOrder(Long techId, Long orderId) {
  2219. TOrder update = new TOrder();
  2220. update.setId(orderId);
  2221. update.setMerchantId(techId);
  2222. update.setStatus(OrderStatusEnum.RECEIVED_ORDER.getCode());
  2223. orderMapper.updateById(update);
  2224. }
  2225. }