MaTechnicianServiceImpl.java 101 KB

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