From 9eeb62c02a7b3c7b95c20678b6a9c74e7f12f943 Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期三, 01 七月 2026 18:15:10 +0800
Subject: [PATCH] Merge branch '3.0.1' of http://139.186.142.91:10010/r/productDev/parkBike into 3.0.1
---
server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java | 793 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 793 insertions(+), 0 deletions(-)
diff --git a/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java b/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
new file mode 100644
index 0000000..8f85057
--- /dev/null
+++ b/server/services/src/main/java/com/doumee/service/business/impl/ReportServiceImpl.java
@@ -0,0 +1,793 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.Constants;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.DateUtil;
+import com.doumee.dao.business.BaseParamMapper;
+import com.doumee.dao.business.BikesMapper;
+import com.doumee.dao.business.GoodsorderMapper;
+import com.doumee.dao.business.MemberMapper;
+import com.doumee.dao.business.MemberRidesTrackMapper;
+import com.doumee.dao.business.join.MemberRidesJoinMapper;
+import com.doumee.dao.business.model.BaseParam;
+import com.doumee.dao.business.model.Bikes;
+import com.doumee.dao.business.model.Goodsorder;
+import com.doumee.dao.business.model.Member;
+import com.doumee.dao.business.model.MemberRides;
+import com.doumee.dao.business.model.MemberRidesTrack;
+import com.doumee.dao.business.vo.BikeIncomeStatVO;
+import com.doumee.dao.business.vo.BikeUsageStatVO;
+import com.doumee.dao.business.vo.DashboardVO;
+import com.doumee.dao.business.vo.IncomeDailyVO;
+import com.doumee.dao.business.vo.IncomeStatVO;
+import com.doumee.dao.business.vo.OperationCenterVO;
+import com.doumee.dao.business.vo.OperationOrderVO;
+import com.doumee.dao.business.vo.OrderRideItemVO;
+import com.doumee.dao.business.vo.OrderRideTrackVO;
+import com.doumee.dao.business.vo.OrderRidesDetailVO;
+import com.doumee.dao.business.vo.OverviewStatVO;
+import com.doumee.dao.business.vo.PackageSourceStatVO;
+import com.doumee.dao.business.web.request.BikeIncomeQueryDTO;
+import com.doumee.dao.business.web.request.OperationOrderQueryDTO;
+import com.doumee.service.business.ReportService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * 鏁版嵁鎶ヨ〃 Service 瀹炵幇(web 绔�:姒傝缁熻 + 鏀跺叆杞﹀瀷鍒嗘瀽)銆�
+ *
+ * @author rk
+ * @date 2026/06/26
+ */
+@Slf4j
+@Service
+public class ReportServiceImpl implements ReportService {
+
+ @Autowired
+ private MemberMapper memberMapper;
+ @Autowired
+ private MemberRidesJoinMapper memberRidesJoinMapper;
+ @Autowired
+ private BikesMapper bikesMapper;
+ @Autowired
+ private GoodsorderMapper goodsorderMapper;
+ @Autowired
+ private BaseParamMapper baseParamMapper;
+ /** 鐢佃溅楠戣杞ㄨ抗 Mapper(鑷杞﹁蛋 MQTT 鏃犺建杩�,浠呯數杞︽湁鏁版嵁) */
+ @Autowired
+ private MemberRidesTrackMapper memberRidesTrackMapper;
+
+ /** 鏃舵蹇嵎绫诲瀷 鈫� 杩� N 澶�:dateType 1鈫�7銆�2鈫�15銆�3鈫�30(鍧囧惈浠婂ぉ) */
+ private static final Map<Integer, Integer> RECENT_DAYS = new LinkedHashMap<>();
+
+ static {
+ RECENT_DAYS.put(1, 7);
+ RECENT_DAYS.put(2, 15);
+ RECENT_DAYS.put(3, 30);
+ }
+
+ /** 缁撶畻閲戦鍒嗏啋鍏冩崲绠楅櫎鏁� */
+ private static final BigDecimal CENT_PER_YUAN = new BigDecimal("100");
+
+ /** 鐧惧垎姣斿熀鏁�(澧為暱鐜� = (鏈湡 - 瀵规瘮鏈�) / 瀵规瘮鏈� 脳 100) */
+ private static final BigDecimal PERCENT_BASE = new BigDecimal("100");
+
+ /** 鏀粯鏂瑰紡:鎶栭煶鍒告牳閿�(濂楅閿�鍞潵婧愯瘑鍒敤) */
+ private static final int PAY_WAY_DOUYIN = 2;
+
+ @Override
+ public OverviewStatVO overview() {
+ OverviewStatVO vo = new OverviewStatVO();
+ // 鎬绘敞鍐岀敤鎴�:鏈垹闄ょ殑鍏ㄩ儴鐢ㄦ埛
+ vo.setTotalMembers((long) memberMapper.selectCount(
+ new QueryWrapper<Member>().lambda().eq(Member::getIsdeleted, Constants.ZERO)));
+ // 浠婃棩鏂板:鍒涘缓鏃堕棿 鈮� 浠婃棩0鐐�
+ Date todayStart = DateUtil.getStartOfDay(new Date());
+ vo.setTodayMembers((long) memberMapper.selectCount(
+ new QueryWrapper<Member>().lambda()
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .ge(Member::getCreateDate, todayStart)));
+ // 鑷杞︽暟閲�(type=0),鐢靛姩杞︽暟閲�(type=1);鍧囧惈鍏ㄩ儴鏈垹闄よ溅杈�(鍚鐢�)
+ vo.setBikeCount((long) bikesMapper.selectCount(
+ new QueryWrapper<Bikes>().lambda()
+ .eq(Bikes::getType, Constants.ZERO)
+ .eq(Bikes::getIsdeleted, Constants.ZERO)));
+ vo.setEleBikeCount((long) bikesMapper.selectCount(
+ new QueryWrapper<Bikes>().lambda()
+ .eq(Bikes::getType, Constants.ONE)
+ .eq(Bikes::getIsdeleted, Constants.ZERO)));
+ return vo;
+ }
+
+ @Override
+ public List<BikeIncomeStatVO> bikeIncome(BikeIncomeQueryDTO query) {
+ // 1. 瑙f瀽鏃舵:1/2/3 杩� N 澶�(鍚粖澶╁叡 N 澶�),4 鑷畾涔�
+ DateRange range = resolveRange(query);
+ Date start = range.start;
+ Date end = range.end;
+
+ // 2. 杞﹀瀷瀛楀吀:base_param type=3 鍗曡溅 / 4 鐢佃溅,渚涜溅鍨嬪悕 + 澶х被褰掔被
+ List<BaseParam> paramList = baseParamMapper.selectList(
+ new QueryWrapper<BaseParam>().lambda()
+ .eq(BaseParam::getIsdeleted, Constants.ZERO)
+ .in(BaseParam::getType, Constants.THREE, Constants.FOUR));
+ Map<String, BaseParam> paramMap = paramList.stream()
+ .collect(Collectors.toMap(BaseParam::getId, p -> p, (a, b) -> a));
+
+ // 3. 鏃舵鍐呭凡缁撶畻鐨勭杞︽娂閲戣鍗�(鏌ヨ妯″紡鍙傝�冨悗鍙� getBikeIncomeReportVOList:
+ // type=0 鎶奸噾绫汇�乻tatus=4 宸茬粨绠椼�乸aramId 闈炵┖銆乸ayDate 钀藉湪鍖洪棿鍐�)
+ List<Goodsorder> orders = goodsorderMapper.selectList(
+ new QueryWrapper<Goodsorder>().lambda()
+ .eq(Goodsorder::getType, Constants.ZERO)
+ .eq(Goodsorder::getStatus, Constants.FOUR)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .isNotNull(Goodsorder::getParamId)
+ .ne(Goodsorder::getParamId, StringUtils.EMPTY)
+ .ge(Goodsorder::getPayDate, start)
+ .le(Goodsorder::getPayDate, end));
+
+ // 4. 鎸� paramId(杞﹀瀷)鍒嗙粍鍚堣缁撶畻閲戦 closeMoney(鍗曚綅:鍒�)
+ Map<String, BigDecimal> incomeByParam = new LinkedHashMap<>();
+ for (Goodsorder o : orders) {
+ BigDecimal amount = o.getCloseMoney() == null ? BigDecimal.ZERO : o.getCloseMoney();
+ incomeByParam.merge(o.getParamId(), amount, BigDecimal::add);
+ }
+
+ // 5. 缁勮缁撴灉:杞﹀瀷鍚� + 澶х被 + 鏀跺叆(鍒嗏啋鍏�,2浣�),鎸夋敹鍏ラ檷搴�
+ List<BikeIncomeStatVO> result = new ArrayList<>();
+ for (Map.Entry<String, BigDecimal> e : incomeByParam.entrySet()) {
+ BaseParam param = paramMap.get(e.getKey());
+ BikeIncomeStatVO vo = new BikeIncomeStatVO();
+ vo.setParamId(e.getKey());
+ vo.setParamName(param == null ? "鏈煡杞﹀瀷" : param.getName());
+ vo.setCategory(param == null ? "鏈煡" : categoryOf(param.getType()));
+ vo.setIncome(e.getValue().divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
+ result.add(vo);
+ }
+ result.sort(Comparator.comparing(BikeIncomeStatVO::getIncome).reversed());
+ return result;
+ }
+
+ /**
+ * 鎸� base_param.type 娲剧敓杞﹁締澶х被:3 鑷杞� / 4 鐢靛姩杞�,鍏朵綑褰�"鏈煡"銆�
+ *
+ * @param type base_param.type(3 鍗曡溅绫诲瀷 / 4 鐢佃溅绫诲瀷)
+ * @return 澶х被涓枃鍚�
+ */
+ private String categoryOf(Integer type) {
+ if (type == null) {
+ return "鏈煡";
+ }
+ // 娉ㄦ剰:Constants.THREE 鏄� Integer銆丆onstants.FOUR 鏄� int(绫诲瀷涓嶄竴鑷�),
+ // 缁熶竴鎷嗘垚 int 姣旇緝,閬垮紑瀵瑰熀鏈被鍨嬭皟鐢� equals / 鍖呰绫诲紩鐢ㄦ瘮杈冪殑鍧�
+ int t = type;
+ if (t == Constants.THREE) {
+ return "鑷杞�";
+ }
+ if (t == Constants.FOUR) {
+ return "鐢靛姩杞�";
+ }
+ return "鏈煡";
+ }
+
+ /**
+ * 瑙f瀽鍚庣殑鏌ヨ鏃舵(鍧囧惈绔偣)銆�
+ */
+ private static final class DateRange {
+ /** 璧峰鏃堕棿(鍚�) */
+ final Date start;
+ /** 缁撴潫鏃堕棿(鍚�) */
+ final Date end;
+
+ DateRange(Date start, Date end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ /**
+ * 瑙f瀽鏌ヨ鏃舵:dateType 1/2/3 鈫� 杩� N 澶�(鍚粖澶╁叡 N 澶�),4 鈫� 鑷畾涔夎捣姝�(鍧囧惈)銆�
+ * <p>{@link #bikeIncome} 涓� {@link #incomeStat} 鍏辩敤,淇濊瘉涓ょ鏃舵鍙e緞涓�鑷淬��
+ *
+ * @param query 鏃舵鏌ヨ鍏ュ弬
+ * @return 瑙f瀽鍚庣殑 [start, end] 鍖洪棿
+ */
+ private DateRange resolveRange(BikeIncomeQueryDTO query) {
+ if (query == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ }
+ Integer dateType = query.getDateType();
+ Date start;
+ Date end;
+ if (dateType != null && RECENT_DAYS.containsKey(dateType)) {
+ // 蹇嵎:杩� N 澶�,鍚粖澶╁叡 N 澶�,璧峰 = 浠婂ぉ寰�鍓� N-1 澶╃殑0鐐�
+ int days = RECENT_DAYS.get(dateType);
+ end = DateUtil.getEndOfDay(new Date());
+ start = DateUtil.getStartOfDay(DateUtil.increaseDay(new Date(), -(days - 1)));
+ } else if (dateType != null && dateType == 4) {
+ // 鑷畾涔�:璧锋鍧囧惈,鏍¢獙闈炵┖涓� start<=end
+ if (query.getStartDate() == null || query.getEndDate() == null
+ || query.getStartDate().getTime() > query.getEndDate().getTime()) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鑷畾涔夋椂娈佃捣姝㈡棩鏈熶笉鍚堟硶");
+ }
+ start = query.getStartDate();
+ end = query.getEndDate();
+ } else {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏃舵绫诲瀷涓嶅悎娉�");
+ }
+ return new DateRange(start, end);
+ }
+
+ @Override
+ public IncomeStatVO incomeStat(BikeIncomeQueryDTO query) {
+ // 1. 瑙f瀽鏃舵
+ DateRange range = resolveRange(query);
+ Date start = range.start;
+ Date end = range.end;
+
+ // 2. 鏌ヨ鏈湡宸茬粨绠楃殑绉熻溅鎶奸噾璁㈠崟(鏌ヨ妯″紡鍙傝�冨悗鍙� getTotalData / getBikeIncomeReportVOList:
+ // type=0 鎶奸噾绫汇�乻tatus=4 宸茬粨绠椼�乸ayDate 钀藉湪鍖洪棿鍐�)銆傛敹鍏ョ粺璁′笉闄愯溅鍨�,鏁呬笉绾︽潫 paramId
+ List<Goodsorder> orders = goodsorderMapper.selectList(
+ new QueryWrapper<Goodsorder>().lambda()
+ .eq(Goodsorder::getType, Constants.ZERO)
+ .eq(Goodsorder::getStatus, Constants.FOUR)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .ge(Goodsorder::getPayDate, start)
+ .le(Goodsorder::getPayDate, end));
+
+ // 3. 鎸� payDate 鐨勬棩鏈�(yyyy-MM-dd)鍒嗙粍姹囨�� closeMoney(鍗曚綅:鍒�),鍚屾椂绱鍖洪棿鎬婚
+ Map<String, BigDecimal> sumByDay = new LinkedHashMap<>();
+ BigDecimal totalCents = BigDecimal.ZERO;
+ for (Goodsorder o : orders) {
+ BigDecimal amount = o.getCloseMoney() == null ? BigDecimal.ZERO : o.getCloseMoney();
+ sumByDay.merge(DateUtil.getShortDateStr(o.getPayDate()), amount, BigDecimal::add);
+ totalCents = totalCents.add(amount);
+ }
+
+ // 4. 鐢熸垚瀹屾暣鏃ユ湡搴忓垪(鏌辩姸鍥炬í杞磋繛缁�),鏃犳暟鎹棩琛� 0銆�
+ // 娉�:DateUtil.getDateList 鐢� dEnd.after(begin) 姣旇緝,鑻ヤ袱绔椂鍒嗙涓嶄竴鑷翠細澶氱畻涓�澶�,
+ // 鏁呯粺涓�瑙勬暣鍒板綋澶� 0 鐐瑰啀鐢熸垚搴忓垪
+ List<IncomeDailyVO> dailyList = new ArrayList<>();
+ for (Date d : DateUtil.getDateList(DateUtil.getStartOfDay(start), DateUtil.getStartOfDay(end))) {
+ IncomeDailyVO vo = new IncomeDailyVO();
+ vo.setDate(DateUtil.getShortDateStr(d));
+ BigDecimal daySum = sumByDay.getOrDefault(vo.getDate(), BigDecimal.ZERO);
+ // 鍒嗏啋鍏�,2浣嶅皬鏁�
+ vo.setIncome(daySum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP));
+ dailyList.add(vo);
+ }
+
+ // 5. 鍖洪棿绱鏀跺叆(澶嶇敤宸叉煡鐨勬湰鏈熸暟鎹�,鍒嗏啋鍏�,閬垮厤閲嶅鏌ヨ)
+ BigDecimal totalIncome = totalCents.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+ IncomeStatVO result = new IncomeStatVO();
+ result.setDailyList(dailyList);
+ result.setTotalIncome(totalIncome);
+
+ // 6. 鐜瘮:绱ч偦鍓嶄竴绛夐暱鍖洪棿(鏁翠綋骞崇Щ -N 澶�,N=鍖洪棿澶╂暟)銆傚姣旀湡鍙眹鎬婚噾棰�,涓嶉渶姣忔棩鏄庣粏
+ int spanDays = DateUtil.daysBetweenDates(start, end) + 1;
+ BigDecimal chainAmount = sumClosedMoney(
+ DateUtil.increaseDay(start, -spanDays), DateUtil.increaseDay(end, -spanDays));
+ result.setChainAmount(chainAmount);
+ result.setChainRate(growthRate(totalIncome, chainAmount));
+
+ // 7. 鍚屾瘮:鍘诲勾鍚屾湡鍚岄暱搴﹀尯闂�(骞崇Щ -1 骞�,increaseYear 鎸� Calendar 绮剧‘骞崇Щ,闂板勾涓嶅け鐪�)
+ BigDecimal yearOnYearAmount = sumClosedMoney(
+ DateUtil.increaseYear(start, -1), DateUtil.increaseYear(end, -1));
+ result.setYearOnYearAmount(yearOnYearAmount);
+ result.setYearOnYearRate(growthRate(totalIncome, yearOnYearAmount));
+
+ return result;
+ }
+
+ /**
+ * 姹囨�绘寚瀹氭椂娈靛唴宸茬粨绠楃杞︽娂閲戣鍗曠殑缁撶畻鏀跺叆(closeMoney 涔嬪拰,鍒嗏啋鍏�)銆�
+ * <p>渚涚疮璁℃敹鍏ャ�佺幆姣斻�佸悓姣斿鐢�,缁熶竴鏀跺叆鍙e緞;鍙� select closeMoney 鍒椾互鍑忓皯鏁版嵁浼犺緭銆�
+ *
+ * @param start 璧峰鏃堕棿(鍚�)
+ * @param end 缁撴潫鏃堕棿(鍚�)
+ * @return 鍖洪棿缁撶畻鏀跺叆(鍏�,2浣嶅皬鏁�);鏃犳暟鎹繑鍥� 0
+ */
+ private BigDecimal sumClosedMoney(Date start, Date end) {
+ List<Goodsorder> orders = goodsorderMapper.selectList(
+ new QueryWrapper<Goodsorder>().lambda()
+ .select(Goodsorder::getCloseMoney)
+ .eq(Goodsorder::getType, Constants.ZERO)
+ .eq(Goodsorder::getStatus, Constants.FOUR)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .ge(Goodsorder::getPayDate, start)
+ .le(Goodsorder::getPayDate, end));
+ BigDecimal sum = BigDecimal.ZERO;
+ for (Goodsorder o : orders) {
+ if (o.getCloseMoney() != null) {
+ sum = sum.add(o.getCloseMoney());
+ }
+ }
+ // 鍒嗏啋鍏�,2浣嶅皬鏁�
+ return sum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ /**
+ * 璁$畻澧為暱鐜�:(current - base) / base 脳 100,淇濈暀2浣嶅皬鏁般��
+ *
+ * @param current 鏈湡鍊�
+ * @param base 瀵规瘮鏈熷��
+ * @return 澧為暱鐜�(%);base 涓� 0 鎴� null 鏃惰繑鍥� null(鏃犳硶璁$畻,鍓嶇鏄剧ず"-")
+ */
+ private BigDecimal growthRate(BigDecimal current, BigDecimal base) {
+ if (base == null || base.compareTo(BigDecimal.ZERO) == 0) {
+ // 瀵规瘮鏈熸棤鏀跺叆,澧為暱鐜囨棤鎰忎箟
+ return null;
+ }
+ BigDecimal current0 = current == null ? BigDecimal.ZERO : current;
+ return current0.subtract(base)
+ .multiply(PERCENT_BASE)
+ .divide(base, 2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ @Override
+ public OperationCenterVO operationCenter() {
+ Date now = new Date();
+ // 浠婃棩璧锋(鍚鐐�),鐢ㄤ簬"浠婃棩*"绯诲垪缁熻
+ Date todayStart = DateUtil.getStartOfDay(now);
+ Date todayEnd = DateUtil.getEndOfDay(now);
+
+ OperationCenterVO vo = new OperationCenterVO();
+ // 浠婃棩鏃ユ湡 + 鏄熸湡鍑�
+ vo.setToday(DateUtil.getShortDateStr(now));
+ vo.setWeekDay(DateUtil.getWeekOfDate(now));
+
+ // 浠婃棩璁㈠崟鎬绘暟:浠婃棩宸叉敮浠樿鍗�(payStatus=1),鍚獞琛屾娂閲�(type=0)涓庡椁愬崱(type=1)
+ vo.setTodayOrderCount((long) goodsorderMapper.selectCount(
+ new QueryWrapper<Goodsorder>().lambda()
+ .eq(Goodsorder::getPayStatus, Constants.ONE)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .ge(Goodsorder::getPayDate, todayStart)
+ .le(Goodsorder::getPayDate, todayEnd)));
+
+ // 杩涜涓鍗曟暟閲�:楠戣涓�(type=0 鎶奸噾銆佸凡鏀粯鏈粨绠� status=1),瀹炴椂鍦ㄩ��,涓嶉檺鏃ユ湡
+ vo.setOngoingOrderCount((long) goodsorderMapper.selectCount(
+ new QueryWrapper<Goodsorder>().lambda()
+ .eq(Goodsorder::getType, Constants.ZERO)
+ .eq(Goodsorder::getStatus, Constants.ONE)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)));
+
+ // 浠婃棩濂楅鏀跺叆(鍏�):浠婃棩濂楅鍗¤喘涔�(type=1銆佸凡鏀粯)鐨� money 涔嬪拰
+ vo.setPackageIncome(sumMoney(Constants.ONE, todayStart, todayEnd));
+
+ // 浠婃棩鎬绘敹鍏�(鍏�):涓庢敹鍏ョ粺璁″悓鍙e緞(type=0 鎶奸噾 + status=4 宸茬粨绠� 鐨� closeMoney),澶嶇敤
+ vo.setTotalIncome(sumClosedMoney(todayStart, todayEnd));
+
+ return vo;
+ }
+
+ /**
+ * 姹囨�绘寚瀹氳鍗曠被鍨嬪湪鏃舵鍐呭凡鏀粯璁㈠崟鐨勬敮浠橀噾棰�(money 涔嬪拰,鍒嗏啋鍏�)銆�
+ * <p>渚涜繍钀ヤ腑蹇�"浠婃棩濂楅鏀跺叆"绛夋寜 type 缁熻鏀粯閲戦浣跨敤銆�
+ *
+ * @param type 璁㈠崟绫诲瀷(0 绉熻溅鎶奸噾 / 1 濂楅鍗¤喘涔�)
+ * @param start 璧峰鏃堕棿(鍚�)
+ * @param end 缁撴潫鏃堕棿(鍚�)
+ * @return 鍖洪棿鏀粯閲戦(鍏�,2浣嶅皬鏁�);鏃犳暟鎹繑鍥� 0
+ */
+ private BigDecimal sumMoney(Integer type, Date start, Date end) {
+ List<Goodsorder> orders = goodsorderMapper.selectList(
+ new QueryWrapper<Goodsorder>().lambda()
+ .select(Goodsorder::getMoney)
+ .eq(Goodsorder::getType, type)
+ .eq(Goodsorder::getPayStatus, Constants.ONE)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .ge(Goodsorder::getPayDate, start)
+ .le(Goodsorder::getPayDate, end));
+ BigDecimal sum = BigDecimal.ZERO;
+ for (Goodsorder o : orders) {
+ if (o.getMoney() != null) {
+ sum = sum.add(o.getMoney());
+ }
+ }
+ // 鍒嗏啋鍏�,2浣嶅皬鏁�
+ return sum.divide(CENT_PER_YUAN, 2, BigDecimal.ROUND_HALF_UP);
+ }
+
+ @Override
+ public PageData<OperationOrderVO> operationOrderPage(PageWrap<OperationOrderQueryDTO> pageWrap) {
+ // 鍒嗛〉瀵硅薄
+ IPage<OperationOrderVO> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+ OperationOrderQueryDTO model = pageWrap.getModel() == null
+ ? new OperationOrderQueryDTO() : pageWrap.getModel();
+
+ // 鈹�鈹� 姝ラ1:鍒嗛〉涓绘煡璇� 鈹�鈹�
+ // 涓昏〃 goodsorder left join member(鍙栨墜鏈哄彿)+ left join base_param(鍙栫粨绠楄溅鍨嬪悕);
+ // 璁㈠崟鐘舵��(status)鏄犲皠鍒� VO 鍐呴儴鎵胯浇瀛楁,渚涘洖濉尯鍒嗗彇鏁板垎鏀�;涓嶅啀甯﹂�愯鍙栨暟瀛愭煡璇€��
+ // 娉�:bikeType 绛涢�変粛鐢� inSql 瀛愭煡璇⑩�斺�斿畠鍙綔 WHERE 鏉′欢銆佹暣椤垫墽琛屼竴娆�,涓嶆槸閫愯鎶曞奖,鍙繚鐣欍��
+ MPJLambdaWrapper<Goodsorder> wrapper = new MPJLambdaWrapper<Goodsorder>()
+ // 涓昏〃瀛楁:璁㈠崟涓婚敭銆佺紪鍙枫�佺粨绠楁椂闂�
+ .select(Goodsorder::getId, Goodsorder::getCode, Goodsorder::getCloseDate)
+ // 璁㈠崟鐘舵��:鍐呴儴鎵胯浇瀛楁(@JsonIgnore,涓嶈繑鍥炲墠绔�),鐢ㄤ簬鍥炲~鍒嗘敮鍒ゆ柇
+ .selectAs(Goodsorder::getStatus, OperationOrderVO::getOrderStatus)
+ // 缁撶畻杞﹀瀷鍚�:宸茬粨绠楄鍗曠洿鎺� left join base_param 鍙�(goodsorder.param_id鈫抌ase_param.name)
+ .selectAs(BaseParam::getName, OperationOrderVO::getSettleParamName)
+ // 鐢ㄦ埛鎵嬫満鍙�:left join member
+ .selectAs(Member::getPhone, OperationOrderVO::getPhone)
+ .leftJoin(Member.class, Member::getId, Goodsorder::getMemberId)
+ .leftJoin(BaseParam.class, BaseParam::getId, Goodsorder::getParamId)
+ // 鍥哄畾鏉′欢:榛樿鍙煡鎶奸噾璁㈠崟(type=0)
+ .eq(Goodsorder::getType, Constants.ZERO)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ // 璁㈠崟鐘舵��:1杩涜涓� / 4宸插畬缁�(鍙��)
+ .eq(Objects.nonNull(model.getStatus()), Goodsorder::getStatus, model.getStatus())
+ // 鐢ㄦ埛鎵嬫満鍙�:妯$硦鍖归厤(鍙��)
+ .like(StringUtils.isNotBlank(model.getPhone()), Member::getPhone, model.getPhone())
+ // 璁㈠崟绫诲瀷:鎸夐獞琛岃褰� member_rides.type 绛涢��(鍙��)
+ .inSql(Objects.nonNull(model.getBikeType()), Goodsorder::getId,
+ "select ordre_id from member_rides where isdeleted = 0 and type = " + model.getBikeType())
+ .orderByDesc(Goodsorder::getPayDate);
+ IPage<OperationOrderVO> result = goodsorderMapper.selectJoinPage(page, OperationOrderVO.class, wrapper);
+ List<OperationOrderVO> records = result.getRecords();
+ // 鏃犳暟鎹洿鎺ヨ繑鍥�,閬垮厤绌� in() 鏌ヨ
+ if (records.isEmpty()) {
+ return PageData.from(result);
+ }
+
+ // 鈹�鈹� 姝ラ2:鏀堕泦褰撳墠椤佃鍗� id 鈹�鈹�
+ List<String> orderIds = records.stream().map(OperationOrderVO::getId).collect(Collectors.toList());
+
+ // 鈹�鈹� 姝ラ3:涓�娆℃�ф壒閲忔煡楠戣璁板綍(鍚獞琛岃溅鍨嬪悕) 鈹�鈹�
+ // left join base_param 鐩存帴甯﹀嚭楠戣杞﹀瀷鍚�(member_rides.param_id鈫抌ase_param.name鈫扢emberRides.paramName);
+ // 鎸� create_date desc 鎺掑簭,鍐呭瓨鎸夎鍗曞垎缁勫彇姣忕粍绗竴鏉″嵆"鏈�杩戜竴鏉¢獞琛岃褰�"
+ List<MemberRides> rides = memberRidesJoinMapper.selectJoinList(MemberRides.class,
+ new MPJLambdaWrapper<MemberRides>()
+ .select(MemberRides::getOrdreId, MemberRides::getType, MemberRides::getRentDate,
+ MemberRides::getBikeCode)
+ .selectAs(BaseParam::getName, MemberRides::getParamName)
+ .leftJoin(BaseParam.class, BaseParam::getId, MemberRides::getParamId)
+ .eq(MemberRides::getIsdeleted, Constants.ZERO)
+ .in(MemberRides::getOrdreId, orderIds)
+ .orderByDesc(MemberRides::getCreateDate)
+ .orderByDesc(MemberRides::getRentDate));
+ Map<String, MemberRides> latestRideByOrder = new LinkedHashMap<>();
+ for (MemberRides r : rides) {
+ // putIfAbsent:宸叉寜 create_date desc 鎺掑簭,棣栨鍑虹幇鍗宠璁㈠崟鏈�杩戜竴鏉�
+ latestRideByOrder.putIfAbsent(r.getOrdreId(), r);
+ }
+
+ // 鈹�鈹� 鍥炲~ VO(鍏ㄧ▼浠� 2 娆℃煡璇�:鍒嗛〉 / 楠戣;杞﹀瀷鍚嶅潎鐢� join 甯﹀嚭,鏃犻渶鍗曠嫭鏌ヨ溅鍨嬪瓧鍏�) 鈹�鈹�
+ for (OperationOrderVO vo : records) {
+ MemberRides latest = latestRideByOrder.get(vo.getId());
+ // 杞﹀瀷鍚嶅垎鏀�:杩涜涓�(status=1)鍙栨渶杩戦獞琛岀殑杞﹀瀷鍚�,鍚﹀垯(鍚凡缁撶畻)鍙栬鍗曠粨绠楄溅鍨嬪悕
+ Integer orderStatus = vo.getOrderStatus();
+ boolean inProgress = orderStatus != null && orderStatus.equals(Constants.ONE);
+ if (latest != null) {
+ // 璁㈠崟绫诲瀷銆侀獞琛屽紑濮嬫椂闂淬�佽溅杈嗙紪鍙�:缁熶竴鍙栨渶杩戜竴鏉¢獞琛岃褰�
+ // (杩涜涓嵆"褰撳墠楠戣杞﹁締",宸插畬缁撳嵆"鏈�鍚庨獞琛岃溅杈�";bike_code = bikes.code)
+ vo.setBikeType(latest.getType());
+ vo.setRentDate(latest.getRentDate());
+ if (inProgress) {
+ // 杩涜涓�:楠戣杞﹀瀷鍚�(join 宸插甫鍑�,瀛樹簬 MemberRides.paramName)
+ vo.setParamName(latest.getParamName());
+ vo.setBikeCode(latest.getBikeCode());
+ }
+ }
+ if (!inProgress) {
+ // 宸茬粨绠�:璁㈠崟缁撶畻杞﹀瀷鍚�(鍒嗛〉 join 宸插甫鍑�,瀛樹簬 settleParamName)
+ vo.setParamName(vo.getSettleParamName());
+ }
+ }
+
+ return PageData.from(result);
+ }
+
+ @Override
+ public OrderRidesDetailVO orderRidesDetail(String orderId) {
+ OrderRidesDetailVO result = new OrderRidesDetailVO();
+ // 璁㈠崟鍙蜂负绌�:鐩存帴杩斿洖绌虹粨鏋�(涓嶆姏寮傚父,鍓嶇鎸� hasTrack=false 鍏滃簳)
+ if (StringUtils.isBlank(orderId)) {
+ result.setHasTrack(false);
+ result.setRides(Collections.emptyList());
+ return result;
+ }
+
+ // 1. 鏌ヨ璁㈠崟涓嬪叏閮ㄩ獞琛岃褰�(鎸夊垱寤烘椂闂村崌搴�,杩樺師鍚屼竴璁㈠崟澶氭楠戣鐨勫厛鍚�)
+ List<MemberRides> ridesList = memberRidesJoinMapper.selectList(
+ new QueryWrapper<MemberRides>().lambda()
+ .eq(MemberRides::getOrdreId, orderId)
+ .eq(MemberRides::getIsdeleted, Constants.ZERO)
+ .orderByAsc(MemberRides::getCreateDate));
+ if (ridesList.isEmpty()) {
+ // 璁㈠崟涓嬫棤楠戣璁板綍(鐞嗚涓嶅簲鍑虹幇,鍏滃簳杩斿洖绌�)
+ result.setHasTrack(false);
+ result.setRides(Collections.emptyList());
+ return result;
+ }
+
+ // 2. 杞﹁締绫诲瀷鍙栭鏉¢獞琛� type(0鑷杞�/1鐢佃溅):鍐冲畾鏄惁鏌ヨ建杩�
+ Integer bikeType = ridesList.get(0).getType();
+ result.setBikeType(bikeType);
+ result.setBikeTypeName(bikeTypeNameOf(bikeType));
+ boolean isEbike = bikeType != null && bikeType.equals(Constants.ONE);
+
+ // 3. 杞ㄨ抗棰勮浇(浠呯數杞�):涓�娆℃�ф煡鍑鸿璁㈠崟鎵�鏈夐獞琛岃建杩�,鎸� rides_id 鍒嗙粍銆佹寜涓婃姤鏃堕棿鍗囧簭,
+ // 閬垮厤閫愭潯楠戣 N 娆℃煡杞ㄨ抗;鑷杞�(type=0)璧� MQTT 鏃� GPS,璺宠繃銆�
+ Map<String, List<OrderRideTrackVO>> trackByRide = new HashMap<>();
+ if (isEbike) {
+ List<String> ridesIds = ridesList.stream()
+ .map(MemberRides::getId).collect(Collectors.toList());
+ List<MemberRidesTrack> tracks = memberRidesTrackMapper.selectList(
+ new QueryWrapper<MemberRidesTrack>().lambda()
+ .select(MemberRidesTrack::getRidesId, MemberRidesTrack::getLongitude,
+ MemberRidesTrack::getLatitude, MemberRidesTrack::getReportTime)
+ .eq(MemberRidesTrack::getIsdeleted, Constants.ZERO)
+ .in(MemberRidesTrack::getRidesId, ridesIds)
+ .orderByAsc(MemberRidesTrack::getReportTime));
+ for (MemberRidesTrack t : tracks) {
+ OrderRideTrackVO tv = new OrderRideTrackVO();
+ tv.setLongitude(t.getLongitude());
+ tv.setLatitude(t.getLatitude());
+ tv.setReportTime(t.getReportTime());
+ trackByRide.computeIfAbsent(t.getRidesId(), k -> new ArrayList<>()).add(tv);
+ }
+ }
+
+ // 4. 缁勮楠戣璁板綍鍒楄〃(姣忔潯鎸傚搴旇建杩圭偣;鑷杞︿竴寰嬬┖杞ㄨ抗)
+ List<OrderRideItemVO> rides = new ArrayList<>(ridesList.size());
+ for (MemberRides r : ridesList) {
+ OrderRideItemVO item = new OrderRideItemVO();
+ item.setRidesId(r.getId());
+ item.setStatus(r.getStatus());
+ item.setStatusName(rideStatusNameOf(r.getStatus()));
+ item.setRentDate(r.getRentDate());
+ item.setBackDate(r.getBackDate());
+ item.setBikeCode(r.getBikeCode());
+ item.setDuration(r.getDuration());
+ item.setBikeTypeName(bikeTypeNameOf(r.getType()));
+ item.setTracks(isEbike
+ ? trackByRide.getOrDefault(r.getId(), Collections.emptyList())
+ : Collections.emptyList());
+ rides.add(item);
+ }
+ result.setRides(rides);
+
+ // 5. 杞ㄨ抗鍙敤鎬� + 鑷杞︽棤杞ㄨ抗鎻愮ず
+ if (isEbike) {
+ result.setHasTrack(true);
+ } else {
+ result.setHasTrack(false);
+ result.setNoTrackMessage("璇ヨ鍗曚负鑷杞﹁鍗�,鏃犺溅杈嗚建杩�");
+ }
+ return result;
+ }
+
+ /**
+ * 楠戣鐘舵�� 鈫� 涓枃鍚嶃��
+ * <p>member_rides.status:0璇锋眰寮�閿佷腑 / 1楠戣涓� / 2宸茶繕杞� / 3寮�閿佸け璐� / 4涓存椂閿佽溅銆�
+ *
+ * @param status 楠戣鐘舵�佸師鏂�(鍙兘涓� null)
+ * @return 鐘舵�佷腑鏂囧悕(绌哄��/鏈煡鍙栧�艰繑鍥�"鏈煡")
+ */
+ private String rideStatusNameOf(Integer status) {
+ if (status == null) {
+ return "鏈煡";
+ }
+ switch (status) {
+ case 0:
+ return "璇锋眰寮�閿佷腑";
+ case 1:
+ return "楠戣涓�";
+ case 2:
+ return "宸茶繕杞�";
+ case 3:
+ return "寮�閿佸け璐�";
+ case 4:
+ return "涓存椂閿佽溅";
+ default:
+ return "鏈煡";
+ }
+ }
+
+ /**
+ * 杞﹁締绫诲瀷 鈫� 涓枃鍚嶃��
+ * <p>member_rides.type:0鑷杞� / 1鐢靛姩杞︺��
+ *
+ * @param type 杞﹁締绫诲瀷(鍙兘涓� null)
+ * @return 绫诲瀷涓枃鍚�(鏈煡鍙栧�艰繑鍥�"鏈煡")
+ */
+ private String bikeTypeNameOf(Integer type) {
+ if (type == null) {
+ return "鏈煡";
+ }
+ if (type.equals(Constants.ZERO)) {
+ return "鑷杞�";
+ }
+ if (type.equals(Constants.ONE)) {
+ return "鐢靛姩杞�";
+ }
+ return "鏈煡";
+ }
+
+ @Override
+ public IncomeStatVO incomeStat30() {
+ // 杩�30澶�(鍚粖澶╁叡30澶�),澶嶇敤 incomeStat 鏃㈡湁瀹炵幇涓庡彛寰�
+ BikeIncomeQueryDTO query = new BikeIncomeQueryDTO();
+ query.setDateType(3);
+ return incomeStat(query);
+ }
+
+ @Override
+ public BikeUsageStatVO bikeUsageStat() {
+ // status:0 绌洪棽 / 1 浣跨敤涓�;绂佺敤(3)涓嶈鍏�;type:0 鑷杞� / 1 鐢靛姩杞�
+ BikeUsageStatVO vo = new BikeUsageStatVO();
+ vo.setBikeIdle(countBikeByStatus(Constants.ZERO, Constants.ZERO));
+ vo.setBikeInUse(countBikeByStatus(Constants.ZERO, Constants.ONE));
+ vo.setEleBikeIdle(countBikeByStatus(Constants.ONE, Constants.ZERO));
+ vo.setEleBikeInUse(countBikeByStatus(Constants.ONE, Constants.ONE));
+ return vo;
+ }
+
+ /**
+ * 鎸夎溅杈嗙被鍨� + 鐘舵�佺粺璁℃湭鍒犻櫎杞﹁締鏁伴噺銆�
+ *
+ * @param type 杞﹁締绫诲瀷 0 鑷杞� / 1 鐢靛姩杞�
+ * @param status 杞﹁締鐘舵�� 0 绌洪棽 / 1 浣跨敤涓�
+ * @return 婊¤冻鏉′欢鐨勮溅杈嗘暟
+ */
+ private long countBikeByStatus(Integer type, Integer status) {
+ return bikesMapper.selectCount(
+ new QueryWrapper<Bikes>().lambda()
+ .eq(Bikes::getType, type)
+ .eq(Bikes::getStatus, status)
+ .eq(Bikes::getIsdeleted, Constants.ZERO));
+ }
+
+ @Override
+ public PackageSourceStatVO packageSourceStat() {
+ // 鏈湀/鏈勾璧锋(pay_date 钀藉湪鍖洪棿鍐�);goodsorder type=1 濂楅鍗¤喘涔� + payStatus=1 宸叉敮浠�
+ Calendar cal = Calendar.getInstance();
+ Date monthStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.MONTH));
+ Date monthEnd = DateUtil.getEndOfDay(new Date());
+ Date yearStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.YEAR));
+ Date yearEnd = DateUtil.getEndOfDay(new Date());
+
+ PackageSourceStatVO vo = new PackageSourceStatVO();
+ vo.setMonth(countPackageSource(monthStart, monthEnd));
+ vo.setYear(countPackageSource(yearStart, yearEnd));
+ return vo;
+ }
+
+ /**
+ * 缁熻鎸囧畾鏃舵鍐呭椁愰攢鍞潵婧愭暟閲忋��
+ * <p>鎶栭煶鍏戞崲 = payWay 2,灏忕▼搴忚喘涔� = payWay 0(寰俊)銆�
+ *
+ * @param start 璧峰鏃堕棿(鍚�)
+ * @param end 缁撴潫鏃堕棿(鍚�)
+ * @return 鎶栭煶/灏忕▼搴忓椁愭暟閲�
+ */
+ private PackageSourceStatVO.PeriodCount countPackageSource(Date start, Date end) {
+ List<Goodsorder> orders = goodsorderMapper.selectList(
+ new QueryWrapper<Goodsorder>().lambda()
+ .select(Goodsorder::getPayWay)
+ .eq(Goodsorder::getType, Constants.ONE)
+ .eq(Goodsorder::getPayStatus, Constants.ONE)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .ge(Goodsorder::getPayDate, start)
+ .le(Goodsorder::getPayDate, end));
+ long douyin = 0L;
+ long mini = 0L;
+ for (Goodsorder o : orders) {
+ if (o.getPayWay() == null) {
+ continue;
+ }
+ if (o.getPayWay() == PAY_WAY_DOUYIN) {
+ douyin++;
+ } else if (o.getPayWay() == Constants.ZERO) {
+ mini++;
+ }
+ }
+ PackageSourceStatVO.PeriodCount pc = new PackageSourceStatVO.PeriodCount();
+ pc.setDouyinCount(douyin);
+ pc.setMiniCount(mini);
+ return pc;
+ }
+
+ @Override
+ public DashboardVO dashboard() {
+ DashboardVO vo = new DashboardVO();
+
+ // 鍚勬椂娈佃捣姝�:鏈湀/鏄ㄦ棩/浠婃棩
+ Date now = new Date();
+ Calendar cal = Calendar.getInstance();
+ Date monthStart = DateUtil.getStartOfDay(getFirstMs(cal, Calendar.MONTH));
+ Date monthEnd = DateUtil.getEndOfDay(now);
+ Date yesterdayStart = DateUtil.getStartOfDay(DateUtil.increaseDay(now, -1));
+ Date yesterdayEnd = DateUtil.getEndOfDay(DateUtil.increaseDay(now, -1));
+ Date todayStart = DateUtil.getStartOfDay(now);
+ Date todayEnd = DateUtil.getEndOfDay(now);
+
+ // 鏀剁泭(鍙e緞鍚� incomeStat:type=0 鎶奸噾 + status=4 宸茬粨绠� 鐨� closeMoney),澶嶇敤 sumClosedMoney
+ vo.setMonthIncome(sumClosedMoney(monthStart, monthEnd));
+ vo.setYesterdayIncome(sumClosedMoney(yesterdayStart, yesterdayEnd));
+ vo.setTodayIncome(sumClosedMoney(todayStart, todayEnd));
+
+ // 璁㈠崟鏁�:宸叉敮浠�(payStatus=1)璁㈠崟璁℃暟
+ vo.setMonthOrderCount(countPaidOrders(monthStart, monthEnd));
+ vo.setYesterdayOrderCount(countPaidOrders(yesterdayStart, yesterdayEnd));
+ vo.setTodayOrderCount(countPaidOrders(todayStart, todayEnd));
+
+ // 杞﹁締:浠呰繑鍥炴湭鍒犻櫎鎬绘暟
+ vo.setTotalBikeCount((long) bikesMapper.selectCount(
+ new QueryWrapper<Bikes>().lambda().eq(Bikes::getIsdeleted, Constants.ZERO)));
+
+ // 瀹㈡埛鏁�:鎬讳細鍛�=鏈垹闄ゅ叏閮�;浠婃棩/鏄ㄦ棩鏂板鎸� create_date 钀藉湪瀵瑰簲鍖洪棿(涓� overview 鍙e緞涓�鑷�)
+ vo.setTotalMemberCount((long) memberMapper.selectCount(
+ new QueryWrapper<Member>().lambda().eq(Member::getIsdeleted, Constants.ZERO)));
+ vo.setYesterdayNewMember((long) memberMapper.selectCount(
+ new QueryWrapper<Member>().lambda()
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .ge(Member::getCreateDate, yesterdayStart)
+ .le(Member::getCreateDate, yesterdayEnd)));
+ vo.setTodayNewMember((long) memberMapper.selectCount(
+ new QueryWrapper<Member>().lambda()
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .ge(Member::getCreateDate, todayStart)));
+ return vo;
+ }
+
+ /**
+ * 缁熻鎸囧畾鏃舵鍐呭凡鏀粯璁㈠崟鏁伴噺(payStatus=1)銆�
+ *
+ * @param start 璧峰鏃堕棿(鍚�)
+ * @param end 缁撴潫鏃堕棿(鍚�)
+ * @return 宸叉敮浠樿鍗曟暟
+ */
+ private long countPaidOrders(Date start, Date end) {
+ return goodsorderMapper.selectCount(
+ new QueryWrapper<Goodsorder>().lambda()
+ .eq(Goodsorder::getPayStatus, Constants.ONE)
+ .eq(Goodsorder::getIsdeleted, Constants.ZERO)
+ .ge(Goodsorder::getPayDate, start)
+ .le(Goodsorder::getPayDate, end));
+ }
+
+ /**
+ * 鍙栨湰鏈�/鏈勾绗竴澶╃殑鏃堕棿鍘熷��(鏃跺垎绉掑綊闆跺墠鐨勬绉�),鐢ㄤ簬鏋勯�犲尯闂磋捣濮嬨��
+ * <p>鐢� Calendar 鎶娿�屽綋鏈�1鏃� 00:00:00.000銆嶆垨銆屽綋骞�1鏈�1鏃� 00:00:00.000銆嶅彇鍑恒��
+ *
+ * @param cal 鏃ュ巻(浼氳淇敼)
+ * @param field Calendar.MONTH 鏈湀绗竴澶� / Calendar.YEAR 鏈勾绗竴澶�
+ * @return 鍖洪棿璧峰鏃堕棿
+ */
+ private Date getFirstMs(Calendar cal, int field) {
+ cal.setTime(new Date());
+ if (field == Calendar.MONTH) {
+ cal.set(Calendar.DAY_OF_MONTH, 1);
+ } else {
+ cal.set(Calendar.MONTH, Calendar.JANUARY);
+ cal.set(Calendar.DAY_OF_MONTH, 1);
+ }
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal.getTime();
+ }
+}
--
Gitblit v1.9.3