AppletHomeServiceImpl.java 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. package com.zsElectric.boot.business.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  3. import com.baomidou.mybatisplus.core.metadata.IPage;
  4. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  5. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  6. import com.zsElectric.boot.business.converter.BannerInfoConverter;
  7. import com.zsElectric.boot.business.mapper.*;
  8. import com.zsElectric.boot.business.model.entity.*;
  9. import com.zsElectric.boot.business.model.query.StationInfoQuery;
  10. import com.zsElectric.boot.business.model.vo.*;
  11. import com.zsElectric.boot.business.model.vo.applet.AppChargingCostVO;
  12. import com.zsElectric.boot.business.model.vo.applet.AppStationSearchVO;
  13. import com.zsElectric.boot.business.model.vo.applet.AppletConnectorDetailVO;
  14. import com.zsElectric.boot.business.service.AppletHomeService;
  15. import com.zsElectric.boot.charging.entity.*;
  16. import com.zsElectric.boot.charging.mapper.ThirdPartyChargeStatusMapper;
  17. import com.zsElectric.boot.charging.mapper.ThirdPartyConnectorInfoMapper;
  18. import com.zsElectric.boot.charging.mapper.ThirdPartyEquipmentPricePolicyMapper;
  19. import com.zsElectric.boot.charging.mapper.ThirdPartyPolicyInfoMapper;
  20. import com.zsElectric.boot.security.util.SecurityUtils;
  21. import com.zsElectric.boot.system.mapper.DictItemMapper;
  22. import com.zsElectric.boot.system.model.entity.DictItem;
  23. import lombok.RequiredArgsConstructor;
  24. import lombok.extern.slf4j.Slf4j;
  25. import org.springframework.stereotype.Service;
  26. import java.math.BigDecimal;
  27. import java.math.RoundingMode;
  28. import java.time.LocalTime;
  29. import java.time.format.DateTimeFormatter;
  30. import java.util.ArrayList;
  31. import java.util.HashMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.stream.Collectors;
  35. @Slf4j
  36. @Service
  37. @RequiredArgsConstructor
  38. public class AppletHomeServiceImpl implements AppletHomeService {
  39. private final ThirdPartyStationInfoMapper thirdPartyStationInfoMapper;
  40. private final UserFirmMapper userFirmMapper;
  41. private final UserAccountMapper userAccountMapper;
  42. private final BannerInfoMapper bannerInfoMapper;
  43. private final BannerInfoConverter bannerInfoConverter;
  44. private final ThirdPartyConnectorInfoMapper thirdPartyConnectorInfoMapper;
  45. private final ThirdPartyEquipmentInfoMapper thirdPartyEquipmentInfoMapper;
  46. private final ThirdPartyEquipmentPricePolicyMapper thirdPartyEquipmentPricePolicyMapper;
  47. private final ThirdPartyPolicyInfoMapper thirdPartyPolicyInfoMapper;
  48. private final PolicyFeeMapper policyFeeMapper;
  49. private final ThirdPartyChargeStatusMapper thirdPartyChargeStatusMapper;
  50. private final ChargeOrderInfoMapper chargeOrderInfoMapper;
  51. private final DiscountsActivityMapper discountsActivityMapper;
  52. private final DictItemMapper dictItemMapper;
  53. /**
  54. * 时间格式化器 HHmmss
  55. */
  56. private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HHmmss");
  57. /**
  58. * 默认运营费字典编码
  59. */
  60. private static final String DEFAULT_OP_FEE_DICT_CODE = "default_op_fee";
  61. @Override
  62. public IPage<StationInfoVO> getStationInfoPage(StationInfoQuery queryParams) {
  63. // 查询用户是否为企业用户,获取企业ID
  64. Long firmId = null;
  65. boolean isFirmUser = false;
  66. if (queryParams.getUserId() != null) {
  67. UserFirm userFirm = userFirmMapper.selectOne(
  68. new LambdaQueryWrapper<UserFirm>()
  69. .eq(UserFirm::getUserId, queryParams.getUserId())
  70. .last("LIMIT 1")
  71. );
  72. if (userFirm != null) {
  73. firmId = userFirm.getFirmId();
  74. isFirmUser = true;
  75. }
  76. }
  77. // 获取当前时间(HHmmss格式)
  78. String currentTime = LocalTime.now().format(TIME_FORMATTER);
  79. // 构建分页对象
  80. Page<StationInfoVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
  81. // 执行查询
  82. IPage<StationInfoVO> resultPage = thirdPartyStationInfoMapper.selectAppletStationInfoPage(
  83. page, queryParams, currentTime, firmId
  84. );
  85. // 设置是否企业用户标识
  86. // 只有当用户属于企业且企业有设置价格时,才标记为企业用户
  87. final boolean finalIsFirmUser = isFirmUser;
  88. resultPage.getRecords().forEach(vo -> {
  89. // 如果是企业用户但企业价为null,说明该企业未设置价格,firmUser应为false
  90. boolean hasFirmPrice = vo.getEnterprisePrice() != null;
  91. vo.setFirmUser(finalIsFirmUser && hasFirmPrice);
  92. });
  93. return resultPage;
  94. }
  95. @Override
  96. public List<StationInfoMapVO> getStationInfoMapList(BigDecimal longitude, BigDecimal latitude) {
  97. // 获取当前时间(HHmmss格式)
  98. String currentTime = LocalTime.now().format(TIME_FORMATTER);
  99. // 查询站点列表(按距离排序)
  100. return thirdPartyStationInfoMapper.selectStationMapList(
  101. longitude, latitude, currentTime
  102. );
  103. }
  104. @Override
  105. public List<BannerInfoVO> getBannerList(Integer location) {
  106. // 查询启用状态的Banner,按排序字段降序
  107. List<BannerInfo> bannerList = bannerInfoMapper.selectList(
  108. new LambdaQueryWrapper<BannerInfo>()
  109. .eq(BannerInfo::getStatus, 1)
  110. .eq(BannerInfo::getLocation, location)
  111. .orderByDesc(BannerInfo::getSort)
  112. );
  113. return bannerInfoConverter.toVO(bannerList);
  114. }
  115. @Override
  116. public AppletStationDetailVO getStationDetail(Long stationId, BigDecimal longitude, BigDecimal latitude) {
  117. // 获取当前登录用户ID
  118. Long userId = SecurityUtils.getUserId();
  119. // 查询用户是否为企业用户,获取企业ID
  120. Long firmId = null;
  121. boolean isFirmUser = false;
  122. if (userId != null) {
  123. UserFirm userFirm = userFirmMapper.selectOne(
  124. new LambdaQueryWrapper<UserFirm>()
  125. .eq(UserFirm::getUserId, userId)
  126. .last("LIMIT 1")
  127. );
  128. if (userFirm != null) {
  129. firmId = userFirm.getFirmId();
  130. isFirmUser = true;
  131. }
  132. }
  133. // 获取当前时间(HHmmss格式)
  134. String currentTime = LocalTime.now().format(TIME_FORMATTER);
  135. // 查询站点详情基本信息
  136. AppletStationDetailVO result = thirdPartyStationInfoMapper.selectStationDetail(
  137. stationId, longitude, latitude, currentTime, firmId
  138. );
  139. if (result == null) {
  140. return null;
  141. }
  142. // 设置是否企业用户
  143. result.setFirmUser(isFirmUser);
  144. // 计算预计省多少(当前价 - 原价,如果为负则取绝对值)
  145. if (result.getCurrentPrice() != null && result.getOriginalPrice() != null) {
  146. BigDecimal saving = result.getOriginalPrice().subtract(result.getCurrentPrice());
  147. result.setEstimatedSaving(saving.compareTo(BigDecimal.ZERO) > 0 ? saving : BigDecimal.ZERO);
  148. result.setShowSpecialPriceTag(saving.compareTo(BigDecimal.ZERO) > 0);
  149. }
  150. // 查询站点对应的stationId
  151. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectById(stationId);
  152. if (stationInfo == null) {
  153. return result;
  154. }
  155. // 根据站点stationId查询设备列表(third_party_equipment_info)
  156. List<ThirdPartyEquipmentInfo> equipmentList = thirdPartyEquipmentInfoMapper.selectList(
  157. new LambdaQueryWrapper<ThirdPartyEquipmentInfo>()
  158. .eq(ThirdPartyEquipmentInfo::getStationId, stationInfo.getStationId())
  159. );
  160. // 构建设备ID到设备类型的映射
  161. Map<String, Integer> equipmentTypeMap = equipmentList.stream()
  162. .collect(Collectors.toMap(
  163. ThirdPartyEquipmentInfo::getEquipmentId,
  164. e -> e.getEquipmentType() != null ? e.getEquipmentType() : 0,
  165. (v1, v2) -> v1
  166. ));
  167. // 根据设备列表的equipmentId查询充电接口信息(third_party_connector_info)
  168. List<ThirdPartyConnectorInfo> connectorList = new ArrayList<>();
  169. if (!equipmentList.isEmpty()) {
  170. List<String> equipmentIds = equipmentList.stream()
  171. .map(ThirdPartyEquipmentInfo::getEquipmentId)
  172. .collect(Collectors.toList());
  173. connectorList = thirdPartyConnectorInfoMapper.selectList(
  174. new LambdaQueryWrapper<ThirdPartyConnectorInfo>()
  175. .in(ThirdPartyConnectorInfo::getEquipmentId, equipmentIds)
  176. );
  177. }
  178. // 统计终端状态
  179. int idleCount = 0;
  180. int occupiedCount = 0;
  181. int offlineCount = 0;
  182. List<AppletStationDetailVO.ConnectorInfoVO> connectorVOList = new ArrayList<>();
  183. for (ThirdPartyConnectorInfo connector : connectorList) {
  184. AppletStationDetailVO.ConnectorInfoVO vo = new AppletStationDetailVO.ConnectorInfoVO();
  185. vo.setConnectorId(connector.getId());
  186. vo.setConnectorName(connector.getConnectorName());
  187. vo.setConnectorCode(connector.getConnectorId());
  188. // 设备分类:从设备表获取设备类型(1-直流设备,2-交流设备,3-交直流一体设备,4-无线设备,5-其他)
  189. Integer equipmentType = equipmentTypeMap.get(connector.getEquipmentId());
  190. if (equipmentType != null) {
  191. switch (equipmentType) {
  192. case 1:
  193. vo.setEquipmentType("直流设备");
  194. break;
  195. case 2:
  196. vo.setEquipmentType("交流设备");
  197. break;
  198. case 3:
  199. vo.setEquipmentType("交直流一体设备");
  200. break;
  201. case 4:
  202. vo.setEquipmentType("无线设备");
  203. break;
  204. case 5:
  205. vo.setEquipmentType("其他");
  206. break;
  207. default:
  208. vo.setEquipmentType("未知");
  209. }
  210. }
  211. // 从充电接口表获取实际状态
  212. // 0-离网,1-空闲,2-占用(未充电),3-占用(充电中),4-占用(预约锁定),255-故障
  213. Integer connectorStatus = connector.getStatus();
  214. if (connectorStatus == null) {
  215. // 默认为离网
  216. connectorStatus = 0;
  217. }
  218. vo.setStatus(connectorStatus);
  219. // 设置状态名称并统计
  220. switch (connectorStatus) {
  221. case 0:
  222. vo.setStatusName("离网");
  223. offlineCount++;
  224. break;
  225. case 1:
  226. vo.setStatusName("空闲");
  227. idleCount++;
  228. break;
  229. case 2:
  230. vo.setStatusName("占用(未充电)");
  231. occupiedCount++;
  232. break;
  233. case 3:
  234. vo.setStatusName("占用(充电中)");
  235. occupiedCount++;
  236. break;
  237. case 4:
  238. vo.setStatusName("占用(预约锁定)");
  239. occupiedCount++;
  240. break;
  241. case 255:
  242. vo.setStatusName("故障");
  243. offlineCount++;
  244. break;
  245. default:
  246. vo.setStatusName("未知");
  247. offlineCount++;
  248. }
  249. connectorVOList.add(vo);
  250. }
  251. result.setConnectorList(connectorVOList);
  252. result.setIdleCount(idleCount);
  253. result.setOccupiedCount(occupiedCount);
  254. result.setOfflineCount(offlineCount);
  255. return result;
  256. }
  257. @Override
  258. public AppletConnectorListVO getConnectorList(Long stationId) {
  259. // 查询站点信息
  260. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectById(stationId);
  261. if (stationInfo == null) {
  262. return null;
  263. }
  264. AppletConnectorListVO result = new AppletConnectorListVO();
  265. result.setStationId(stationId);
  266. result.setStationName(stationInfo.getStationName());
  267. result.setTips(stationInfo.getStationTips());
  268. // 根据站点stationId查询设备列表
  269. List<ThirdPartyEquipmentInfo> equipmentList = thirdPartyEquipmentInfoMapper.selectList(
  270. new LambdaQueryWrapper<ThirdPartyEquipmentInfo>()
  271. .eq(ThirdPartyEquipmentInfo::getStationId, stationInfo.getStationId())
  272. );
  273. // 构建设备ID到设备类型的映射
  274. Map<String, Integer> equipmentTypeMap = equipmentList.stream()
  275. .collect(Collectors.toMap(
  276. ThirdPartyEquipmentInfo::getEquipmentId,
  277. e -> e.getEquipmentType() != null ? e.getEquipmentType() : 0,
  278. (v1, v2) -> v1
  279. ));
  280. // 查询充电接口信息
  281. List<ThirdPartyConnectorInfo> connectorList = new ArrayList<>();
  282. if (!equipmentList.isEmpty()) {
  283. List<String> equipmentIds = equipmentList.stream()
  284. .map(ThirdPartyEquipmentInfo::getEquipmentId)
  285. .collect(Collectors.toList());
  286. connectorList = thirdPartyConnectorInfoMapper.selectList(
  287. new LambdaQueryWrapper<ThirdPartyConnectorInfo>()
  288. .in(ThirdPartyConnectorInfo::getEquipmentId, equipmentIds)
  289. );
  290. }
  291. // 统计终端状态
  292. int idleCount = 0;
  293. int occupiedCount = 0;
  294. int offlineCount = 0;
  295. List<AppletConnectorListVO.ConnectorItemVO> connectorVOList = new ArrayList<>();
  296. for (ThirdPartyConnectorInfo connector : connectorList) {
  297. AppletConnectorListVO.ConnectorItemVO vo = new AppletConnectorListVO.ConnectorItemVO();
  298. vo.setConnectorId(connector.getId());
  299. vo.setConnectorName(connector.getConnectorName());
  300. vo.setConnectorCode(connector.getConnectorId());
  301. // 设备分类
  302. Integer equipmentType = equipmentTypeMap.get(connector.getEquipmentId());
  303. vo.setEquipmentType(getEquipmentTypeName(equipmentType));
  304. // 状态处理
  305. Integer connectorStatus = connector.getStatus();
  306. if (connectorStatus == null) {
  307. connectorStatus = 0;
  308. }
  309. vo.setStatus(connectorStatus);
  310. vo.setStatusName(getStatusName(connectorStatus));
  311. // 统计
  312. switch (connectorStatus) {
  313. case 1:
  314. idleCount++;
  315. break;
  316. case 2:
  317. case 3:
  318. case 4:
  319. occupiedCount++;
  320. break;
  321. default:
  322. offlineCount++;
  323. }
  324. connectorVOList.add(vo);
  325. }
  326. result.setConnectorList(connectorVOList);
  327. result.setIdleCount(idleCount);
  328. result.setOccupiedCount(occupiedCount);
  329. result.setOfflineCount(offlineCount);
  330. return result;
  331. }
  332. @Override
  333. public AppletStationPriceListVO getStationPriceList(Long stationId) {
  334. // 查询站点信息
  335. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectById(stationId);
  336. if (stationInfo == null) {
  337. log.warn("站点信息不存在,stationId: {}", stationId);
  338. return null;
  339. }
  340. log.info("查询站点价格列表,stationId: {}, stationName: {}, station_id字段: {}",
  341. stationId, stationInfo.getStationName(), stationInfo.getStationId());
  342. AppletStationPriceListVO result = new AppletStationPriceListVO();
  343. result.setStationId(stationId);
  344. result.setStationName(stationInfo.getStationName());
  345. result.setTips(stationInfo.getStationTips());
  346. // 获取当前登录用户ID
  347. Long userId = SecurityUtils.getUserId();
  348. log.info("当前用户ID: {}", userId);
  349. // 查询用户是否为企业用户,获取企业ID
  350. Long firmId = null;
  351. if (userId != null) {
  352. UserFirm userFirm = userFirmMapper.selectOne(
  353. new LambdaQueryWrapper<UserFirm>()
  354. .eq(UserFirm::getUserId, userId)
  355. .last("LIMIT 1")
  356. );
  357. if (userFirm != null) {
  358. firmId = userFirm.getFirmId();
  359. log.info("用户所属企业ID: {}", firmId);
  360. }
  361. }
  362. // 获取当前时间(HHmmss格式)
  363. String currentTime = LocalTime.now().format(TIME_FORMATTER);
  364. log.info("当前时间: {}", currentTime);
  365. // 查询站点对应的价格策略
  366. // 1. 先查询充电接口
  367. ThirdPartyConnectorInfo connector = thirdPartyConnectorInfoMapper.selectOne(
  368. new LambdaQueryWrapper<ThirdPartyConnectorInfo>()
  369. .eq(ThirdPartyConnectorInfo::getStationId, stationInfo.getStationId())
  370. .last("LIMIT 1")
  371. );
  372. if (connector == null) {
  373. log.warn("未找到充电接口信息,station_id: {}", stationInfo.getStationId());
  374. result.setPriceList(new ArrayList<>());
  375. return result;
  376. }
  377. log.info("找到充电接口,connectorId: {}", connector.getConnectorId());
  378. // 2. 查询价格策略
  379. ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
  380. new LambdaQueryWrapper<ThirdPartyEquipmentPricePolicy>()
  381. .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, connector.getConnectorId())
  382. .eq(ThirdPartyEquipmentPricePolicy::getIsDeleted, 0)
  383. .last("LIMIT 1")
  384. );
  385. if (pricePolicy == null) {
  386. log.warn("未找到价格策略,connectorId: {}", connector.getConnectorId());
  387. result.setPriceList(new ArrayList<>());
  388. return result;
  389. }
  390. log.info("找到价格策略,pricePolicyId: {}", pricePolicy.getId());
  391. // 3. 查询价格明细列表
  392. List<ThirdPartyPolicyInfo> policyInfoList = thirdPartyPolicyInfoMapper.selectList(
  393. new LambdaQueryWrapper<ThirdPartyPolicyInfo>()
  394. .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
  395. .eq(ThirdPartyPolicyInfo::getIsDeleted, 0)
  396. .orderByAsc(ThirdPartyPolicyInfo::getStartTime)
  397. );
  398. log.info("查询到价格明细记录数: {}", policyInfoList.size());
  399. // 如果没有查询到价格策略明细,说明该站点未配置价格策略,直接返回空列表
  400. if (policyInfoList.isEmpty()) {
  401. log.warn("third_party_policy_info表中没有价格策略数据,pricePolicyId: {}", pricePolicy.getId());
  402. result.setPriceList(new ArrayList<>());
  403. result.setHasEnterprisePrice(false);
  404. return result;
  405. }
  406. // 4. 查询平台价格(salesType=0),用于普通用户的价格计算
  407. Map<Integer, PolicyFee> platformPriceMap = new HashMap<>();
  408. List<PolicyFee> platformPolicyFeeList = policyFeeMapper.selectList(
  409. new LambdaQueryWrapper<PolicyFee>()
  410. .eq(PolicyFee::getStationInfoId, stationId)
  411. .eq(PolicyFee::getSalesType, 0) // 0-平台
  412. .eq(PolicyFee::getIsDeleted, 0)
  413. );
  414. log.info("查询平台价格记录数: {}, stationInfoId: {}", platformPolicyFeeList.size(), stationId);
  415. for (PolicyFee policyFee : platformPolicyFeeList) {
  416. platformPriceMap.put(policyFee.getPeriodFlag(), policyFee);
  417. }
  418. // 5. 如果是企业用户,查询企业价格
  419. Map<Integer, BigDecimal> enterprisePriceMap = new HashMap<>();
  420. boolean hasEnterprisePrice = false;
  421. if (firmId != null) {
  422. List<PolicyFee> policyFeeList = policyFeeMapper.selectList(
  423. new LambdaQueryWrapper<PolicyFee>()
  424. .eq(PolicyFee::getStationInfoId, stationId)
  425. .eq(PolicyFee::getSalesType, 1) // 1-企业
  426. .eq(PolicyFee::getFirmId, firmId)
  427. .eq(PolicyFee::getIsDeleted, 0)
  428. );
  429. log.info("查询企业价格记录数: {}, stationInfoId: {}, firmId: {}",
  430. policyFeeList.size(), stationId, firmId);
  431. // 如果查询到企业价格记录,设置标志为true
  432. if (!policyFeeList.isEmpty()) {
  433. hasEnterprisePrice = true;
  434. }
  435. // 构建企业价格映射 (periodFlag -> enterprisePrice)
  436. for (PolicyFee policyFee : policyFeeList) {
  437. // 企业价格 = 综合销售费(直接取c_policy_fee.comp_sales_fee)
  438. BigDecimal compSalesFee = policyFee.getCompSalesFee() != null ? policyFee.getCompSalesFee() : BigDecimal.ZERO;
  439. enterprisePriceMap.put(policyFee.getPeriodFlag(), compSalesFee);
  440. log.debug("企业价格计算 - periodFlag: {}, 综合销售费(企业价): {}",
  441. policyFee.getPeriodFlag(), compSalesFee);
  442. }
  443. }
  444. // 设置是否有企业价格
  445. result.setHasEnterprisePrice(hasEnterprisePrice);
  446. log.info("是否有企业价格: {}", hasEnterprisePrice);
  447. // 6. 构建价格列表
  448. List<AppletStationPriceListVO.PriceItemVO> priceList = new ArrayList<>();
  449. for (int i = 0; i < policyInfoList.size(); i++) {
  450. ThirdPartyPolicyInfo policyInfo = policyInfoList.get(i);
  451. AppletStationPriceListVO.PriceItemVO priceItem = new AppletStationPriceListVO.PriceItemVO();
  452. // 时段格式化:HHmmss -> HH:mm
  453. String startTime = formatTime(policyInfo.getStartTime());
  454. // 下一个时段的开始时间作为当前时段的结束时间
  455. String endTime = (i + 1 < policyInfoList.size())
  456. ? formatTime(policyInfoList.get(i + 1).getStartTime())
  457. : "24:00";
  458. priceItem.setTimePeriod(startTime + "-" + endTime);
  459. priceItem.setPeriodFlag(policyInfo.getPeriodFlag());
  460. priceItem.setPeriodFlagName(getPeriodFlagName(policyInfo.getPeriodFlag()));
  461. priceItem.setElecPrice(policyInfo.getElecPrice());
  462. // 从平台价格映射中获取运营费和综合销售费
  463. BigDecimal originalServicePrice = policyInfo.getServicePrice() != null ? policyInfo.getServicePrice() : BigDecimal.ZERO;
  464. BigDecimal platformOpFee = BigDecimal.ZERO;
  465. BigDecimal platformCompSalesFee = BigDecimal.ZERO;
  466. PolicyFee platformPolicyFee = platformPriceMap.get(policyInfo.getPeriodFlag());
  467. if (platformPolicyFee != null) {
  468. platformOpFee = platformPolicyFee.getOpFee() != null ? platformPolicyFee.getOpFee() : BigDecimal.ZERO;
  469. platformCompSalesFee = platformPolicyFee.getCompSalesFee() != null ? platformPolicyFee.getCompSalesFee() : BigDecimal.ZERO;
  470. }
  471. // 服务费 = 原服务费 + 运营费
  472. BigDecimal servicePrice = originalServicePrice.add(platformOpFee);
  473. priceItem.setServicePrice(servicePrice);
  474. // 合计充电价 = 综合销售费(直接取c_policy_fee.comp_sales_fee)
  475. priceItem.setTotalPrice(platformCompSalesFee);
  476. log.debug("平台价格计算 - periodFlag: {}, 电费: {}, 原服务费: {}, 运营费: {}, 服务费(含运营): {}, 综合销售费(totalPrice): {}",
  477. policyInfo.getPeriodFlag(), policyInfo.getElecPrice(), originalServicePrice, platformOpFee, servicePrice, platformCompSalesFee);
  478. // 设置企业价格(如果有)- 通过periodFlag匹配
  479. BigDecimal enterprisePrice = enterprisePriceMap.get(policyInfo.getPeriodFlag());
  480. priceItem.setEnterprisePrice(enterprisePrice);
  481. // 判断是否为当前时段
  482. priceItem.setCurrentPeriod(isCurrentPeriod(policyInfo.getStartTime(),
  483. (i + 1 < policyInfoList.size()) ? policyInfoList.get(i + 1).getStartTime() : null,
  484. currentTime));
  485. priceList.add(priceItem);
  486. }
  487. result.setPriceList(priceList);
  488. return result;
  489. }
  490. /**
  491. * 获取设备类型名称
  492. */
  493. private String getEquipmentTypeName(Integer equipmentType) {
  494. if (equipmentType == null) {
  495. return "未知";
  496. }
  497. switch (equipmentType) {
  498. case 1:
  499. return "直流设备";
  500. case 2:
  501. return "交流设备";
  502. case 3:
  503. return "交直流一体设备";
  504. case 4:
  505. return "无线设备";
  506. case 5:
  507. return "其他";
  508. default:
  509. return "未知";
  510. }
  511. }
  512. /**
  513. * 获取状态名称
  514. */
  515. private String getStatusName(Integer status) {
  516. if (status == null) {
  517. return "未知";
  518. }
  519. switch (status) {
  520. case 0:
  521. return "离网";
  522. case 1:
  523. return "空闲";
  524. case 2:
  525. return "占用(未充电)";
  526. case 3:
  527. return "占用(充电中)";
  528. case 4:
  529. return "占用(预约锁定)";
  530. case 255:
  531. return "故障";
  532. default:
  533. return "未知";
  534. }
  535. }
  536. /**
  537. * 获取时段标志名称
  538. */
  539. private String getPeriodFlagName(Integer periodFlag) {
  540. if (periodFlag == null) {
  541. return "";
  542. }
  543. switch (periodFlag) {
  544. case 1:
  545. return "尖";
  546. case 2:
  547. return "峰";
  548. case 3:
  549. return "平";
  550. case 4:
  551. return "谷";
  552. default:
  553. return "";
  554. }
  555. }
  556. /**
  557. * 格式化时间 HHmmss -> HH:mm
  558. */
  559. private String formatTime(String time) {
  560. if (time == null || time.length() < 4) {
  561. return "00:00";
  562. }
  563. return time.substring(0, 2) + ":" + time.substring(2, 4);
  564. }
  565. /**
  566. * 判断是否为当前时段
  567. */
  568. private boolean isCurrentPeriod(String startTime, String nextStartTime, String currentTime) {
  569. if (startTime == null || currentTime == null) {
  570. return false;
  571. }
  572. // 当前时间 >= 开始时间 且 当前时间 < 下一个时段开始时间
  573. if (currentTime.compareTo(startTime) >= 0) {
  574. if (nextStartTime == null || currentTime.compareTo(nextStartTime) < 0) {
  575. return true;
  576. }
  577. }
  578. return false;
  579. }
  580. /**
  581. * 从字典表获取默认运营费
  582. * @return 默认运营费,如果字典未配置则返回0.2
  583. */
  584. private BigDecimal getDefaultOpFee() {
  585. DictItem dictItem = dictItemMapper.selectOne(
  586. new LambdaQueryWrapper<DictItem>()
  587. .eq(DictItem::getDictCode, DEFAULT_OP_FEE_DICT_CODE)
  588. .eq(DictItem::getStatus, 1)
  589. .last("LIMIT 1")
  590. );
  591. if (dictItem != null && dictItem.getValue() != null) {
  592. try {
  593. return new BigDecimal(dictItem.getValue());
  594. } catch (NumberFormatException e) {
  595. log.warn("默认运营费字典值转换失败: {}", dictItem.getValue());
  596. }
  597. }
  598. return new BigDecimal("0.2");
  599. }
  600. @Override
  601. public AppChargingCostVO getCurrentChargingCost() {
  602. // 获取当前登录用户ID
  603. Long userId = SecurityUtils.getUserId();
  604. if (userId == null) {
  605. return null;
  606. }
  607. return thirdPartyStationInfoMapper.selectCurrentChargingCost(userId);
  608. }
  609. @Override
  610. public List<AppStationSearchVO> searchStations(String keyword, BigDecimal longitude, BigDecimal latitude) {
  611. // 关键词为空或空字符串,返回空列表
  612. if (keyword == null || keyword.trim().isEmpty()) {
  613. return new ArrayList<>();
  614. }
  615. // 调用Mapper查询
  616. return thirdPartyStationInfoMapper.searchStations(keyword.trim(), longitude, latitude);
  617. }
  618. @Override
  619. public AppletConnectorDetailVO getConnectorDetail(String connectorCode) {
  620. // 获取当前登录用户ID
  621. Long userId = SecurityUtils.getUserId();
  622. // 获取当前时间(HHmmss格式)
  623. String currentTime = LocalTime.now().format(TIME_FORMATTER);
  624. // 查询用户余额
  625. BigDecimal userBalance = null;
  626. if (userId != null) {
  627. UserAccount userAccount = userAccountMapper.selectOne(
  628. new LambdaQueryWrapper<UserAccount>()
  629. .eq(UserAccount::getUserId, userId)
  630. .eq(UserAccount::getIsDeleted, 0)
  631. );
  632. if (userAccount != null) {
  633. userBalance = userAccount.getBalance();
  634. }
  635. }
  636. // 查询新用户优惠金额(仅当用户为新用户时)
  637. BigDecimal newUserDiscount = null;
  638. if (userId != null) {
  639. // 检查用户是否有充电订单
  640. Long orderCount = chargeOrderInfoMapper.selectCount(
  641. new LambdaQueryWrapper<ChargeOrderInfo>()
  642. .eq(ChargeOrderInfo::getUserId, userId)
  643. .eq(ChargeOrderInfo::getIsDeleted, 0)
  644. );
  645. // 如果是新用户(没有订单),查询新用户首单优惠金额
  646. if (orderCount == 0) {
  647. // 查询c_discounts_activity表,type=1(新用户首单立减)且status=1(启用)的数据
  648. List<DiscountsActivity> discountActivities = discountsActivityMapper.selectList(
  649. new LambdaQueryWrapper<DiscountsActivity>()
  650. .eq(DiscountsActivity::getType, 1) // 1-新用户首单立减
  651. .eq(DiscountsActivity::getStatus, 1) // 1-启用
  652. .eq(DiscountsActivity::getIsDeleted, 0)
  653. );
  654. // 计算所有启用的新用户优惠金额总和
  655. if (!discountActivities.isEmpty()) {
  656. newUserDiscount = discountActivities.stream()
  657. .map(DiscountsActivity::getDiscount)
  658. .filter(discount -> discount != null)
  659. .reduce(BigDecimal.ZERO, BigDecimal::add);
  660. log.info("新用户首单优惠金额 - userId: {}, discount: {}", userId, newUserDiscount);
  661. }
  662. }
  663. }
  664. // 调用Mapper方法,一次SQL查询获取所有信息
  665. AppletConnectorDetailVO result = thirdPartyConnectorInfoMapper.selectConnectorDetailById(
  666. connectorCode, userId, currentTime, userBalance, newUserDiscount
  667. );
  668. if (result == null) {
  669. log.warn("充电接口不存在,connectorId: {}", connectorCode);
  670. }
  671. return result;
  672. }
  673. @Override
  674. public BigDecimal calculateAvailableChargingAmount(String connectorCode) {
  675. // 获取当前登录用户ID
  676. Long userId = SecurityUtils.getUserId();
  677. if (userId == null) {
  678. log.warn("未登录用户,无法计算可用充电金额");
  679. return BigDecimal.ZERO;
  680. }
  681. // 1. 查询当前用户余额
  682. UserAccount userAccount = userAccountMapper.selectOne(
  683. new LambdaQueryWrapper<UserAccount>()
  684. .eq(UserAccount::getUserId, userId)
  685. .eq(UserAccount::getIsDeleted, 0)
  686. );
  687. if (userAccount == null || userAccount.getBalance() == null) {
  688. log.warn("用户账户不存在或余额为空 - userId: {}", userId);
  689. return BigDecimal.ZERO;
  690. }
  691. BigDecimal userBalance = userAccount.getBalance();
  692. // 2. 从字典表查询安全价(safety_fee)
  693. DictItem safetyFeeItem = dictItemMapper.selectOne(
  694. new LambdaQueryWrapper<DictItem>()
  695. .eq(DictItem::getDictCode, "safety_fee")
  696. .eq(DictItem::getStatus, 1)
  697. .last("LIMIT 1")
  698. );
  699. BigDecimal safetyFee = safetyFeeItem != null && safetyFeeItem.getValue() != null
  700. ? new BigDecimal(safetyFeeItem.getValue())
  701. : BigDecimal.ZERO;
  702. // 计算扣除安全价后的余额
  703. BigDecimal balanceAfterSafety = userBalance.subtract(safetyFee);
  704. if (balanceAfterSafety.compareTo(BigDecimal.ZERO) <= 0) {
  705. log.info("用户余额不足 - userId: {}, balance: {}, safetyFee: {}", userId, userBalance, safetyFee);
  706. return BigDecimal.ZERO;
  707. }
  708. // 3. 查询充电接口、设备、站点信息
  709. ThirdPartyConnectorInfo connectorInfo = thirdPartyConnectorInfoMapper.selectOne(Wrappers.<ThirdPartyConnectorInfo>lambdaQuery()
  710. .eq(ThirdPartyConnectorInfo::getConnectorId,connectorCode).last("LIMIT 1"));
  711. if (connectorInfo == null) {
  712. log.warn("充电接口不存在 - connectorId: {}", connectorCode);
  713. return BigDecimal.ZERO;
  714. }
  715. ThirdPartyEquipmentInfo equipmentInfo = thirdPartyEquipmentInfoMapper.selectOne(
  716. new LambdaQueryWrapper<ThirdPartyEquipmentInfo>()
  717. .eq(ThirdPartyEquipmentInfo::getEquipmentId, connectorInfo.getEquipmentId())
  718. .eq(ThirdPartyEquipmentInfo::getIsDeleted, 0)
  719. .last("LIMIT 1")
  720. );
  721. if (equipmentInfo == null) {
  722. log.warn("设备信息不存在 - equipmentId: {}", connectorInfo.getEquipmentId());
  723. return BigDecimal.ZERO;
  724. }
  725. ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
  726. new LambdaQueryWrapper<ThirdPartyStationInfo>()
  727. .eq(ThirdPartyStationInfo::getStationId, equipmentInfo.getStationId())
  728. .eq(ThirdPartyStationInfo::getIsDeleted, 0)
  729. .last("LIMIT 1")
  730. );
  731. if (stationInfo == null) {
  732. log.warn("站点信息不存在 - stationId: {}", equipmentInfo.getStationId());
  733. return BigDecimal.ZERO;
  734. }
  735. // 4. 查询价格策略
  736. ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
  737. new LambdaQueryWrapper<ThirdPartyEquipmentPricePolicy>()
  738. .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, connectorInfo.getConnectorId())
  739. .eq(ThirdPartyEquipmentPricePolicy::getIsDeleted, 0)
  740. .last("LIMIT 1")
  741. );
  742. if (pricePolicy == null) {
  743. log.warn("价格策略不存在 - connectorId: {}", connectorInfo.getConnectorId());
  744. return BigDecimal.ZERO;
  745. }
  746. // 5. 查询所有时段的价格信息(按开始时间排序)
  747. List<ThirdPartyPolicyInfo> policyInfoList = thirdPartyPolicyInfoMapper.selectList(
  748. new LambdaQueryWrapper<ThirdPartyPolicyInfo>()
  749. .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
  750. .eq(ThirdPartyPolicyInfo::getIsDeleted, 0)
  751. .orderByAsc(ThirdPartyPolicyInfo::getStartTime)
  752. );
  753. if (policyInfoList.isEmpty()) {
  754. log.warn("价格策略明细不存在 - pricePolicyId: {}", pricePolicy.getId());
  755. return BigDecimal.ZERO;
  756. }
  757. // 6. 查询用户是否为企业用户
  758. UserFirm userFirm = userFirmMapper.selectOne(
  759. new LambdaQueryWrapper<UserFirm>()
  760. .eq(UserFirm::getUserId, userId)
  761. .eq(UserFirm::getIsDeleted, 0)
  762. .last("LIMIT 1")
  763. );
  764. // 7. 跨时段计算可用充电金额
  765. // 公式:可用余额 = 当前用户余额 - 安全价 - [(度数 × 运营费) + (度数 × 增值费用)]
  766. // 其中:度数 = (余额 - 安全价) / (电费 + 服务费)
  767. BigDecimal remainingBalance = balanceAfterSafety; // 剩余可用余额(用于计算度数)
  768. BigDecimal totalOpFeeCost = BigDecimal.ZERO; // 总运营费
  769. BigDecimal totalValueAddedCost = BigDecimal.ZERO; // 总增值费用
  770. // 遍历每个时段,计算在该时段能充多少度电及对应的费用
  771. for (ThirdPartyPolicyInfo policyInfo : policyInfoList) {
  772. // 获取电费、服务费
  773. BigDecimal elecPrice = policyInfo.getElecPrice() != null ? policyInfo.getElecPrice() : BigDecimal.ZERO;
  774. BigDecimal servicePrice = policyInfo.getServicePrice() != null ? policyInfo.getServicePrice() : BigDecimal.ZERO;
  775. BigDecimal basePrice = elecPrice.add(servicePrice); // 基础价格 = 电费 + 服务费
  776. if (basePrice.compareTo(BigDecimal.ZERO) == 0) {
  777. continue; // 跳过基础价格为0的时段
  778. }
  779. // 查询该时段的运营费
  780. PolicyFee policyFee;
  781. if (userFirm != null) {
  782. policyFee = policyFeeMapper.selectOne(
  783. new LambdaQueryWrapper<PolicyFee>()
  784. .eq(PolicyFee::getStationInfoId, stationInfo.getId())
  785. .eq(PolicyFee::getPeriodFlag, policyInfo.getPeriodFlag())
  786. .eq(PolicyFee::getSalesType, 1)
  787. .eq(PolicyFee::getFirmId, userFirm.getFirmId())
  788. .eq(PolicyFee::getIsDeleted, 0)
  789. .last("LIMIT 1")
  790. );
  791. } else {
  792. policyFee = policyFeeMapper.selectOne(
  793. new LambdaQueryWrapper<PolicyFee>()
  794. .eq(PolicyFee::getStationInfoId, stationInfo.getId())
  795. .eq(PolicyFee::getPeriodFlag, policyInfo.getPeriodFlag())
  796. .eq(PolicyFee::getSalesType, 0)
  797. .eq(PolicyFee::getIsDeleted, 0)
  798. .last("LIMIT 1")
  799. );
  800. }
  801. BigDecimal opFee = (policyFee != null && policyFee.getOpFee() != null)
  802. ? policyFee.getOpFee()
  803. : getDefaultOpFee();
  804. // 查询增值费用
  805. BigDecimal valueAddedFee = BigDecimal.ZERO;
  806. if (policyInfo.getPeriodFlag() != null) {
  807. String periodLabel = "";
  808. switch (policyInfo.getPeriodFlag()) {
  809. case 1: periodLabel = "尖"; break;
  810. case 2: periodLabel = "峰"; break;
  811. case 3: periodLabel = "平"; break;
  812. case 4: periodLabel = "谷"; break;
  813. }
  814. if (!periodLabel.isEmpty()) {
  815. DictItem valueAddedItem = dictItemMapper.selectOne(
  816. new LambdaQueryWrapper<DictItem>()
  817. .eq(DictItem::getDictCode, "time_period_flag")
  818. .eq(DictItem::getLabel, periodLabel)
  819. .eq(DictItem::getStatus, 1)
  820. .last("LIMIT 1")
  821. );
  822. if (valueAddedItem != null && valueAddedItem.getValue() != null) {
  823. valueAddedFee = new BigDecimal(valueAddedItem.getValue());
  824. }
  825. }
  826. }
  827. // 计算在当前时段能充多少度电(使用剩余余额)
  828. // 度数 = 剩余余额 / (电费 + 服务费)
  829. BigDecimal kwh = remainingBalance.divide(basePrice, 4, RoundingMode.HALF_UP);
  830. // 如果剩余余额不足以充电,结束计算
  831. if (kwh.compareTo(BigDecimal.ZERO) <= 0) {
  832. break;
  833. }
  834. // 计算该时段的运营费和增值费用
  835. BigDecimal periodOpFeeCost = kwh.multiply(opFee);
  836. BigDecimal periodValueAddedCost = kwh.multiply(valueAddedFee);
  837. totalOpFeeCost = totalOpFeeCost.add(periodOpFeeCost);
  838. totalValueAddedCost = totalValueAddedCost.add(periodValueAddedCost);
  839. // 更新剩余余额(减去当前时段的所有费用)
  840. // 当前时段总费用 = (电费 + 服务费) * 度数 + 运营费 + 增值费用
  841. BigDecimal periodBaseCost = kwh.multiply(basePrice);
  842. BigDecimal periodTotalCost = periodBaseCost.add(periodOpFeeCost).add(periodValueAddedCost);
  843. remainingBalance = remainingBalance.subtract(periodTotalCost);
  844. log.debug("时段计算 - startTime: {}, 电费: {}, 服务费: {}, 运营费: {}, 增值费: {}, 度数: {}, 该时段费用: {}, 剩余余额: {}",
  845. policyInfo.getStartTime(), elecPrice, servicePrice, opFee, valueAddedFee, kwh, periodTotalCost, remainingBalance);
  846. // 如果剩余余额小于等于0,结束计算
  847. if (remainingBalance.compareTo(BigDecimal.ZERO) <= 0) {
  848. break;
  849. }
  850. }
  851. // 8. 计算最终可用余额
  852. BigDecimal availableAmount = userBalance.subtract(safetyFee).subtract(totalOpFeeCost).subtract(totalValueAddedCost);
  853. // 确保不为负数
  854. if (availableAmount.compareTo(BigDecimal.ZERO) < 0) {
  855. availableAmount = BigDecimal.ZERO;
  856. }
  857. // 保留 2 位小数
  858. availableAmount = availableAmount.setScale(2, RoundingMode.HALF_UP);
  859. log.info("可用充电金额计算完成 - userId: {}, connectorId: {}, userBalance: {}, safetyFee: {}, " +
  860. "totalOpFeeCost: {}, totalValueAddedCost: {}, availableAmount: {}",
  861. userId, connectorCode, userBalance, safetyFee, totalOpFeeCost, totalValueAddedCost, availableAmount);
  862. return availableAmount;
  863. }
  864. }