using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Data.Common; using System.Globalization; using System.Linq; using System.Threading.Tasks; using VOL.Business.IServices.SunshineRun; using VOL.Core.EFDbContext; using VOL.Core.Extensions.AutofacManager; using VOL.Core.ManageUser; using VOL.Entity.DomainModels; using VOL.Model; using VOL.Model.SunshineRun; namespace VOL.Business.Services.SunshineRun { public class SunshineRunService : ISunshineRunService, IDependency { private readonly VOLContext _dbContext; public SunshineRunService(VOLContext dbContext) { _dbContext = dbContext; } public async Task> GetPageList(SunshineRunPageParam param) { param ??= new SunshineRunPageParam(); ValidateDateRange(param); var schoolId = await GetSchoolId(); var total = await GetCount(param, schoolId); var records = await GetRecords(param, schoolId, param.PageIndex, param.PageSize); return new PageDataDto { Total = total, TotalPages = (int)Math.Ceiling(total / (double)param.PageSize), PageIndex = param.PageIndex, PageSize = param.PageSize, Datas = records }; } public async Task> GetExportList(SunshineRunFilterParam param) { param ??= new SunshineRunFilterParam(); ValidateDateRange(param); return await GetRecords(param, await GetSchoolId(), null, null); } private static void ValidateDateRange(SunshineRunFilterParam param) { if (param.StartDate.HasValue && param.EndDate.HasValue && param.StartDate.Value.Date > param.EndDate.Value.Date) throw new ArgumentException("结束日期不能早于开始日期"); } private async Task GetSchoolId() { if (UserContext.Current.IsSuperAdmin) return null; var schoolId = await _dbContext.Set().AsNoTracking() .Where(x => x.SchoolCode == UserContext.Current.TenantId) .Select(x => (int?)x.Id) .FirstOrDefaultAsync(); return schoolId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty; } private static string BuildWhere(SunshineRunFilterParam param, string schoolId) { var conditions = new List { "ISNULL(r.EffectiveDistance, 0) > 0", "ISNULL(r.Times, 0) - ISNULL(r.InvalidTimes, 0) > 0" }; if (schoolId != null) conditions.Add("r.OrgId = @OrgId"); if (!string.IsNullOrWhiteSpace(param.StudentName)) conditions.Add("r.StudentName LIKE @StudentName"); if (!string.IsNullOrWhiteSpace(param.ClassName)) conditions.Add("(r.ClassName LIKE @ClassName OR CONCAT(r.GradeName, r.ClassName) LIKE @ClassName)"); if (param.GradeId.HasValue) conditions.Add("r.GradeId = @GradeId"); if (param.ClassId.HasValue) conditions.Add("r.ClassId = @ClassId"); // CreateTime is stored as an ISO-formatted string in XW_SunshineRunTrain. if (param.StartDate.HasValue) conditions.Add("r.CreateTime >= @StartDate"); if (param.EndDate.HasValue) conditions.Add("r.CreateTime < @EndDateExclusive"); return string.Join(" AND ", conditions); } private static void AddParameter(DbCommand command, string name, object value) { var parameter = command.CreateParameter(); parameter.ParameterName = name; parameter.Value = value ?? DBNull.Value; command.Parameters.Add(parameter); } private static void AddFilters(DbCommand command, SunshineRunFilterParam param, string schoolId) { if (schoolId != null) AddParameter(command, "@OrgId", schoolId); if (!string.IsNullOrWhiteSpace(param.StudentName)) AddParameter(command, "@StudentName", "%" + param.StudentName.Trim() + "%"); if (!string.IsNullOrWhiteSpace(param.ClassName)) AddParameter(command, "@ClassName", "%" + param.ClassName.Trim() + "%"); if (param.GradeId.HasValue) AddParameter(command, "@GradeId", param.GradeId.Value.ToString(CultureInfo.InvariantCulture)); if (param.ClassId.HasValue) AddParameter(command, "@ClassId", param.ClassId.Value.ToString(CultureInfo.InvariantCulture)); if (param.StartDate.HasValue) AddParameter(command, "@StartDate", param.StartDate.Value.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); if (param.EndDate.HasValue) AddParameter(command, "@EndDateExclusive", param.EndDate.Value.Date.AddDays(1).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } private async Task GetCount(SunshineRunFilterParam param, string schoolId) { var connection = _dbContext.Database.GetDbConnection(); await connection.OpenAsync(); try { await using var command = connection.CreateCommand(); command.CommandText = "SELECT COUNT(*) FROM XW_SunshineRunTrain r WHERE " + BuildWhere(param, schoolId); AddFilters(command, param, schoolId); return Convert.ToInt32(await command.ExecuteScalarAsync(), CultureInfo.InvariantCulture); } finally { await connection.CloseAsync(); } } private async Task> GetRecords( SunshineRunFilterParam param, string schoolId, int? pageIndex, int? pageSize) { var connection = _dbContext.Database.GetDbConnection(); await connection.OpenAsync(); try { await using var command = connection.CreateCommand(); command.CommandText = @"SELECT r.StudentName, r.Sex, r.GradeName, r.ClassName, r.EffectiveDistance, r.InvalidDistance, r.Times, r.InvalidTimes, r.CreateTime FROM XW_SunshineRunTrain r WHERE " + BuildWhere(param, schoolId) + " ORDER BY r.CreateTime DESC, r.Id DESC" + (pageIndex.HasValue ? " OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY" : ""); AddFilters(command, param, schoolId); if (pageIndex.HasValue) { AddParameter(command, "@Offset", (pageIndex.Value - 1) * pageSize.Value); AddParameter(command, "@PageSize", pageSize.Value); } var rows = new List(); await using var reader = await command.ExecuteReaderAsync(); var index = pageIndex.HasValue ? (pageIndex.Value - 1) * pageSize.Value : 0; while (await reader.ReadAsync()) { var effectiveDistance = GetInt(reader, 4); var invalidDistance = GetInt(reader, 5); var seconds = GetInt(reader, 6); var invalidSeconds = GetInt(reader, 7); var start = reader.IsDBNull(8) ? null : reader.GetString(8); var parsedStart = DateTime.TryParse(start, CultureInfo.InvariantCulture, DateTimeStyles.None, out var startTime); rows.Add(new SunshineRunRecordModel { Index = ++index, StudentName = GetString(reader, 0), SexName = GetInt(reader, 1) switch { 1 => "男", 2 => "女", _ => "-" }, ClassName = string.Join(" ", new[] { GetString(reader, 2), GetString(reader, 3) } .Where(x => !string.IsNullOrWhiteSpace(x))), EffectiveDistance = FormatDistance(effectiveDistance), EffectiveTime = FormatTime(seconds.HasValue ? Math.Max(0, seconds.Value - (invalidSeconds ?? 0)) : null), TotalDistance = FormatDistance(effectiveDistance.HasValue || invalidDistance.HasValue ? (effectiveDistance ?? 0) + (invalidDistance ?? 0) : null), TotalTime = FormatTime(seconds), StartTime = parsedStart ? startTime.ToString("yyyy-MM-dd HH:mm:ss") : start ?? "-", EndTime = parsedStart && seconds.HasValue ? startTime.AddSeconds(seconds.Value).ToString("yyyy-MM-dd HH:mm:ss") : "-" }); } return rows; } finally { await connection.CloseAsync(); } } private static string GetString(DbDataReader reader, int ordinal) => reader.IsDBNull(ordinal) ? string.Empty : reader.GetString(ordinal); private static int? GetInt(DbDataReader reader, int ordinal) => reader.IsDBNull(ordinal) ? null : Convert.ToInt32(reader.GetValue(ordinal), CultureInfo.InvariantCulture); private static string FormatDistance(int? meters) => meters.HasValue ? (meters.Value / 1000m).ToString("0.##", CultureInfo.InvariantCulture) + "公里" : "-"; private static string FormatTime(int? seconds) { if (!seconds.HasValue) return "-"; var value = Math.Max(0, seconds.Value); var minutes = value / 60; return minutes > 0 ? minutes + "分" + value % 60 + "秒" : value + "秒"; } } }