feat: add vehicle mileage statistics

This commit is contained in:
lingniu
2026-07-14 13:18:52 +08:00
parent bb59303a4b
commit 6cddc0a43d
15 changed files with 522 additions and 1 deletions

View File

@@ -474,6 +474,83 @@ func buildMileageSummarySQL(query url.Values) SQLQuery {
}
}
func buildMileageStatisticsWhere(query url.Values) (string, []any) {
where := []string{"1 = 1"}
args := []any{}
if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
where = append(where, "(m.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
like := "%" + vin + "%"
args = append(args, like, like, like, like)
}
if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
where = append(where, "m.protocol = ?")
args = append(args, protocol)
}
if dateFrom := strings.TrimSpace(query.Get("dateFrom")); dateFrom != "" {
where = append(where, "m.stat_date >= ?")
args = append(args, dateFrom)
}
if dateTo := strings.TrimSpace(query.Get("dateTo")); dateTo != "" {
where = append(where, "m.stat_date <= ?")
args = append(args, dateTo)
}
return strings.Join(where, " AND "), args
}
func buildMileageStatisticsBaseSQL(query url.Values) (string, []any) {
where, args := buildMileageStatisticsWhere(query)
return `SELECT m.vin, COALESCE(MAX(NULLIF(b.plate, '')), '') AS plate, m.stat_date, ` +
`MAX(COALESCE(m.daily_mileage_km, 0)) AS daily_mileage_km, ` +
`MAX(COALESCE(m.latest_total_mileage_km, 0)) AS latest_mileage_km ` +
`FROM vehicle_daily_mileage m LEFT JOIN vehicle_identity_binding b ON b.vin = m.vin ` +
`WHERE ` + where + ` GROUP BY m.vin, m.stat_date`, args
}
func buildMileageStatisticsSummarySQL(query url.Values) SQLQuery {
base, args := buildMileageStatisticsBaseSQL(query)
return SQLQuery{Text: `SELECT COUNT(DISTINCT d.vin), COUNT(*), COALESCE(SUM(d.daily_mileage_km), 0), ` +
`COALESCE(AVG(d.daily_mileage_km), 0) FROM (` + base + `) d`, Args: args}
}
func buildMileageStatisticsTrendSQL(query url.Values) SQLQuery {
base, args := buildMileageStatisticsBaseSQL(query)
return SQLQuery{Text: `SELECT DATE_FORMAT(d.stat_date, '%Y-%m-%d'), COALESCE(SUM(d.daily_mileage_km), 0), ` +
`COUNT(DISTINCT d.vin) FROM (` + base + `) d GROUP BY d.stat_date ORDER BY d.stat_date ASC`, Args: args}
}
func buildMileageStatisticsRankingSQL(query url.Values) SQLQuery {
base, args := buildMileageStatisticsBaseSQL(query)
return SQLQuery{Text: `SELECT d.vin, COALESCE(MAX(d.plate), ''), COALESCE(SUM(d.daily_mileage_km), 0), ` +
`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(d.latest_mileage_km AS CHAR) ORDER BY d.stat_date DESC), ',', 1) AS DECIMAL(18,3)), 0), ` +
`COUNT(DISTINCT d.stat_date) FROM (` + base + `) d ` +
`GROUP BY d.vin ORDER BY SUM(d.daily_mileage_km) DESC, d.vin ASC LIMIT 20`, Args: args}
}
func buildMileageStatisticsSourceCountSQL(query url.Values) SQLQuery {
where, args := buildMileageStatisticsWhere(query)
return SQLQuery{Text: `SELECT COUNT(DISTINCT m.protocol) FROM vehicle_daily_mileage m ` +
`LEFT JOIN vehicle_identity_binding b ON b.vin = m.vin WHERE ` + where, Args: args}
}
func buildFleetLatestMileageSQL(query url.Values) SQLQuery {
where := []string{"l.total_mileage_km IS NOT NULL", "l.total_mileage_km >= 0"}
args := []any{}
if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
where = append(where, "(l.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
like := "%" + vin + "%"
args = append(args, like, like, like, like)
}
if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
where = append(where, "l.protocol = ?")
args = append(args, protocol)
}
inner := `SELECT l.vin, CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.total_mileage_km AS CHAR) ` +
`ORDER BY l.updated_at DESC, l.protocol ASC), ',', 1) AS DECIMAL(18,3)) AS latest_mileage_km ` +
`FROM vehicle_realtime_location l LEFT JOIN vehicle_identity_binding b ON b.vin = l.vin WHERE ` +
strings.Join(where, " AND ") + ` GROUP BY l.vin`
return SQLQuery{Text: `SELECT COALESCE(SUM(x.latest_mileage_km), 0) FROM (` + inner + `) x`, Args: args}
}
func buildLimitOffset(query url.Values) (int, int) {
return parsePositive(query.Get("limit"), 20), parsePositive(query.Get("offset"), 0)
}