package platform import ( "context" "encoding/json" "math/big" "net/url" "regexp" "sort" "strconv" "strings" "time" ) const operationComplianceMenu = "operation-compliance" var operationMileageDecimal = regexp.MustCompile(`^[0-9]{1,15}(\.[0-9]{1,3})?$`) type OperationMileageTotal struct { VIN string Meters *int64 ValidDays int InvalidDays int } type OperationComplianceStore interface { OperationVehicleRoster(context.Context, url.Values) ([]CustomerVehicleAssignment, error) OperationAssignments(context.Context, string) ([]CustomerVehicleAssignment, time.Time, error) OperationMileageTotals(context.Context, url.Values) ([]OperationMileageTotal, error) OperationMileageDays(context.Context, url.Values) ([]OperationMileageDay, error) } type OperationComplianceRow struct { CustomerVehicleAssignment MileageKm *float64 `json:"mileageKm"` Status string `json:"status"` ValidDays int `json:"validDays"` ExpectedDays int `json:"expectedDays"` AuthorizedDays int `json:"authorizedDays"` InvalidDays int `json:"invalidDays"` MissingDays int `json:"missingDays"` Incomplete bool `json:"incomplete"` Reasons []string `json:"reasons"` } type OperationComplianceSummary struct { Total int `json:"total"` Passed int `json:"passed"` Failed int `json:"failed"` Insufficient int `json:"insufficient"` PassRate *float64 `json:"passRate"` } type OperationComplianceOptions struct { Customers []string `json:"customers"` Departments []string `json:"departments"` ResponsiblePeople []string `json:"responsiblePeople"` } type OperationComplianceResponse struct { Items []OperationComplianceRow `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` Summary OperationComplianceSummary `json:"summary"` DateFrom string `json:"dateFrom"` DateTo string `json:"dateTo"` TargetKm float64 `json:"targetKm"` MileageSource string `json:"mileageSource"` AssignmentSource string `json:"assignmentSource"` AssignmentObservedAt string `json:"assignmentObservedAt"` AssignmentStale bool `json:"assignmentStale"` AssignmentMissingCount int `json:"assignmentMissingCount"` } func operationMileageSource(q url.Values) (string, error) { if len(q["mileageSource"]) > 1 { return "", clientError{Code: "INVALID_MILEAGE_SOURCE", Message: "请选择一个里程数据源"} } source := q.Get("mileageSource") if source == "" { source = "gps" } if source != "gps" && source != "odometer" && source != "hydrogen" { return "", clientError{Code: "INVALID_MILEAGE_SOURCE", Message: "里程数据源仅支持GPS、仪表或纯氢里程"} } return source, nil } func operationSourceLabel(source string) string { if source == "odometer" { return "仪表" } if source == "hydrogen" { return "纯氢" } return "GPS" } func operationDates(q url.Values) ([]string, error) { datesQuery := url.Values{"dateFrom": {q.Get("dateFrom")}, "dateTo": {q.Get("dateTo")}} if datesQuery.Get("dateFrom") == "" && datesQuery.Get("dateTo") == "" { now := time.Now().In(time.FixedZone("Asia/Shanghai", 8*3600)) datesQuery.Set("dateFrom", now.AddDate(0, 0, -30).Format("2006-01-02")) datesQuery.Set("dateTo", now.AddDate(0, 0, -1).Format("2006-01-02")) } return customerMileageDates(datesQuery, time.Now()) } func operationComplianceAccess(ctx context.Context) (Principal, error) { p, ok := PrincipalFromContext(ctx) if !ok || p.AuthorizationStatus == "pending" || p.AuthorizationStatus == "loading" || !p.CanMenu(operationComplianceMenu) { return p, clientError{Code: "MENU_PERMISSION_DENIED", Message: "当前账号无权查看车辆运营达标统计"} } return p, nil } func parseOperationTarget(q url.Values) (int64, error) { values, exists := q["targetKm"] if !exists { return 3000000, nil } if len(values) != 1 || !operationMileageDecimal.MatchString(values[0]) { return 0, clientError{Code: "INVALID_OPERATION_TARGET", Message: "达标里程必须为正数,最多保留三位小数"} } r, ok := new(big.Rat).SetString(values[0]) if !ok { return 0, clientError{Code: "INVALID_OPERATION_TARGET", Message: "达标里程无效"} } r.Mul(r, big.NewRat(1000, 1)) if !r.IsInt() || !r.Num().IsInt64() || r.Sign() <= 0 { return 0, clientError{Code: "INVALID_OPERATION_TARGET", Message: "达标里程必须大于零且在有效数值范围内"} } return r.Num().Int64(), nil } func operationSelections(q url.Values, key string) (map[string]bool, error) { if len(q[key]) > 200 { return nil, clientError{Code: "INVALID_FILTER", Message: "单项筛选最多选择200项"} } out := map[string]bool{} for _, value := range q[key] { value = strings.TrimSpace(value) if len([]rune(value)) > 512 { return nil, clientError{Code: "INVALID_FILTER", Message: "筛选项过长"} } if value != "" { out[value] = true } } return out, nil } // The roster is authoritative: measurements and ownership are left joins, so // unreported or unassigned authorized vehicles remain in the report. func (s *Service) operationRoster(ctx context.Context) ([]CustomerVehicleAssignment, OperationComplianceResponse, error) { meta := OperationComplianceResponse{Items: []OperationComplianceRow{}} p, err := operationComplianceAccess(ctx) if err != nil { return nil, meta, err } store, ok := s.store.(OperationComplianceStore) if !ok { return nil, meta, clientError{Code: "OPERATION_COMPLIANCE_UNAVAILABLE", Message: "当前数据源尚未启用车辆运营达标统计"} } snapshot, _ := ctx.Value(customerAssignmentContextKey{}).(CustomerAssignmentSnapshot) var roster, assignments []CustomerVehicleAssignment observed := snapshot.ObservedAt if snapshot.Live { assignments, err = normalizeCustomerAssignments(snapshot.Rows) roster = assignments meta.AssignmentSource = "oneos-live" } else { tenant := p.TenantRef if snapshot.Tenant != "" { tenant = snapshot.Tenant } assignments, observed, err = store.OperationAssignments(ctx, tenant) if err != nil { return nil, meta, err } assignments, err = normalizeCustomerAssignments(assignments) if err != nil { return nil, meta, err } query, scopeErr := applyPrincipalVehicleScope(ctx, url.Values{}) if scopeErr != nil { return nil, meta, scopeErr } roster, err = store.OperationVehicleRoster(ctx, query) meta.AssignmentSource = "oneos-synced" } if err != nil { return nil, meta, err } meta.AssignmentStale = observed.IsZero() || time.Since(observed) > 24*time.Hour if !observed.IsZero() { meta.AssignmentObservedAt = observed.UTC().Format(time.RFC3339) } byVIN := map[string]CustomerVehicleAssignment{} for _, row := range assignments { byVIN[row.VIN] = row } result := []CustomerVehicleAssignment{} seen := map[string]bool{} for _, row := range roster { row.VIN = strings.ToUpper(strings.TrimSpace(row.VIN)) if !isLikelyVIN(row.VIN) || !p.CanVIN(row.VIN) || seen[row.VIN] { continue } seen[row.VIN] = true if assignment, exists := byVIN[row.VIN]; exists { if assignment.Plate == "" { assignment.Plate = row.Plate } row = assignment } else { meta.AssignmentMissingCount++ } result = append(result, row) } sort.Slice(result, func(i, j int) bool { return result[i].VIN < result[j].VIN }) return result, meta, nil } func (s *Service) OperationComplianceOptions(ctx context.Context) (OperationComplianceOptions, error) { roster, _, err := s.operationRoster(ctx) out := OperationComplianceOptions{Customers: []string{}, Departments: []string{}, ResponsiblePeople: []string{}} if err != nil { return out, err } customers, departments, people := map[string]bool{}, map[string]bool{}, map[string]bool{} for _, r := range roster { if r.Customer != "" { customers[r.Customer] = true } if r.Department != "" { departments[r.Department] = true } if r.ResponsiblePerson != "" { people[r.ResponsiblePerson] = true } } for value := range customers { out.Customers = append(out.Customers, value) } for value := range departments { out.Departments = append(out.Departments, value) } for value := range people { out.ResponsiblePeople = append(out.ResponsiblePeople, value) } sort.Strings(out.Customers) sort.Strings(out.Departments) sort.Strings(out.ResponsiblePeople) return out, nil } func operationAuthorizedBounds(p Principal, vin, from, to string) (string, string, int) { zone := time.FixedZone("Asia/Shanghai", 8*3600) start, _ := time.ParseInLocation("2006-01-02", from, zone) end, _ := time.ParseInLocation("2006-01-02", to, zone) if p.HasHistoryTimeScope() { grant, ok := p.VehicleGrant(vin) if !ok || grant.ValidFrom.IsZero() { return "", "", 0 } g := grant.ValidFrom.In(zone) first := time.Date(g.Year(), g.Month(), g.Day(), 0, 0, 0, 0, zone) if !first.Equal(g) { first = first.AddDate(0, 0, 1) } if first.After(start) { start = first } if grant.ValidTo != nil { g = grant.ValidTo.In(zone) last := time.Date(g.Year(), g.Month(), g.Day(), 0, 0, 0, 0, zone).AddDate(0, 0, -1) if last.Before(end) { end = last } } } if start.After(end) { return "", "", 0 } return start.Format("2006-01-02"), end.Format("2006-01-02"), int(end.Sub(start)/(24*time.Hour)) + 1 } func (s *Service) OperationCompliance(ctx context.Context, q url.Values) (OperationComplianceResponse, error) { var empty OperationComplianceResponse source, err := operationMileageSource(q) if err != nil { return empty, err } target, err := parseOperationTarget(q) if err != nil { return empty, err } dates, err := operationDates(q) if err != nil { return empty, err } filters := make([]map[string]bool, 3) for i, key := range []string{"customer", "department", "responsiblePerson"} { filters[i], err = operationSelections(q, key) if err != nil { return empty, err } } status := q.Get("status") if status != "" && status != "passed" && status != "failed" && status != "insufficient" { return empty, clientError{Code: "INVALID_STATUS", Message: "达标状态无效"} } order := q.Get("sort") if order == "" { order = "mileage-desc" } if order != "mileage-desc" && order != "mileage-asc" { return empty, clientError{Code: "INVALID_SORT", Message: "里程排序无效"} } limit, offset := 30, 0 if q.Has("limit") { limit, err = strconv.Atoi(q.Get("limit")) if err != nil || limit < 1 || limit > 100 { return empty, clientError{Code: "INVALID_PAGINATION", Message: "每页数量应为1至100"} } } if q.Has("offset") { offset, err = strconv.Atoi(q.Get("offset")) if err != nil || offset < 0 { return empty, clientError{Code: "INVALID_PAGINATION", Message: "分页位置无效"} } } roster, response, err := s.operationRoster(ctx) if err != nil { return response, err } p, _ := PrincipalFromContext(ctx) selected := []CustomerVehicleAssignment{} query := url.Values{"dateFrom": {dates[0]}, "dateTo": {dates[len(dates)-1]}, "mileageSource": {source}} starts, ends, authorized := map[string]string{}, map[string]string{}, map[string]int{} vins := []string{} for _, row := range roster { if len(filters[0]) > 0 && !filters[0][row.Customer] || len(filters[1]) > 0 && !filters[1][row.Department] || len(filters[2]) > 0 && !filters[2][row.ResponsiblePerson] { continue } selected = append(selected, row) from, to, days := operationAuthorizedBounds(p, row.VIN, dates[0], dates[len(dates)-1]) authorized[row.VIN] = days if days > 0 { vins = append(vins, row.VIN) starts[row.VIN] = from ends[row.VIN] = to } } if len(selected)*len(dates) > 100000 { return response, clientError{Code: "OPERATION_RANGE_TOO_LARGE", Message: "本次查询超过100,000个车辆日,请缩短日期或筛选车辆归属"} } totals := map[string]OperationMileageTotal{} if len(vins) > 0 { query.Set("vins", strings.Join(vins, ",")) encoded, _ := json.Marshal(starts) query.Set("scopeVehicleFrom", string(encoded)) encoded, _ = json.Marshal(ends) query.Set("operationVehicleTo", string(encoded)) rows, queryErr := s.store.(OperationComplianceStore).OperationMileageTotals(ctx, query) if queryErr != nil { return response, queryErr } for _, row := range rows { if _, exists := totals[row.VIN]; exists { return response, clientError{Code: "OPERATION_DUPLICATE_TOTAL", Message: "同一车辆存在重复区间汇总"} } totals[row.VIN] = row } } response.DateFrom, response.DateTo, response.TargetKm = dates[0], dates[len(dates)-1], float64(target)/1000 response.MileageSource = source response.Limit, response.Offset = limit, offset rows := []OperationComplianceRow{} for _, vehicle := range selected { total := totals[vehicle.VIN] row, rowErr := evaluateOperationRow(vehicle, total, authorized[vehicle.VIN], len(dates), target, source) if rowErr != nil { return response, rowErr } if status != "" && row.Status != status { continue } rows = append(rows, row) } sort.Slice(rows, func(i, j int) bool { a, b := rows[i], rows[j] if a.MileageKm == nil || b.MileageKm == nil { if (a.MileageKm == nil) != (b.MileageKm == nil) { return a.MileageKm != nil } return a.VIN < b.VIN } // Ordering uses the exact integer totals too; displayed decimals are presentation only. am, bm := *totals[a.VIN].Meters, *totals[b.VIN].Meters if am == bm { return a.VIN < b.VIN } if order == "mileage-asc" { return am < bm } return am > bm }) response.Total = len(rows) response.Summary.Total = len(rows) for _, row := range rows { switch row.Status { case "passed": response.Summary.Passed++ case "failed": response.Summary.Failed++ default: response.Summary.Insufficient++ } } if denominator := response.Summary.Passed + response.Summary.Failed; denominator > 0 { rate := float64(response.Summary.Passed) / float64(denominator) * 100 response.Summary.PassRate = &rate } if offset < len(rows) { end := offset + limit if end > len(rows) || end < offset { end = len(rows) } response.Items = rows[offset:end] } return response, nil } func evaluateOperationRow(vehicle CustomerVehicleAssignment, total OperationMileageTotal, authorized, expected int, target int64, source string) (OperationComplianceRow, error) { row := OperationComplianceRow{CustomerVehicleAssignment: vehicle, ExpectedDays: expected, AuthorizedDays: authorized, ValidDays: total.ValidDays, InvalidDays: total.InvalidDays, Reasons: []string{}} row.MissingDays = authorized - total.ValidDays - total.InvalidDays if row.MissingDays < 0 || row.ValidDays < 0 || row.InvalidDays < 0 { return row, clientError{Code: "OPERATION_INVALID_COVERAGE", Message: "里程覆盖计数异常"} } row.Incomplete = row.ValidDays != expected if authorized < expected { row.Reasons = append(row.Reasons, "部分日期不在完整自然日历史授权范围内") } if row.InvalidDays > 0 { row.Reasons = append(row.Reasons, "存在异常或冲突日里程,已排除") } if row.MissingDays > 0 { row.Reasons = append(row.Reasons, "部分授权日期缺少"+operationSourceLabel(source)+"日里程") } row.Status = "insufficient" if total.Meters != nil && row.ValidDays > 0 { value := float64(*total.Meters) / 1000 row.MileageKm = &value if *total.Meters >= target { row.Status = "passed" } else if !row.Incomplete { row.Status = "failed" } } else if authorized > 0 { row.Reasons = append(row.Reasons, "暂无有效"+operationSourceLabel(source)+"日里程") } return row, nil }