@@ -0,0 +1,564 @@
package com.zbkj.service.service.impl ;
import cn.hutool.core.date.DateTime ;
import cn.hutool.core.date.DateUtil ;
import cn.hutool.core.util.StrUtil ;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper ;
import com.zbkj.common.model.consignment.WaMerchandise ;
import com.zbkj.common.model.consignment.WaOrder ;
import com.zbkj.common.model.consignment.WaSelfbonusLog ;
import com.zbkj.common.model.consignment.WaSharebonusLog ;
import com.zbkj.common.model.consignment.WaUsers ;
import com.zbkj.common.model.consignment.WaWithdraw ;
import com.zbkj.common.response.dashboard.BossDashboardResponse ;
import com.zbkj.service.dao.consignment.WaMerchandiseDao ;
import com.zbkj.service.dao.consignment.WaOrderDao ;
import com.zbkj.service.dao.consignment.WaSelfbonusLogDao ;
import com.zbkj.service.dao.consignment.WaSharebonusLogDao ;
import com.zbkj.service.dao.consignment.WaUsersDao ;
import com.zbkj.service.dao.consignment.WaWithdrawDao ;
import com.zbkj.service.service.BossDashboardService ;
import org.springframework.stereotype.Service ;
import javax.annotation.Resource ;
import java.math.BigDecimal ;
import java.math.RoundingMode ;
import java.util.Calendar ;
import java.util.Date ;
import java.util.List ;
import java.util.Map ;
/**
* 老板经营驾驶舱服务实现
*/
@Service
public class BossDashboardServiceImpl implements BossDashboardService {
@Resource
private WaOrderDao waOrderDao ;
@Resource
private WaMerchandiseDao waMerchandiseDao ;
@Resource
private WaUsersDao waUsersDao ;
@Resource
private WaSelfbonusLogDao waSelfbonusLogDao ;
@Resource
private WaSharebonusLogDao waSharebonusLogDao ;
@Resource
private WaWithdrawDao waWithdrawDao ;
@Override
public BossDashboardResponse overview ( String date ) {
DateTime businessDate = StrUtil . isBlank ( date ) ? previousWorkday ( DateUtil . date ( ) ) : DateUtil . parseDate ( date ) ;
DateTime previousDate = previousWorkday ( businessDate ) ;
DateRange businessRange = dayRange ( businessDate ) ;
DateRange previousRange = dayRange ( previousDate ) ;
DailyMetrics metrics = buildDailyMetrics ( businessRange ) ;
DailyMetrics previousMetrics = buildDailyMetrics ( previousRange ) ;
BossDashboardResponse response = new BossDashboardResponse ( ) ;
response . setBusinessDate ( businessDate . toString ( " yyyy-MM-dd " ) ) ;
response . setGeneratedAt ( DateUtil . formatDateTime ( new Date ( ) ) ) ;
response . setSummary ( buildSummary ( metrics ) ) ;
response . getKpis ( ) . add ( metric ( " dealAmount " , " 上个工作日成交额 " , metrics . dealAmount , " 元 " , " 较上一工作日 " , ratio ( metrics . dealAmount , previousMetrics . dealAmount ) , statusByRatio ( metrics . dealAmount , previousMetrics . dealAmount ) , true ) ) ;
response . getKpis ( ) . add ( metric ( " orderCount " , " 上个工作日订单数 " , metrics . orderCount , " 单 " , " 较上一工作日 " , ratio ( metrics . orderCount , previousMetrics . orderCount ) , statusByRatio ( metrics . orderCount , previousMetrics . orderCount ) , false ) ) ;
response . getKpis ( ) . add ( metric ( " purchaseUsers " , " 采购用户 " , metrics . purchaseUsers , " 人 " , " 较上一工作日 " , ratio ( metrics . purchaseUsers , previousMetrics . purchaseUsers ) , statusByRatio ( metrics . purchaseUsers , previousMetrics . purchaseUsers ) , false ) ) ;
response . getKpis ( ) . add ( metric ( " newUsers " , " 新增用户 " , metrics . newUsers , " 人 " , " 较上一工作日 " , ratio ( metrics . newUsers , previousMetrics . newUsers ) , statusByRatio ( metrics . newUsers , previousMetrics . newUsers ) , false ) ) ;
response . getKpis ( ) . add ( metric ( " newMerchandise " , " 新增寄售商品 " , metrics . newMerchandiseCount , " 件 " , " 较上一工作日 " , ratio ( metrics . newMerchandiseCount , previousMetrics . newMerchandiseCount ) , statusByRatio ( metrics . newMerchandiseCount , previousMetrics . newMerchandiseCount ) , false ) ) ;
response . getKpis ( ) . add ( metric ( " selfBonus " , " 个人奖金发放 " , metrics . selfBonus , " 元 " , " 较上一工作日 " , ratio ( metrics . selfBonus , previousMetrics . selfBonus ) , " normal " , false ) ) ;
response . getKpis ( ) . add ( metric ( " shareBonus " , " 推广奖金发放 " , metrics . shareBonus , " 元 " , " 较上一工作日 " , ratio ( metrics . shareBonus , previousMetrics . shareBonus ) , " normal " , false ) ) ;
response . getKpis ( ) . add ( metric ( " pendingAmount " , " 待支付/待结算 " , metrics . pendingAmount , " 元 " , " 需关注 " , null , metrics . pendingAmount . compareTo ( BigDecimal . ZERO ) > 0 ? " warning " : " normal " , false ) ) ;
buildFundPool ( response ) ;
buildSnapshots ( response ) ;
buildTrends ( response , businessDate ) ;
buildRanks ( response ) ;
buildRisks ( response ) ;
return response ;
}
@Override
public String dailyReportArchiveHtml ( String date ) {
BossDashboardResponse data = overview ( date ) ;
StringBuilder html = new StringBuilder ( ) ;
html . append ( " <!doctype html><html lang= \" zh-CN \" ><head><meta charset= \" utf-8 \" > " ) ;
html . append ( " <meta name= \" viewport \" content= \" width=device-width,initial-scale=1 \" > " ) ;
html . append ( " <title>经营日报归档 - " ) . append ( escape ( data . getBusinessDate ( ) ) ) . append ( " </title> " ) ;
html . append ( " <style> " ) ;
html . append ( " :root{color:#132033;background:#fff6f1;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at top left,rgba(255,91,54,.18),transparent 30rem),#fff6f1}.page{max-width:820px;margin:0 auto;padding:28px 18px 40px}.hero{color:#fff;padding:26px;border-radius:0 0 28px 28px;background:linear-gradient(145deg,#ff5b36,#ff8b52),radial-gradient(circle at 90% 10%,rgba(255,176,0,.42),transparent 18rem);box-shadow:0 16px 40px rgba(255,91,54,.14)}.eyebrow{margin:0;color:rgba(255,255,255,.76);font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.hero h1{margin:12px 0 8px;font-size:32px;line-height:1.1}.hero p{margin:0;color:rgba(255,255,255,.82);line-height:1.7}.meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}.meta span{padding:7px 12px;border-radius:999px;background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.2);font-size:12px;font-weight:700}.section{margin-top:16px;padding:18px;background:#fff;border:1px solid rgba(19,32,51,.08);border-radius:24px;box-shadow:0 10px 28px rgba(22,47,80,.08)}.section h2{margin:0 0 14px;font-size:20px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.card{padding:14px;border-radius:18px;background:#f6f9fb}.card small{display:block;color:#6b7a90}.card strong{display:block;margin-top:6px;font-size:22px}.list{display:grid;gap:10px}.item{padding:12px;border-radius:16px;background:#f6f9fb}.item strong{display:block}.item small,.item p{color:#6b7a90}.risk-red strong{color:#dc2626}.risk-yellow strong{color:#ffb000}.footer{margin-top:18px;color:#6b7a90;font-size:12px;text-align:center}@media(max-width:560px){.grid{grid-template-columns:1fr}.hero h1{font-size:28px}} " ) ;
html . append ( " </style></head><body><main class= \" page \" > " ) ;
html . append ( " <header class= \" hero \" ><p class= \" eyebrow \" >Daily Report Archive</p><h1>经营日报归档</h1> " ) ;
html . append ( " <p> " ) . append ( escape ( data . getSummary ( ) ) ) . append ( " </p><div class= \" meta \" > " ) ;
html . append ( " <span>数据日期: " ) . append ( escape ( data . getBusinessDate ( ) ) ) . append ( " </span> " ) ;
html . append ( " <span>生成时间: " ) . append ( escape ( data . getGeneratedAt ( ) ) ) . append ( " </span> " ) ;
html . append ( " <span>归档类型: Standalone HTML</span></div></header> " ) ;
appendMetricsSection ( html , " 核心经营指标 " , data . getKpis ( ) ) ;
appendTrendSection ( html , data ) ;
appendMetricsSection ( html , " 资金池摘要 " , data . getFundPool ( ) ) ;
appendRankSection ( html , " 高价值用户 " , data . getUserRanks ( ) ) ;
appendRankSection ( html , " 团队贡献排行 " , data . getTeamRanks ( ) ) ;
appendRankSection ( html , " 高货值未成交商品 " , data . getProductRanks ( ) ) ;
appendRiskSection ( html , data ) ;
html . append ( " <p class= \" footer \" >本归档由经营驾驶舱实时数据生成,可独立保存和打开。</p> " ) ;
html . append ( " </main></body></html> " ) ;
return html . toString ( ) ;
}
private DailyMetrics buildDailyMetrics ( DateRange range ) {
DailyMetrics metrics = new DailyMetrics ( ) ;
metrics . dealAmount = sumOrderAmount ( range . start , range . end , true , null ) ;
metrics . orderCount = countOrders ( range . start , range . end , null ) ;
metrics . purchaseUsers = distinctBuyerCount ( range . start , range . end , null ) ;
metrics . newUsers = countUsers ( range . start , range . end ) ;
metrics . newMerchandiseCount = countMerchandise ( range . start , range . end ) ;
metrics . selfBonus = sumSelfBonus ( range . start , range . end ) ;
metrics . shareBonus = sumShareBonus ( range . start , range . end ) ;
metrics . pendingAmount = sumOrderAmount ( range . start , range . end , false , null ) ;
return metrics ;
}
private void buildFundPool ( BossDashboardResponse response ) {
BigDecimal money = sumUsersDecimal ( " money " ) ;
BigDecimal coupon = sumUsersDecimal ( " coupon " ) ;
BigDecimal selfBonus = sumUsersDecimal ( " self_bonus " ) ;
BigDecimal shareBonus = sumUsersDecimal ( " share_bonus " ) ;
BigDecimal score = sumUsersDecimal ( " score " ) ;
BigDecimal pendingWithdraw = sumWithdrawAmount ( 0 ) ;
Integer pendingWithdrawCount = countWithdraw ( 0 ) ;
response . getFundPool ( ) . add ( metric ( " balance " , " 余额总额 " , money , " 元 " , null , null , " normal " , false ) ) ;
response . getFundPool ( ) . add ( metric ( " coupon " , " 优惠券总额 " , coupon , " 元 " , null , null , " normal " , false ) ) ;
response . getFundPool ( ) . add ( metric ( " selfBonusPool " , " 个人奖金总额 " , selfBonus , " 元 " , null , null , selfBonus . compareTo ( BigDecimal . ZERO ) > 0 ? " warning " : " normal " , false ) ) ;
response . getFundPool ( ) . add ( metric ( " shareBonusPool " , " 推广奖金总额 " , shareBonus , " 元 " , null , null , " normal " , false ) ) ;
response . getFundPool ( ) . add ( metric ( " integral " , " 积分总额 " , score , " 分 " , null , null , " normal " , false ) ) ;
response . getFundPool ( ) . add ( metric ( " withdrawPending " , " 待审核提现 " , pendingWithdraw , " 元 " , pendingWithdrawCount + " 笔 " , null , pendingWithdrawCount > 0 ? " danger " : " normal " , false ) ) ;
}
private void buildSnapshots ( BossDashboardResponse response ) {
DateTime today = DateUtil . date ( ) ;
DateRange morningRange = range ( today , " 00:00:00 " , " 10:15:59 " ) ;
DateRange afternoonRange = range ( today , " 10:16:00 " , " 14:55:59 " ) ;
DailyMetrics morningMetrics = buildDailyMetrics ( morningRange ) ;
BossDashboardResponse . TodaySnapshot morning = snapshot ( " 1015 " , " 10:15 上午快报 " , morningRange . end , " 上午抢购节点已完成,上一日寄卖商品消化情况请关注成交额、付款和采购用户。 " , morningMetrics , " success " ) ;
response . getSnapshots ( ) . add ( morning ) ;
String afternoonStatus = new Date ( ) . after ( afternoonRange . end ) ? " success " : " pending " ;
String afternoonMessage = " success " . equals ( afternoonStatus )
? " 下午寄卖/转卖节点已完成,请关注用户抢购商品的再次上架与转卖承接。 "
: " 下午寄卖/转卖节点尚未生成,预计 14:55 后可查看用户抢购商品的再次上架情况。 " ;
DailyMetrics afternoonMetrics = " success " . equals ( afternoonStatus ) ? buildDailyMetrics ( afternoonRange ) : new DailyMetrics ( ) ;
response . getSnapshots ( ) . add ( snapshot ( " 1455 " , " 14:55 下午快报 " , afternoonRange . end , afternoonMessage , afternoonMetrics , afternoonStatus ) ) ;
}
private void buildTrends ( BossDashboardResponse response , DateTime businessDate ) {
for ( int i = 6 ; i > = 0 ; i - - ) {
DateTime date = DateUtil . offsetDay ( businessDate , - i ) ;
DailyMetrics metrics = buildDailyMetrics ( dayRange ( date ) ) ;
BossDashboardResponse . TrendPoint point = new BossDashboardResponse . TrendPoint ( ) ;
point . setDate ( date . toString ( " MM-dd " ) ) ;
point . setAmount ( metrics . dealAmount ) ;
point . setOrders ( metrics . orderCount ) ;
point . setNewUsers ( metrics . newUsers ) ;
point . setBonus ( metrics . selfBonus . add ( metrics . shareBonus ) ) ;
response . getTrends ( ) . add ( point ) ;
}
}
private void buildRanks ( BossDashboardResponse response ) {
QueryWrapper < WaUsers > userWrapper = new QueryWrapper < WaUsers > ( )
. select ( " id " , " nickname " , " mobile " , " self_bonus " , " share_bonus " , " coupon " , " score " )
. orderByDesc ( " IFNULL(self_bonus,0) + IFNULL(share_bonus,0) + IFNULL(coupon,0) " )
. last ( " limit 3 " ) ;
List < WaUsers > users = waUsersDao . selectList ( userWrapper ) ;
for ( int i = 0 ; i < users . size ( ) ; i + + ) {
WaUsers user = users . get ( i ) ;
BigDecimal value = defaultDecimal ( user . getSelfBonus ( ) ) . add ( defaultDecimal ( user . getShareBonus ( ) ) ) . add ( defaultDecimal ( user . getCoupon ( ) ) ) ;
response . getUserRanks ( ) . add ( rank ( " u " + user . getId ( ) , displayName ( user ) , value , maskMobile ( user . getMobile ( ) ) , i = = 0 ? " 高价值 " : null ) ) ;
}
QueryWrapper < WaUsers > teamWrapper = new QueryWrapper < WaUsers > ( )
. select ( " pid as id " , " COUNT(id) as memberCount " , " IFNULL(SUM(self_bonus),0) as selfBonus " , " IFNULL(SUM(share_bonus),0) as shareBonus " )
. isNotNull ( " pid " )
. gt ( " pid " , 0 )
. groupBy ( " pid " )
. orderByDesc ( " IFNULL(SUM(self_bonus),0) + IFNULL(SUM(share_bonus),0) " )
. last ( " limit 3 " ) ;
List < Map < String , Object > > teams = waUsersDao . selectMaps ( teamWrapper ) ;
for ( int i = 0 ; i < teams . size ( ) ; i + + ) {
Map < String , Object > team = teams . get ( i ) ;
BigDecimal selfBonus = decimal ( team . get ( " selfBonus " ) ) ;
BigDecimal shareBonus = decimal ( team . get ( " shareBonus " ) ) ;
String leaderId = stringValue ( team . get ( " id " ) ) ;
WaUsers leader = waUsersDao . selectById ( leaderId ) ;
String leaderName = leader = = null ? " 团队 " + leaderId : displayName ( leader ) ;
response . getTeamRanks ( ) . add ( rank ( " t " + leaderId , leaderName , selfBonus . add ( shareBonus ) , " 成员 " + intValue ( team . get ( " memberCount " ) ) + " 人 " , i = = 0 ? " TOP1 " : null ) ) ;
}
QueryWrapper < WaMerchandise > productWrapper = new QueryWrapper < WaMerchandise > ( )
. select ( " id " , " title " , " price " , " created_at " )
. eq ( " status " , 1 )
. orderByDesc ( " price " )
. last ( " limit 3 " ) ;
List < WaMerchandise > products = waMerchandiseDao . selectList ( productWrapper ) ;
for ( int i = 0 ; i < products . size ( ) ; i + + ) {
WaMerchandise product = products . get ( i ) ;
response . getProductRanks ( ) . add ( rank ( " p " + product . getId ( ) , StrUtil . isBlank ( product . getTitle ( ) ) ? " 未命名商品 " : product . getTitle ( ) , defaultDecimal ( product . getPrice ( ) ) , " 高货值待成交 " , i = = 0 ? " 滞销 " : null ) ) ;
}
}
private void buildRisks ( BossDashboardResponse response ) {
BigDecimal pendingWithdraw = sumWithdrawAmount ( 0 ) ;
if ( pendingWithdraw . compareTo ( BigDecimal . ZERO ) > 0 ) {
response . getRisks ( ) . add ( risk ( " r1 " , " red " , " 资金 " , " 待审核提现 " , " 当前待审核提现 " + pendingWithdraw + " 元,建议今日处理。 " ) ) ;
}
Integer pendingOrders = countPendingOrders ( ) ;
if ( pendingOrders > 0 ) {
response . getRisks ( ) . add ( risk ( " r2 " , " yellow " , " 订单 " , " 待支付订单未处理 " , " 当前存在 " + pendingOrders + " 笔待支付订单,请关注付款转化。 " ) ) ;
}
Integer hiddenProducts = countHiddenMerchandise ( ) ;
if ( hiddenProducts > 0 ) {
response . getRisks ( ) . add ( risk ( " r3 " , " gray " , " 商品 " , " 隐藏寄售商品 " , " 当前存在 " + hiddenProducts + " 个隐藏寄售商品,可按需核查。 " ) ) ;
}
}
private BossDashboardResponse . KpiMetric metric ( String key , String title , Object value , String unit , String trendLabel , BigDecimal trendValue , String status , Boolean featured ) {
BossDashboardResponse . KpiMetric metric = new BossDashboardResponse . KpiMetric ( ) ;
metric . setKey ( key ) ;
metric . setTitle ( title ) ;
metric . setValue ( value ) ;
metric . setUnit ( unit ) ;
metric . setTrendLabel ( trendLabel ) ;
metric . setTrendValue ( trendValue ) ;
metric . setStatus ( status ) ;
metric . setFeatured ( featured ) ;
return metric ;
}
private BossDashboardResponse . TodaySnapshot snapshot ( String slot , String title , Date generatedAt , String message , DailyMetrics metrics , String status ) {
BossDashboardResponse . TodaySnapshot snapshot = new BossDashboardResponse . TodaySnapshot ( ) ;
snapshot . setSlot ( slot ) ;
snapshot . setTitle ( title ) ;
snapshot . setStatus ( status ) ;
snapshot . setGeneratedAt ( DateUtil . formatDateTime ( generatedAt ) ) ;
snapshot . setMessage ( message ) ;
snapshot . setPurchaseUsers ( metrics . purchaseUsers ) ;
snapshot . setOrderCount ( metrics . orderCount ) ;
snapshot . setDealAmount ( metrics . dealAmount ) ;
snapshot . setPaidAmount ( metrics . dealAmount ) ;
snapshot . setNewMerchandiseCount ( metrics . newMerchandiseCount ) ;
snapshot . setSelfBonusChange ( metrics . selfBonus ) ;
snapshot . setShareBonusChange ( metrics . shareBonus ) ;
return snapshot ;
}
private BossDashboardResponse . RankItem rank ( String id , String name , BigDecimal value , String description , String badge ) {
BossDashboardResponse . RankItem rank = new BossDashboardResponse . RankItem ( ) ;
rank . setId ( id ) ;
rank . setName ( name ) ;
rank . setValue ( value ) ;
rank . setDescription ( description ) ;
rank . setBadge ( badge ) ;
return rank ;
}
private BossDashboardResponse . RiskAlert risk ( String id , String level , String type , String title , String description ) {
BossDashboardResponse . RiskAlert risk = new BossDashboardResponse . RiskAlert ( ) ;
risk . setId ( id ) ;
risk . setLevel ( level ) ;
risk . setType ( type ) ;
risk . setTitle ( title ) ;
risk . setDescription ( description ) ;
risk . setDiscoveredAt ( DateUtil . format ( new Date ( ) , " HH:mm " ) ) ;
return risk ;
}
private void appendMetricsSection ( StringBuilder html , String title , List < BossDashboardResponse . KpiMetric > metrics ) {
html . append ( " <section class= \" section \" ><h2> " ) . append ( escape ( title ) ) . append ( " </h2><div class= \" grid \" > " ) ;
for ( BossDashboardResponse . KpiMetric metric : metrics ) {
html . append ( " <article class= \" card \" ><small> " ) . append ( escape ( metric . getTitle ( ) ) ) . append ( " </small> " ) ;
html . append ( " <strong> " ) . append ( formatMetric ( metric . getValue ( ) , metric . getUnit ( ) ) ) . append ( " </strong> " ) ;
if ( StrUtil . isNotBlank ( metric . getTrendLabel ( ) ) | | metric . getTrendValue ( ) ! = null ) {
html . append ( " <small> " ) . append ( escape ( metric . getTrendLabel ( ) ) ) ;
if ( metric . getTrendValue ( ) ! = null ) {
html . append ( " " ) . append ( metric . getTrendValue ( ) ) . append ( " % " ) ;
}
html . append ( " </small> " ) ;
}
html . append ( " </article> " ) ;
}
html . append ( " </div></section> " ) ;
}
private void appendTrendSection ( StringBuilder html , BossDashboardResponse data ) {
html . append ( " <section class= \" section \" ><h2>最近 7 天趋势</h2><div class= \" list \" > " ) ;
for ( BossDashboardResponse . TrendPoint point : data . getTrends ( ) ) {
html . append ( " <div class= \" item \" ><strong> " ) . append ( escape ( point . getDate ( ) ) ) . append ( " : " ) . append ( formatMoney ( point . getAmount ( ) ) ) . append ( " </strong> " ) ;
html . append ( " <small> " ) . append ( point . getOrders ( ) ) . append ( " 单 / 新增用户 " ) . append ( point . getNewUsers ( ) ) . append ( " / 奖金 " ) . append ( formatMoney ( point . getBonus ( ) ) ) . append ( " </small></div> " ) ;
}
html . append ( " </div></section> " ) ;
}
private void appendRankSection ( StringBuilder html , String title , List < BossDashboardResponse . RankItem > ranks ) {
html . append ( " <section class= \" section \" ><h2> " ) . append ( escape ( title ) ) . append ( " </h2><div class= \" list \" > " ) ;
if ( ranks . isEmpty ( ) ) {
html . append ( " <div class= \" item \" ><strong>暂无数据</strong><small>当前实时数据未生成该排行。</small></div> " ) ;
}
for ( int i = 0 ; i < ranks . size ( ) ; i + + ) {
BossDashboardResponse . RankItem rank = ranks . get ( i ) ;
html . append ( " <div class= \" item \" ><strong> " ) . append ( i + 1 ) . append ( " . " ) . append ( escape ( rank . getName ( ) ) ) . append ( " · " ) . append ( formatMoney ( rank . getValue ( ) ) ) . append ( " </strong> " ) ;
html . append ( " <small> " ) . append ( escape ( rank . getDescription ( ) ) ) ;
if ( StrUtil . isNotBlank ( rank . getBadge ( ) ) ) {
html . append ( " / " ) . append ( escape ( rank . getBadge ( ) ) ) ;
}
html . append ( " </small></div> " ) ;
}
html . append ( " </div></section> " ) ;
}
private void appendRiskSection ( StringBuilder html , BossDashboardResponse data ) {
html . append ( " <section class= \" section \" ><h2>风险预警</h2><div class= \" list \" > " ) ;
if ( data . getRisks ( ) . isEmpty ( ) ) {
html . append ( " <div class= \" item \" ><strong>暂无风险</strong><small>当前实时数据未触发风险预警。</small></div> " ) ;
}
for ( BossDashboardResponse . RiskAlert risk : data . getRisks ( ) ) {
html . append ( " <div class= \" item risk- " ) . append ( escape ( risk . getLevel ( ) ) ) . append ( " \" ><strong> " ) ;
html . append ( escape ( risk . getType ( ) ) ) . append ( " / " ) . append ( escape ( risk . getTitle ( ) ) ) . append ( " </strong> " ) ;
html . append ( " <p> " ) . append ( escape ( risk . getDescription ( ) ) ) . append ( " </p> " ) ;
html . append ( " <small>发现时间: " ) . append ( escape ( risk . getDiscoveredAt ( ) ) ) . append ( " </small></div> " ) ;
}
html . append ( " </div></section> " ) ;
}
private BigDecimal sumOrderAmount ( Date start , Date end , Boolean paidOnly , Boolean isResell ) {
QueryWrapper < WaOrder > wrapper = new QueryWrapper < WaOrder > ( ) . select ( " IFNULL(SUM(total_money),0) as total " ) . between ( " buy_time " , start , end ) ;
wrapper . eq ( " is_cancel " , 0 ) ;
if ( Boolean . TRUE . equals ( paidOnly ) ) {
wrapper . ge ( " status " , 1 ) ;
} else if ( Boolean . FALSE . equals ( paidOnly ) ) {
wrapper . eq ( " status " , 0 ) ;
}
if ( isResell ! = null ) {
wrapper . eq ( " is_resell " , isResell ? 1 : 0 ) ;
}
return aggregateDecimal ( waOrderDao . selectMaps ( wrapper ) , " total " ) ;
}
private Integer countOrders ( Date start , Date end , Boolean isResell ) {
QueryWrapper < WaOrder > wrapper = new QueryWrapper < WaOrder > ( ) . between ( " buy_time " , start , end ) . eq ( " is_cancel " , 0 ) ;
if ( isResell ! = null ) {
wrapper . eq ( " is_resell " , isResell ? 1 : 0 ) ;
}
return waOrderDao . selectCount ( wrapper ) ;
}
private Integer distinctBuyerCount ( Date start , Date end , Boolean isResell ) {
QueryWrapper < WaOrder > wrapper = new QueryWrapper < WaOrder > ( ) . select ( " COUNT(DISTINCT buyer_id) as total " ) . between ( " buy_time " , start , end ) . eq ( " is_cancel " , 0 ) ;
if ( isResell ! = null ) {
wrapper . eq ( " is_resell " , isResell ? 1 : 0 ) ;
}
return aggregateInt ( waOrderDao . selectMaps ( wrapper ) , " total " ) ;
}
private Integer countUsers ( Date start , Date end ) {
return waUsersDao . selectCount ( new QueryWrapper < WaUsers > ( ) . between ( " join_time " , start , end ) ) ;
}
private Integer countMerchandise ( Date start , Date end ) {
return waMerchandiseDao . selectCount ( new QueryWrapper < WaMerchandise > ( ) . between ( " created_at " , start , end ) ) ;
}
private BigDecimal sumSelfBonus ( Date start , Date end ) {
QueryWrapper < WaSelfbonusLog > wrapper = new QueryWrapper < WaSelfbonusLog > ( ) . select ( " IFNULL(SUM(money),0) as total " ) . eq ( " type " , 1 ) . between ( " created_at " , start , end ) ;
return aggregateDecimal ( waSelfbonusLogDao . selectMaps ( wrapper ) , " total " ) ;
}
private BigDecimal sumShareBonus ( Date start , Date end ) {
QueryWrapper < WaSharebonusLog > wrapper = new QueryWrapper < WaSharebonusLog > ( ) . select ( " IFNULL(SUM(money),0) as total " ) . eq ( " type " , 1 ) . between ( " created_at " , start , end ) ;
return aggregateDecimal ( waSharebonusLogDao . selectMaps ( wrapper ) , " total " ) ;
}
private BigDecimal sumUsersDecimal ( String column ) {
QueryWrapper < WaUsers > wrapper = new QueryWrapper < WaUsers > ( ) . select ( " IFNULL(SUM( " + column + " ),0) as total " ) ;
return aggregateDecimal ( waUsersDao . selectMaps ( wrapper ) , " total " ) ;
}
private BigDecimal sumWithdrawAmount ( Integer status ) {
QueryWrapper < WaWithdraw > wrapper = new QueryWrapper < WaWithdraw > ( ) . select ( " IFNULL(SUM(money),0) as total " ) . eq ( " status " , status ) ;
return aggregateDecimal ( waWithdrawDao . selectMaps ( wrapper ) , " total " ) ;
}
private Integer countWithdraw ( Integer status ) {
return waWithdrawDao . selectCount ( new QueryWrapper < WaWithdraw > ( ) . eq ( " status " , status ) ) ;
}
private Integer countPendingOrders ( ) {
return waOrderDao . selectCount ( new QueryWrapper < WaOrder > ( ) . eq ( " status " , 0 ) . eq ( " is_cancel " , 0 ) ) ;
}
private Integer countHiddenMerchandise ( ) {
return waMerchandiseDao . selectCount ( new QueryWrapper < WaMerchandise > ( ) . eq ( " is_show " , 0 ) ) ;
}
private BigDecimal ratio ( BigDecimal current , BigDecimal previous ) {
if ( previous = = null | | previous . compareTo ( BigDecimal . ZERO ) = = 0 ) {
return current ! = null & & current . compareTo ( BigDecimal . ZERO ) > 0 ? BigDecimal . valueOf ( 100 ) : BigDecimal . ZERO ;
}
return current . subtract ( previous ) . multiply ( BigDecimal . valueOf ( 100 ) ) . divide ( previous , 1 , RoundingMode . HALF_UP ) ;
}
private BigDecimal ratio ( Integer current , Integer previous ) {
return ratio ( BigDecimal . valueOf ( current = = null ? 0 : current ) , BigDecimal . valueOf ( previous = = null ? 0 : previous ) ) ;
}
private String statusByRatio ( BigDecimal current , BigDecimal previous ) {
BigDecimal value = ratio ( current , previous ) ;
return value . compareTo ( BigDecimal . ZERO ) > = 0 ? " success " : " warning " ;
}
private String statusByRatio ( Integer current , Integer previous ) {
return statusByRatio ( BigDecimal . valueOf ( current = = null ? 0 : current ) , BigDecimal . valueOf ( previous = = null ? 0 : previous ) ) ;
}
private String buildSummary ( DailyMetrics metrics ) {
if ( metrics . dealAmount . compareTo ( BigDecimal . ZERO ) = = 0 & & metrics . orderCount = = 0 ) {
return " 当前日期暂无成交数据,请关注抢购与寄卖节点是否正常生成。 " ;
}
return " 上个工作日成交 " + metrics . dealAmount + " 元,订单 " + metrics . orderCount + " 单,采购用户 " + metrics . purchaseUsers + " 人;请重点关注待支付、提现和寄售供给。 " ;
}
private DateRange dayRange ( DateTime date ) {
return new DateRange ( DateUtil . beginOfDay ( date ) , DateUtil . endOfDay ( date ) ) ;
}
private DateTime previousWorkday ( DateTime referenceDate ) {
DateTime date = DateUtil . offsetDay ( referenceDate , - 1 ) ;
while ( isWeekend ( date ) ) {
date = DateUtil . offsetDay ( date , - 1 ) ;
}
return date ;
}
private boolean isWeekend ( DateTime date ) {
int dayOfWeek = DateUtil . dayOfWeek ( date ) ;
return dayOfWeek = = Calendar . SATURDAY | | dayOfWeek = = Calendar . SUNDAY ;
}
private DateRange range ( DateTime date , String startTime , String endTime ) {
String day = date . toString ( " yyyy-MM-dd " ) ;
return new DateRange ( DateUtil . parse ( day + " " + startTime ) , DateUtil . parse ( day + " " + endTime ) ) ;
}
private BigDecimal aggregateDecimal ( List < Map < String , Object > > maps , String key ) {
if ( maps = = null | | maps . isEmpty ( ) ) {
return BigDecimal . ZERO ;
}
return decimal ( maps . get ( 0 ) . get ( key ) ) ;
}
private Integer aggregateInt ( List < Map < String , Object > > maps , String key ) {
if ( maps = = null | | maps . isEmpty ( ) ) {
return 0 ;
}
return intValue ( maps . get ( 0 ) . get ( key ) ) ;
}
private BigDecimal decimal ( Object value ) {
if ( value = = null ) {
return BigDecimal . ZERO ;
}
if ( value instanceof BigDecimal ) {
return ( BigDecimal ) value ;
}
return new BigDecimal ( String . valueOf ( value ) ) ;
}
private BigDecimal defaultDecimal ( BigDecimal value ) {
return value = = null ? BigDecimal . ZERO : value ;
}
private Integer intValue ( Object value ) {
if ( value = = null ) {
return 0 ;
}
if ( value instanceof Number ) {
return ( ( Number ) value ) . intValue ( ) ;
}
return Integer . parseInt ( String . valueOf ( value ) ) ;
}
private String stringValue ( Object value ) {
return value = = null ? " " : String . valueOf ( value ) ;
}
private String displayName ( WaUsers user ) {
if ( StrUtil . isNotBlank ( user . getNickname ( ) ) ) {
return user . getNickname ( ) ;
}
if ( StrUtil . isNotBlank ( user . getUsername ( ) ) ) {
return user . getUsername ( ) ;
}
return " 用户 " + user . getId ( ) ;
}
private String maskMobile ( String mobile ) {
if ( StrUtil . isBlank ( mobile ) | | mobile . length ( ) < 7 ) {
return " 手机号未完善 " ;
}
return mobile . substring ( 0 , 3 ) + " **** " + mobile . substring ( mobile . length ( ) - 4 ) ;
}
private String formatMetric ( Object value , String unit ) {
if ( " 元 " . equals ( unit ) ) {
return formatMoney ( decimal ( value ) ) ;
}
if ( value = = null ) {
return " -- " ;
}
return escape ( String . valueOf ( value ) ) + ( unit = = null ? " " : escape ( unit ) ) ;
}
private String formatMoney ( BigDecimal value ) {
return " ¥ " + defaultDecimal ( value ) . setScale ( 2 , RoundingMode . HALF_UP ) . toPlainString ( ) ;
}
private String escape ( String value ) {
if ( value = = null ) {
return " " ;
}
return value . replace ( " & " , " & " )
. replace ( " < " , " < " )
. replace ( " > " , " > " )
. replace ( " \" " , " " " )
. replace ( " ' " , " ' " ) ;
}
private static class DailyMetrics {
private BigDecimal dealAmount = BigDecimal . ZERO ;
private Integer orderCount = 0 ;
private Integer purchaseUsers = 0 ;
private Integer newUsers = 0 ;
private Integer newMerchandiseCount = 0 ;
private BigDecimal selfBonus = BigDecimal . ZERO ;
private BigDecimal shareBonus = BigDecimal . ZERO ;
private BigDecimal pendingAmount = BigDecimal . ZERO ;
}
private static class DateRange {
private Date start ;
private Date end ;
private DateRange ( Date start , Date end ) {
this . start = start ;
this . end = end ;
}
}
}