using Furion.DatabaseAccessor.Extensions;
using Furion.JsonSerialization;
using NPOI.SS.UserModel;
using NPOI.SS.Util;
using NPOI.XSSF.UserModel;
using YBEE.EQM.Core;
namespace YBEE.EQM.Application;
///
/// 监测抽样方案管理服务
///
public class ExamSampleService(IRepository rep,
IExportExcelService exportExcelService,
ISysDictDataService sysDictDataService) : IExamSampleService, ITransient
{
#region 方案管理
///
/// 添加监测抽样方案
///
///
///
public async Task Add(AddExamSampleInput input)
{
var examPlan = await rep.Change().DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
var maxSeq = await rep.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId).MaxAsync(t => (short?)t.Sequence);
var item = input.Adapt();
item.Sequence = (short)(maxSeq.HasValue ? (maxSeq + 1) : 1);
string sampleName = $"学生抽样方案{ConvertUtil.ConvertToChinese(item.Sequence)}";
item.FullName = $"{examPlan.FullName}{sampleName}";
item.Name = $"{examPlan.Name}{sampleName}";
item.ShortName = $"{examPlan.ShortName}{sampleName}";
await item.InsertAsync();
}
///
/// 更新监测抽样方案
///
///
///
public async Task Update(UpdateExamSampleInput input)
{
if (!await rep.AnyAsync(t => t.Id == input.Id))
{
throw Oops.Oh(ErrorCode.E2001);
}
var item = input.Adapt();
await item.UpdateIncludeAsync(new[] {
nameof(item.Name),
nameof(item.FullName),
nameof(item.ShortName),
nameof(item.Remark),
nameof(item.Config)
});
}
///
/// 复制抽样方案信息
///
///
///
public async Task Duplicate(BaseId input)
{
var item = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
var examPlan = await rep.Change().DetachedEntities.FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
var maxSeq = await rep.DetachedEntities.Where(t => t.ExamPlanId == item.ExamPlanId).MaxAsync(t => (short?)t.Sequence);
short sequence = (short)(maxSeq.HasValue ? (maxSeq + 1) : 1);
string sampleName = $"学生抽样方案{ConvertUtil.ConvertToChinese(sequence)}";
ExamSample newItem = new()
{
ExamPlanId = item.ExamPlanId,
Sequence = sequence,
FullName = $"{examPlan.FullName}{sampleName}",
Name = $"{examPlan.Name}{sampleName}",
ShortName = $"{examPlan.ShortName}{sampleName}",
Status = ExamSampleStatus.INITIAL,
Config = item.Config,
Remark = item.Remark,
};
await newItem.InsertAsync();
}
///
/// 删除监测抽样方案
///
///
///
public async Task Del(BaseId input)
{
var item = await rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
var examPlan = await rep.Change().DetachedEntities.FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
if (examPlan.IsFixedExamSample || item.Status == ExamSampleStatus.RUNNING || item.IsSelected == true)
{
throw Oops.Oh(ErrorCode.E3001);
}
// 批量删除已抽测学生
await rep.Change().Where(t => t.ExamSampleId == item.Id).ExecuteDeleteAsync();
// 删除抽样方案
await item.DeleteNowAsync();
}
///
/// 保存全抽班级ID列表
///
///
///
public async Task SaveExamSampleAllClasses(SaveExamSampleAllClasses input)
{
var item = await rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
var config = JSON.Deserialize(item.Config);
config.SampleAllSchoolClassIds = input.ClassIds;
item.Config = JSON.Serialize(config);
await item.UpdateIncludeAsync(new[] { nameof(item.Config) });
}
///
/// 切换全抽班级
///
///
///
public async Task SwitchExamSampleAllClass(SwitchExamSampleAllClassInput input)
{
var item = await rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
var config = JSON.Deserialize(item.Config);
if (input.IsAdd && !config.SampleAllSchoolClassIds.Any(t => t == input.SchoolClassId))
{
config.SampleAllSchoolClassIds.Add(input.SchoolClassId);
}
else if (!input.IsAdd && config.SampleAllSchoolClassIds.Any(t => t == input.SchoolClassId))
{
config.SampleAllSchoolClassIds.Remove(input.SchoolClassId);
}
item.Config = JSON.Serialize(config);
await item.UpdateIncludeAsync(new[] { nameof(item.Config) });
}
///
/// 选定方案
///
///
///
public async Task SelectSample(BaseId input)
{
var item = await rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001);
var examPlan = await rep.Change().FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
item.IsSelected = true;
item.SelectedTime = DateTime.Now;
item.SelectedSysUserId = CurrentSysUserInfo.SysUserId;
examPlan.IsFixedExamSample = true;
await item.UpdateAsync();
await examPlan.UpdateAsync();
}
///
/// 执行抽样
///
/// 抽样方案ID
///
///
public async Task ExecuteSample(BaseId input)
{
// 抽样方案
var item = await rep.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
// 监测计划
var plan = await rep.Change().DetachedEntities
.ProjectToType()
.FirstOrDefaultAsync(t => t.Id == item.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
// 监测年级字典
var examGradeDict = plan.ExamGrades.ToDictionary(t => t.GradeId);
var examSample = item.Adapt();
try
{
var sampleItem = item.Adapt();
// 更新状态为【生成中】
item.Status = ExamSampleStatus.RUNNING;
await item.UpdateIncludeNowAsync(new[] { nameof(item.Status) });
// 获取所有学生信息
var stus = await GetStudentScoreList(sampleItem.Config, item.ExamScoreRefExamPlanId, item.ExamPlanId, plan.ExamGrades.Select(t => t.GradeId).Distinct().ToList());
// 获取抽样学生信息
var sampleStus = GetSampleList(sampleItem.Config, examGradeDict, stus);
// 获取最终抽样学生列表
var finalSampleStus = GetFinalSampleList(examSample, examGradeDict, sampleStus);
// 删除已存在数据
string deleteSql = $"DELETE FROM exam_sample_student WHERE exam_sample_id = {input.Id}";
await rep.SqlNonQueryAsync(deleteSql);
#region 批量写入
int si = 0;
int stuCount = finalSampleStus.Count;
List insertValues = [];
foreach (var stu in finalSampleStus)
{
si++;
var iss = stu.IsSpecialStudent ? 1 : 0;
string valueSql = $"({stu.ExamSampleId}, {stu.ExamStudentId}, '{stu.ExamNumber}', {stu.Sequence}, {(short)stu.ExamSampleType}, {iss}, {stu.PreTotalScore})";
insertValues.Add(valueSql);
// 一次写入2000行
if (si % 2000 == 0 || si == stuCount)
{
string insertSql = $"INSERT INTO exam_sample_student(exam_sample_id, exam_student_id, exam_number, sequence, exam_sample_type, is_special_student, pre_total_score) VALUES {string.Join(", ", insertValues)}";
await rep.SqlNonQueryAsync(insertSql);
insertValues.Clear();
}
}
#endregion
// 更新状态为【已生成】
item.Status = ExamSampleStatus.SUCCESSFUL;
await item.UpdateIncludeNowAsync(new[] { nameof(item.Status) });
}
catch (Exception ex)
{
// 更新状态为【已失败】
item.Status = ExamSampleStatus.FAILED;
await item.UpdateIncludeNowAsync(new[] { nameof(item.Status) });
throw new Exception(ex.Message);
}
}
#endregion
#region 导出抽样数据
///
/// 导出抽样方案存档文件
///
///
///
///
public async Task<(string fileName, byte[] fileBytes)> ExportToArchived(int id, bool hideIdNumber = false)
{
// 抽样方案
var examSample = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
// 监测计划
var plan = await rep.Change().DetachedEntities
.ProjectToType()
.FirstOrDefaultAsync(t => t.Id == examSample.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
// 监测年级字典
var examGradeDict = plan.ExamGrades.ToDictionary(t => t.GradeId);
// 获取证件类型
var cts = await sysDictDataService.GetListByDictTypeId(304);
var certificateTypes = cts.ToDictionary(x => (Core.CertificateType)x.Value, y => y.Name);
// 临时存放目录
string fileRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
Directory.CreateDirectory(fileRoot);
// 抽样文件目录
string sampleFileRoot = Path.Combine(fileRoot, examSample.FullName);
string selectStusSql = @$"
SELECT
T2.sys_org_id,
T4.full_name AS sys_org_full_name,
T4.`name` AS sys_org_name,
T4.`code` AS sys_org_code,
T2.sys_org_branch_id,
T5.`name` AS sys_org_branch_name,
T2.grade_id,
T3.grade_number,
T3.`name` AS grade_name,
T2.school_class_id,
T2.class_number,
T1.exam_student_id,
T2.`name` AS exam_student_name,
T2.certificate_type,
T2.id_number,
T1.exam_number,
T1.exam_sample_type
FROM exam_sample_student AS T1
JOIN exam_student AS T2 ON T1.exam_student_id = T2.id
JOIN base_grade AS T3 ON T2.grade_id = T3.id
JOIN sys_org AS T4 ON T2.sys_org_id = T4.id
LEFT JOIN sys_org AS T5 ON T2.sys_org_branch_id = T5.id
WHERE T1.exam_sample_id = {id}
";
// 所有学生
var stus = await rep.SqlQueriesAsync(selectStusSql);
// 按年级生成考生文件
var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgFullName, t.SysOrgCode, t.SysOrgBranchId, t.SysOrgBranchName, t.GradeId })
.OrderBy(t => t.SysOrgId).ThenBy(t => t.SysOrgBranchId).ThenBy(t => t.GradeId).Distinct().ToList();
foreach (var w in ws)
{
// 监测年级
var eg = examGradeDict[w.GradeId];
// 学校目录
string orgDir = Path.Combine(sampleFileRoot, $"{w.SysOrgCode}-{w.SysOrgFullName}");
if (!Directory.Exists(orgDir))
{
Directory.CreateDirectory(orgDir);
}
// 校区名称
string branchName = "";
if (w.SysOrgBranchId != null)
{
branchName = $"@{w.SysOrgBranchName}";
}
#region 导出抽中学生
string sampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.DISTRICT.GetDescription()}-学生检录表";
// 抽中学生列表
var sampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.DISTRICT)
.OrderBy(t => t.ExamNumber?[^4..]).ToList();
// 自编号按导入顺序
if (eg.IsRequiredSelfExamNumber)
{
sampleStus = sampleStus.OrderBy(t => t.ExamStudentId).ToList();
}
// 定义EXCEL列
List> cols = new()
{
new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
new() { Name = "监测号", Width = 14, GetCellValue = (r) => r.ExamNumber },
new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
};
if (w.SysOrgBranchId != null)
{
cols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
}
if (!hideIdNumber)
{
cols.Add(new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] });
cols.Add(new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber });
}
cols.Add(new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() });
// 导出EXCEL文件
var ret = exportExcelService.ExportExcel(new ExportExcelDto()
{
Title = sampleFileName,
Columns = cols,
Items = sampleStus,
IncludeExportTime = false,
});
// 写入文件
await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{sampleFileName}.xlsx"), ret);
#endregion
#region 导出未抽中学生
string noSampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.SCHOOL_EXAM.GetDescription()}-学生检录表";
// 抽中学生列表
var noSampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.SCHOOL_EXAM)
.OrderBy(t => t.ExamNumber?[^4..]).ToList();
// 自编号按导入顺序
if (eg.IsRequiredSelfExamNumber)
{
noSampleStus = noSampleStus.OrderBy(t => t.ExamStudentId).ToList();
}
// 定义EXCEL列
List> noCols = new()
{
new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
new() { Name = "考号", Width = 14, GetCellValue = (r) => r.ExamNumber },
new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
};
if (w.SysOrgBranchId != null)
{
noCols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
}
if (!hideIdNumber)
{
noCols.Add(new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] });
noCols.Add(new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber });
}
noCols.Add(new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() });
// 导出EXCEL文件
var noRet = exportExcelService.ExportExcel(new ExportExcelDto()
{
Title = noSampleFileName,
Columns = noCols,
Items = noSampleStus,
IncludeExportTime = false,
});
// 写入文件
await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{noSampleFileName}.xlsx"), noRet);
#endregion
}
// 统计表文件
var countFile = await ExportSampleCount(id);
await File.WriteAllBytesAsync(Path.Combine(sampleFileRoot, countFile.fileName), countFile.fileBytes);
string outFileName = $"{examSample.FullName}.zip";
string outFilePath = Path.Combine(fileRoot, outFileName);
ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
zip.CreateZip(outFilePath, sampleFileRoot, true, string.Empty);
var retBytes = await File.ReadAllBytesAsync(outFilePath);
Directory.Delete(fileRoot, true);
return (outFileName, retBytes);
}
///
/// 导出给印刷厂和网阅机构文件
///
///
///
public async Task<(string fileName, byte[] fileBytes)> ExportToPrintshop(int id)
{
var item = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id && t.Status == ExamSampleStatus.SUCCESSFUL && t.IsSelected == true);
return item == null ? throw Oops.Oh(ErrorCode.E2006) : await ExportToArchived(id, true);
}
///
/// 导出给学校
///
///
///
public async Task<(string fileName, byte[] fileBytes)> ExportToOrg(int id)
{
// 抽样方案
var examSample = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id && t.ExamPlan.IsFixedExamSample == true && t.Status == ExamSampleStatus.SUCCESSFUL && t.IsSelected == true) ?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
// 监测计划
var plan = await rep.Change().DetachedEntities
.ProjectToType()
.FirstOrDefaultAsync(t => t.Id == examSample.ExamPlanId) ?? throw Oops.Oh(ErrorCode.E2001, "监测计划");
// 必须发布了的才能下载
var pb = await rep.Change().DetachedEntities.AnyAsync(t => t.ExamPlanId == plan.Id && t.Type == DataPublishType.STUDENT_SAMPLE_LIST && t.Status == PublishStatus.PUBLISHED);
if (!pb)
{
throw Oops.Oh(ErrorCode.E2006);
}
// 监测年级字典
var examGradeDict = plan.ExamGrades.ToDictionary(t => t.GradeId);
// 获取证件类型
var cts = await sysDictDataService.GetListByDictTypeId(304);
var certificateTypes = cts.ToDictionary(x => (Core.CertificateType)x.Value, y => y.Name);
// 临时存放目录
string fileRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
Directory.CreateDirectory(fileRoot);
string selectStusSql = @$"
SELECT
T2.sys_org_id,
T4.full_name AS sys_org_full_name,
T4.`name` AS sys_org_name,
T4.`code` AS sys_org_code,
T2.sys_org_branch_id,
T5.`name` AS sys_org_branch_name,
T2.grade_id,
T3.grade_number,
T3.`name` AS grade_name,
T2.school_class_id,
T2.class_number,
T1.exam_student_id,
T2.`name` AS exam_student_name,
T2.certificate_type,
T2.id_number,
T1.exam_number,
T1.exam_sample_type
FROM exam_sample_student AS T1
JOIN exam_student AS T2 ON T1.exam_student_id = T2.id
JOIN base_grade AS T3 ON T2.grade_id = T3.id
JOIN sys_org AS T4 ON T2.sys_org_id = T4.id
LEFT JOIN sys_org AS T5 ON T2.sys_org_branch_id = T5.id
WHERE T1.exam_sample_id = {id} AND T2.sys_org_id = {CurrentSysUserInfo.SysOrgId}
";
// 所有学生
var stus = await rep.SqlQueriesAsync(selectStusSql);
// 按年级生成考生文件
var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgFullName, t.SysOrgCode, t.SysOrgBranchId, t.SysOrgBranchName, t.GradeId })
.OrderBy(t => t.SysOrgId).ThenBy(t => t.SysOrgBranchId).ThenBy(t => t.GradeId).Distinct().ToList();
string fileName = "";
foreach (var w in ws)
{
// 监测年级
var eg = examGradeDict[w.GradeId];
// 学校目录
if (fileName == "")
{
fileName = $"{w.SysOrgCode}-{w.SysOrgFullName}";
}
string orgDir = Path.Combine(fileRoot, fileName);
if (!Directory.Exists(orgDir))
{
Directory.CreateDirectory(orgDir);
}
// 校区名称
string branchName = "";
if (w.SysOrgBranchId != null)
{
branchName = $"@{w.SysOrgBranchName}";
}
#region 导出抽中学生
string sampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.DISTRICT.GetDescription()}-学生检录表";
// 抽中学生列表
var sampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.DISTRICT)
.OrderBy(t => t.ExamNumber?[^4..]).ToList();
// 自编号按导入顺序
if (eg.IsRequiredSelfExamNumber)
{
sampleStus = sampleStus.OrderBy(t => t.ExamStudentId).ToList();
}
// 定义EXCEL列
List> cols = new()
{
new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
new() { Name = "监测号", Width = 14, GetCellValue = (r) => r.ExamNumber },
new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] },
new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber },
new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() },
};
if (w.SysOrgBranchId != null)
{
cols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
}
// 导出EXCEL文件
var ret = exportExcelService.ExportExcel(new ExportExcelDto()
{
Title = sampleFileName,
Columns = cols,
Items = sampleStus,
IncludeExportTime = false,
});
// 写入文件
await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{sampleFileName}.xlsx"), ret);
#endregion
#region 导出未抽中学生
string noSampleFileName = $"{w.SysOrgCode}-{w.SysOrgFullName}{branchName}-{eg.Grade.Name}-{ExamSampleType.SCHOOL_EXAM.GetDescription()}-学生检录表";
// 抽中学生列表
var noSampleStus = stus.Where(t => t.SysOrgId == w.SysOrgId && t.SysOrgBranchId == w.SysOrgBranchId && t.GradeId == w.GradeId && t.ExamSampleType == ExamSampleType.SCHOOL_EXAM)
.OrderBy(t => t.ExamNumber?[^4..]).ToList();
// 自编号按导入顺序
if (eg.IsRequiredSelfExamNumber)
{
noSampleStus = noSampleStus.OrderBy(t => t.ExamStudentId).ToList();
}
// 定义EXCEL列
List> noCols = new()
{
new() { Name = "年级号", Width = 8, GetCellValue = (r) => r.GradeNumber },
new() { Name = "班级号", Width = 8, GetCellValue = (r) => r.ClassNumber },
new() { Name = "考号", Width = 14, GetCellValue = (r) => r.ExamNumber },
new() { Name = "姓名", Width = 20, GetCellValue = (r) => r.ExamStudentName },
new() { Name = "证件类型", Width = 16, GetCellValue = (r) => certificateTypes[r.CertificateType] },
new() { Name = "证件号码", Width = 20, GetCellValue = (r) => r.IdNumber },
new() { Name = "名单类型", Width = 10, GetCellValue = (r) => r.ExamSampleType.GetDescription() },
};
if (w.SysOrgBranchId != null)
{
noCols.Insert(0, new() { Name = "校区", Width = 10, GetCellValue = (r) => r.SysOrgBranchName });
}
// 导出EXCEL文件
var noRet = exportExcelService.ExportExcel(new ExportExcelDto()
{
Title = noSampleFileName,
Columns = noCols,
Items = noSampleStus,
IncludeExportTime = false,
});
// 写入文件
await File.WriteAllBytesAsync(Path.Combine(orgDir, $"{noSampleFileName}.xlsx"), noRet);
#endregion
}
// 学生目录
var schoolFilePath = Path.Combine(fileRoot, fileName);
// 统计表文件
var countFile = await ExportSampleCountToOrg(id);
await File.WriteAllBytesAsync(Path.Combine(schoolFilePath, $"{fileName}-监测抽样统计表.xlsx"), countFile.fileBytes);
string outFileName = $"{plan.FullName}-{fileName}-监测抽样学生检录表.zip";
string outFilePath = Path.Combine(fileRoot, outFileName);
ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
zip.CreateZip(outFilePath, schoolFilePath, true, string.Empty);
var retBytes = await File.ReadAllBytesAsync(outFilePath);
Directory.Delete(fileRoot, true);
return (outFileName, retBytes);
}
///
/// 导出抽样统计表
///
///
///
public async Task<(string fileName, byte[] fileBytes)> ExportSampleCount(int id)
{
var examSample = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
var items = await GetSampleCountListById(id);
XSSFWorkbook wb = new();
ISheet sheet = wb.CreateSheet();
sheet.DisplayGridlines = false;
// 获取样式
var cellStyle = exportExcelService.GetCellStyle(wb);
#region 表头
int rowNum = 0;
IRow headerRow = sheet.CreateRow(rowNum++);
headerRow.Height = ExportExcelCellStyle.DefaultRowHeight;
int ci = 0;
exportExcelService.AddCell("数据类型", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
exportExcelService.AddCell("学校代码", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
exportExcelService.AddCell("学校名称", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 20);
exportExcelService.AddCell("年级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
exportExcelService.AddCell("班级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
exportExcelService.AddCell(ExamSampleType.DISTRICT.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
exportExcelService.AddCell(ExamSampleType.SCHOOL_EXAM.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
exportExcelService.AddCell("合计", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
sheet.CreateFreezePane(0, 1);
#endregion
foreach (var item in items)
{
IRow row = sheet.CreateRow(rowNum);
row.Height = ExportExcelCellStyle.DefaultRowHeight;
int si = 0;
string schoolCode = item.SysOrgCode ?? "";
string schoolName = item.SysOrgName ?? "";
string gradeName = item.GradeName ?? "";
long classNumber = item.ClassNumber;
ICellStyle cstyle = cellStyle.CenterCellStyle;
switch (item.TypeId)
{
case 2:
si = 3;
break;
case 3:
si = 2;
break;
case 4:
si = 3;
schoolCode = "";
break;
case 5:
si = 2;
schoolCode = "";
break;
}
if (si > 0)
{
cstyle = cellStyle.FillCellStyle;
sheet.AddMergedRegion(new CellRangeAddress(rowNum, rowNum, si, 4));
}
int rci = 0;
exportExcelService.AddCell(item.TypeName, row, rci++, cstyle);
exportExcelService.AddCell(schoolCode, row, rci++, cstyle);
exportExcelService.AddCell(schoolName, row, rci++, cstyle);
exportExcelService.AddCell(gradeName, row, rci++, cstyle);
exportExcelService.AddCell(classNumber, row, rci++, cstyle);
exportExcelService.AddCell(item.CenterStudentCount, row, rci++, cstyle);
exportExcelService.AddCell(item.SchoolStudentCount, row, rci++, cstyle);
exportExcelService.AddCell(item.TotalStudentCount, row, rci++, cstyle);
rowNum++;
}
MemoryStream ms = new();
wb.Write(ms, false);
ms.Flush();
return ($"{examSample.FullName}-监测抽样统计表.xlsx", ms.ToArray());
}
///
/// 导出学校抽样统计表
///
///
///
public async Task<(string fileName, byte[] fileBytes)> ExportSampleCountToOrg(int id)
{
var examSample = await rep.DetachedEntities.Include(t => t.ExamPlan).FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
var items = await GetSampleCountListById(id);
items = items.Where(t => t.TypeId < 4 && t.SysOrgId == CurrentSysUserInfo.SysOrgId).ToList();
XSSFWorkbook wb = new();
ISheet sheet = wb.CreateSheet();
sheet.DisplayGridlines = false;
// 获取样式
var cellStyle = exportExcelService.GetCellStyle(wb);
#region 表头
int rowNum = 0;
IRow headerRow = sheet.CreateRow(rowNum++);
headerRow.Height = ExportExcelCellStyle.DefaultRowHeight;
int ci = 0;
exportExcelService.AddCell("数据类型", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
exportExcelService.AddCell("学校代码", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
exportExcelService.AddCell("学校名称", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 20);
exportExcelService.AddCell("年级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
exportExcelService.AddCell("班级", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 10);
exportExcelService.AddCell(ExamSampleType.DISTRICT.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
exportExcelService.AddCell(ExamSampleType.SCHOOL_EXAM.GetDescription(), headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
exportExcelService.AddCell("合计", headerRow, ci++, cellStyle.ColumnHeaderStyle, sheet, 12);
sheet.CreateFreezePane(0, 1);
#endregion
foreach (var item in items)
{
IRow row = sheet.CreateRow(rowNum);
row.Height = ExportExcelCellStyle.DefaultRowHeight;
int si = 0;
string schoolCode = item.SysOrgCode ?? "";
string schoolName = item.SysOrgName ?? "";
string gradeName = item.GradeName ?? "";
long classNumber = item.ClassNumber;
ICellStyle cstyle = cellStyle.CenterCellStyle;
switch (item.TypeId)
{
case 2:
si = 3;
break;
case 3:
si = 2;
break;
case 4:
si = 3;
schoolCode = "";
break;
case 5:
si = 2;
schoolCode = "";
break;
}
if (si > 0)
{
cstyle = cellStyle.FillCellStyle;
sheet.AddMergedRegion(new CellRangeAddress(rowNum, rowNum, si, 4));
}
int rci = 0;
exportExcelService.AddCell(item.TypeName, row, rci++, cstyle);
exportExcelService.AddCell(schoolCode, row, rci++, cstyle);
exportExcelService.AddCell(schoolName, row, rci++, cstyle);
exportExcelService.AddCell(gradeName, row, rci++, cstyle);
exportExcelService.AddCell(classNumber, row, rci++, cstyle);
exportExcelService.AddCell(item.CenterStudentCount, row, rci++, cstyle);
exportExcelService.AddCell(item.SchoolStudentCount, row, rci++, cstyle);
exportExcelService.AddCell(item.TotalStudentCount, row, rci++, cstyle);
rowNum++;
}
MemoryStream ms = new();
wb.Write(ms, false);
ms.Flush();
return ($"{examSample.ExamPlan.FullName}-{CurrentSysUserInfo.SysOrgName}-监测抽样统计表.xlsx", ms.ToArray());
}
#endregion
#region 方案查询
///
/// 根据ID获取抽样方案
///
///
///
public async Task GetById(int id)
{
var item = await rep.DetachedEntities.ProjectToType().FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
//item.Config.ExamSampleRefExamPlanId;
return item.Adapt();
}
///
/// 根据监测计划ID获取全部抽样方案
///
///
///
public async Task> GetListByExamPlanId(int examPlanId)
{
var items = await rep.DetachedEntities.Where(t => t.ExamPlanId == examPlanId).ProjectToType().ToListAsync();
return items;
}
///
/// 获取抽样统计表
///
///
///
public async Task> GetSampleCountListById(int id)
{
var examSample = await rep.DetachedEntities.FirstOrDefaultAsync(t => t.Id == id) ?? throw Oops.Oh(ErrorCode.E2001);
string whereSql = $"WHERE T1.exam_plan_id = {examSample.ExamPlanId} AND T2.exam_sample_id = {id}";
string querySql = @$"
SELECT
T1.type_id,
T3.`name` AS type_name,
T1.sys_org_id,
T2.`code` AS sys_org_code,
T2.`name` AS sys_org_name,
T2.full_name AS sys_org_full_name,
T1.grade_id,
T4.`name` AS grade_name,
T1.school_class_id,
T1.class_number,
T1.total_student_count,
T1.center_student_count,
T1.school_student_count
FROM
(
-- 班级汇总
SELECT 1 AS type_id, T.sys_org_id, T.grade_id, T.school_class_id, T.class_number,
SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
SUM(T.student_count) AS total_student_count
FROM
(
SELECT T1.sys_org_id, T1.grade_id, T1.school_class_id, T1.class_number, T2.exam_sample_type, COUNT(1) AS student_count
FROM exam_student AS T1
JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
{whereSql}
GROUP BY T1.sys_org_id, T1.grade_id, T1.school_class_id, T1.class_number, T2.exam_sample_type
) AS T
GROUP BY T.sys_org_id, T.grade_id, T.school_class_id, T.class_number
UNION ALL
-- 年级汇总
SELECT 2 AS type_id, T.sys_org_id, T.grade_id, NULL AS school_class_id, 9999 AS class_number,
SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
SUM(T.student_count) AS total_student_count
FROM
(
SELECT T1.sys_org_id, T1.grade_id, T2.exam_sample_type, COUNT(1) AS student_count
FROM exam_student AS T1
JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
{whereSql}
GROUP BY T1.sys_org_id, T1.grade_id, T2.exam_sample_type
) AS T
GROUP BY T.sys_org_id, T.grade_id
UNION ALL
-- 学校汇总
SELECT 3 AS type_id, T.sys_org_id, 9999 AS grade_id, NULL AS school_class_id, 9999 AS class_number,
SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
SUM(T.student_count) AS total_student_count
FROM
(
SELECT T1.sys_org_id, T2.exam_sample_type, COUNT(1) AS student_count
FROM exam_student AS T1
JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
{whereSql}
GROUP BY T1.sys_org_id, T2.exam_sample_type
) AS T
GROUP BY T.sys_org_id
UNION ALL
-- 全区年级汇总
SELECT 4 AS type_id, 9999 AS sys_org_id, T.grade_id, NULL AS school_class_id, 9999 AS class_number,
SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
SUM(T.student_count) AS total_student_count
FROM
(
SELECT T1.grade_id, T2.exam_sample_type, COUNT(1) AS student_count
FROM exam_student AS T1
JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
{whereSql}
GROUP BY T1.grade_id, T2.exam_sample_type
) AS T
GROUP BY T.grade_id
UNION ALL
-- 全区汇总
SELECT 5 AS type_id, 9999 AS sys_org_id, 9999 AS grade_id, NULL AS school_class_id, 9999 AS class_number,
SUM(CASE WHEN T.exam_sample_type = @district THEN T.student_count ELSE 0 END) AS center_student_count,
SUM(CASE WHEN T.exam_sample_type = @schoolExam THEN T.student_count ELSE 0 END) AS school_student_count,
SUM(T.student_count) AS total_student_count
FROM
(
SELECT T2.exam_sample_type, COUNT(1) AS student_count
FROM exam_student AS T1
JOIN exam_sample_student AS T2 ON T1.id = T2.exam_student_id
{whereSql}
GROUP BY T2.exam_sample_type
) AS T
) AS T1
LEFT JOIN
(
SELECT id, `code`, `name`, full_name FROM sys_org WHERE org_type = 3
UNION ALL
SELECT 9999 AS id, '999' AS `code`, '全区' AS `name`, '全区' AS full_name
) AS T2 ON T1.sys_org_id = T2.id
JOIN
(
SELECT 1 AS id, '学校班级' AS `name` UNION ALL
SELECT 2 AS id, '学校年级' AS `name` UNION ALL
SELECT 3 AS id, '学校' AS `name` UNION ALL
SELECT 4 AS id, '全区年级' AS `name` UNION ALL
SELECT 5 AS id, '全区' AS `name`
) AS T3 ON T1.type_id = T3.id
LEFT JOIN base_grade AS T4 ON T1.grade_id = T4.id
ORDER BY T2.`code`, T1.grade_id, T1.class_number, T1.type_id
";
var items = await rep.SqlQueriesAsync(querySql, new
{
District = (short)ExamSampleType.DISTRICT,
SchoolExam = (short)ExamSampleType.SCHOOL_EXAM
});
return items;
}
///
/// 获取学校抽样统计表
///
///
///
public async Task> GetOrgSampleCountListById(int id)
{
var items = await GetSampleCountListById(id);
items = items.Where(t => t.TypeId < 4 && t.SysOrgId == CurrentSysUserInfo.SysOrgId).ToList();
return items;
}
///
/// 查询已发布抽样
///
/// 监测发布内容ID
/// 抽样数据发布类型
///
public async Task GetByExamDataPublishId(int examDataPublishId, DataPublishType type)
{
if (type != DataPublishType.STUDENT_SAMPLE_LIST && type != DataPublishType.STUDENT_SAMPLE_COUNT_LIST)
{
throw Oops.Oh(ErrorCode.E1014, "数据发布");
}
var pub = await rep.Change().DetachedEntities.FirstOrDefaultAsync(t => t.Id == examDataPublishId && t.Type == type) ?? throw Oops.Oh(ErrorCode.E2001, "反馈内容");
var item = await rep.DetachedEntities.Include(t => t.ExamPlan)
.FirstOrDefaultAsync(t => t.ExamPlanId == pub.ExamPlanId && t.ExamPlan.IsFixedExamSample == true && t.Status == ExamSampleStatus.SUCCESSFUL && t.IsSelected == true)
?? throw Oops.Oh(ErrorCode.E2001, "抽样方案");
return item.Adapt();
}
#endregion
#region 机构查询
#endregion
#region 私有方法
///
/// 获取最终抽样学生列表
///
///
///
///
///
private static List GetFinalSampleList(ExamSampleOutput examSample, Dictionary examGradeDict, List stus)
{
// 抽样配置
var config = examSample.Config;
// 返回结果集
List retItems = new();
// 遍历集合
var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgBranchId, t.GradeId, t.SchoolClassId }).Distinct().ToList();
// 学校
var orgs = ws.Select(t => t.SysOrgId).Distinct().ToList();
foreach (var orgId in orgs)
{
// 跳过不参与监测的学校
if (config.ExcludeSysOrgIds.Contains(orgId))
{
continue;
}
// 校区
var branches = ws.Where(t => t.SysOrgId == orgId).Select(t => t.SysOrgBranchId).Distinct().ToList();
foreach (var branchId in branches)
{
// 年级
var grades = ws.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId).Select(t => t.GradeId).Distinct().ToList();
foreach (var gradeId in grades)
{
var eg = examGradeDict[gradeId];
// 自编号直接写入
if (eg.IsRequiredSelfExamNumber)
{
var selfList = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.IsSelfExamNumber == true).OrderBy(t => t.ExamNumber?[^4..]).ToList();
retItems.AddRange(selfList.Select((t, i) => new AddExamSampleStudentInput
{
ExamSampleId = examSample.Id,
ExamStudentId = t.ExamStudentId,
ExamNumber = t.ExamNumber,
ExamSampleType = t.ExamSampleType ?? ExamSampleType.DISTRICT,
IsSpecialStudent = t.IsSpecialStudent,
PreTotalScore = t.TotalScore ?? 0,
Sequence = i + 1,
}));
continue;
}
// 区测学生列表
var sampleStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.ExamSampleType == ExamSampleType.DISTRICT)
.OrderBy(t => t.ClassNumber).ThenBy(t => t.Sequence).ToList();
// 区测学生数量
var sampleCount = sampleStus.Count;
// 是否在年级内随机打乱顺序
if (config.IsGradeSeatNumberRandom)
{
Utils.ListRandom(sampleStus);
}
// 生成区测学生监测号并插入
retItems.AddRange(sampleStus.Select((t, i) => new AddExamSampleStudentInput
{
ExamSampleId = examSample.Id,
ExamStudentId = t.ExamStudentId,
ExamNumber = BuildExamNumber(t.SysOrgCode, eg.Grade.GradeNumber, t.ClassNumber, i + 1),
ExamSampleType = ExamSampleType.DISTRICT,
IsSpecialStudent = t.IsSpecialStudent,
PreTotalScore = t.TotalScore ?? 0,
Sequence = i + 1,
}));
// 校测学生列表
var noSampleStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.ExamSampleType == ExamSampleType.SCHOOL_EXAM)
.OrderBy(t => t.ClassNumber).ThenBy(t => t.Sequence).ToList();
// 是否在年级内随机打乱顺序
if (config.IsGradeSeatNumberRandom)
{
Utils.ListRandom(noSampleStus);
}
// 生成校测学生监测号并插入
retItems.AddRange(noSampleStus.Select((t, i) => new AddExamSampleStudentInput
{
ExamSampleId = examSample.Id,
ExamStudentId = t.ExamStudentId,
ExamNumber = BuildExamNumber(t.SysOrgCode, eg.Grade.GradeNumber, t.ClassNumber, sampleCount + 100 + i + 1),
ExamSampleType = ExamSampleType.SCHOOL_EXAM,
IsSpecialStudent = t.IsSpecialStudent,
PreTotalScore = t.TotalScore ?? 0,
Sequence = i + 1,
}));
}
}
}
return retItems;
}
///
/// 生成监测号
///
/// 学校代码
/// 年级序号
/// 班级序号
/// 顺序号
///
private static string BuildExamNumber(string sysOrgCode, short gradeNumber, short classNumber, int sequence)
{
return $"{sysOrgCode.PadLeft(3, '0')}{gradeNumber.ToString().PadLeft(2, '0')}{classNumber.ToString().PadLeft(2, '0')}{sequence.ToString().PadLeft(4, '0')}";
}
///
/// 获取抽样名单
///
/// 抽样配置
/// 监测年级字典,键为年级ID
/// 学生列表
///
private static List GetSampleList(ExamSampleConfig config, Dictionary examGradeDict, List stus)
{
// 抽样比例
var sampleRate = config.Percent / 100.0;
// 返回结果集
List retItems = [];
// 遍历集合
var ws = stus.Select(t => new { t.SysOrgId, t.SysOrgBranchId, t.GradeId, t.SchoolClassId }).Distinct().ToList();
// 学校
var orgs = ws.Select(t => t.SysOrgId).Distinct().ToList();
foreach (var orgId in orgs)
{
// 跳过不参与监测的学校
if (config.ExcludeSysOrgIds.Contains(orgId))
{
continue;
}
// 校区
var branches = ws.Where(t => t.SysOrgId == orgId).Select(t => t.SysOrgBranchId).Distinct().ToList();
foreach (var branchId in branches)
{
// 年级
var grades = ws.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId).Select(t => t.GradeId).Distinct().ToList();
foreach (var gradeId in grades)
{
// 班级
var classes = ws.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).Select(t => t.SchoolClassId).Distinct().Order().ToList();
var classCount = classes.Count;
// 1.年级自编号直接写入
var eg = examGradeDict[gradeId];
if (eg != null && eg.IsRequiredSelfExamNumber)
{
var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).ToList();
foreach (var item in items)
{
item.ExamSampleType = ExamSampleType.DISTRICT;
item.IsSelfExamNumber = true;
item.ExamNumber = item.ExamNumber.Trim().Replace(" ", "");
//if (item.ExamNumber.Length != eg.SelfExamNumberLength)
//{
// throw Oops.Oh(ErrorCode.E1013, $"{item.ExamStudentId},{item.ExamStudentName},{item.ExamNumber}");
//}
}
retItems.AddRange(items.Where(t => t.ExamNumber.Length >= 4));
continue;
}
// 2.年级只有一个班,处理最低人数限制,满足条件该年级全抽
if (config.IsEnabledOnlyOneClassStudentMin && classCount == 1)
{
var stuCount = stus.Count(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId);
if (stuCount <= config.OnlyOneClassStudentMin)
{
var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).ToList();
for (int i = 0; i < items.Count; i++)
{
items[i].ExamSampleType = ExamSampleType.DISTRICT;
items[i].Sequence = i + 1;
}
retItems.AddRange(items);
continue;
}
}
// 3.年级多于一个班,处理年级未抽样最低限制,满足条件该年级全抽
if (config.IsEnabledGradeNoSampleStudentMin && classCount > 1)
{
var classStuCount = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).GroupBy(t => t.SchoolClassId).Select(t => new { t.Key, Count = t.Count() }).ToList();
if (classStuCount.Sum(t => Math.Ceiling(t.Count * (1 - sampleRate))) <= config.GradeNoSampleStudentMin)
{
var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId).ToList();
for (int i = 0; i < items.Count; i++)
{
items[i].ExamSampleType = ExamSampleType.DISTRICT;
items[i].Sequence = i + 1;
}
retItems.AddRange(items);
continue;
}
}
// 遍历处理各班级
foreach (var classId in classes)
{
// 班级学生数量
var classStuCount = stus.Count(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId);
if (classStuCount == 0)
{
continue;
}
// 4.班级设置为全抽的直接写入
// 5.班级学生人数低于限制
if (config.SampleAllSchoolClassIds.Contains(classId) || (config.IsEnabledClassStudentMin && classStuCount <= config.ClassStudentMin))
{
var items = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId).ToList();
for (int i = 0; i < items.Count; i++)
{
items[i].ExamSampleType = ExamSampleType.DISTRICT;
items[i].Sequence = i + 1;
}
retItems.AddRange(items);
continue;
}
// -------------------------
// 以下是需要抽样的
// -------------------------
// 班级学生列表
var classStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId).ToList();
// 抽中学生列表
List sampleStus = [];
// 该班是否有成绩
var hasScore = classStus.Any(t => t.TotalScore != null && t.TotalScore > 0);
// 最大抽样数量
var maxSampleStuCount = (int)Math.Ceiling(classStuCount * sampleRate);
// 排除特殊学生
if (config.IsExcludeSpecialStudent)
{
// 非特殊学生列表
var normalStus = stus.Where(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId && t.IsSpecialStudent == false).ToList();
// 特殊学生数量
var classSpecialStuCount = stus.Count(t => t.SysOrgId == orgId && t.SysOrgBranchId == branchId && t.GradeId == gradeId && t.SchoolClassId == classId && t.IsSpecialStudent == true);
// 特殊学生数量占比大于等于 1-抽样比例,非特殊学生全抽
if (classSpecialStuCount / classStuCount >= (1 - sampleRate))
{
// 非特殊学生
for (int i = 0; i < normalStus.Count; i++)
{
normalStus[i].ExamSampleType = ExamSampleType.DISTRICT;
normalStus[i].Sequence = i + 1;
}
retItems.AddRange(normalStus);
}
// 仅非特殊学生参与抽样
else
{
// 有成绩的循环抽样
if (hasScore && !config.IsRandomSampling)
{
int sq = 1;
CyclicSampling(config, normalStus, sampleStus, maxSampleStuCount, sq);
retItems.AddRange(sampleStus);
}
// 无成绩随机抽样
else
{
sampleStus = RandomSampling(normalStus, classStuCount, sampleRate);
retItems.AddRange(sampleStus);
}
}
}
// 不排除特殊学生
else
{
// 有成绩的循环抽样
if (hasScore && !config.IsRandomSampling)
{
int sq = 1;
CyclicSampling(config, classStus, sampleStus, maxSampleStuCount, sq);
retItems.AddRange(sampleStus);
}
// 无成绩随机抽样
else
{
sampleStus = RandomSampling(classStus, classStuCount, sampleRate);
retItems.AddRange(sampleStus);
}
}
// 未抽中学生列表
var noSampleStus = (from s in classStus where !(from n in sampleStus select n.ExamStudentId).Contains(s.ExamStudentId) select s).ToList();
for (int i = 0; i < noSampleStus.Count; i++)
{
noSampleStus[i].ExamSampleType = ExamSampleType.SCHOOL_EXAM;
noSampleStus[i].Sequence = i + 1;
}
retItems.AddRange(noSampleStus);
}
}
}
}
return retItems;
}
///
/// 循环抽样
///
///
///
///
///
///
private static void CyclicSampling(ExamSampleConfig config, List stus, List sampleStus, int maxStuCount, int sequence)
{
var interval = config.Interval + 1;
for (int i = config.StartPosition >= stus.Count ? 0 : config.StartPosition - 1; i < stus.Count; i += interval)
{
var stu = stus[i];
var nstus = stu.Adapt();
nstus.Sequence = sequence++;
nstus.ExamSampleType = ExamSampleType.DISTRICT;
sampleStus.Add(nstus);
// 如果已抽满结束抽样
if (sampleStus.Count >= maxStuCount)
{
break;
}
}
if (sampleStus.Count < maxStuCount)
{
var noSampleStus = (from s in stus where !(from n in sampleStus select n.ExamStudentId).Contains(s.ExamStudentId) select s).ToList();
CyclicSampling(config, noSampleStus, sampleStus, maxStuCount, sequence);
}
}
///
/// 随机抽样
///
///
///
///
///
private static List RandomSampling(List stus, int classStuCount, double sampleRate)
{
List sampleStus = [];
var sampleIndexList = GetRandomIndex(classStuCount, sampleRate);
int sq = 1;
for (int i = 0; i < stus.Count; i++)
{
if (sampleIndexList.Contains(i))
{
var item = stus[i].Adapt();
item.ExamSampleType = ExamSampleType.DISTRICT;
item.Sequence = sq++;
sampleStus.Add(item);
}
}
return sampleStus;
}
///
/// 获取随机位置列表
///
/// 总数量
/// 抽样比例
///
private static List GetRandomIndex(int totalCount, double sampleRate)
{
Random random = new();
List samples = [];
int i = 0;
var sc = Math.Ceiling(totalCount * sampleRate);
while (i < sc)
{
int s = random.Next(totalCount);
if (samples.Any(t => t == s))
{
continue;
}
samples.Add(s);
i++;
}
return samples.OrderBy(t => t).ToList();
}
///
/// 获取学生并带往期成绩
///
/// 抽样配置
/// 成绩引用监测计划ID
/// 抽样监测计划ID
/// 年级ID列表
///
private async Task> GetStudentScoreList(ExamSampleConfig config, int? examScoreRefExamPlanId, int examPlanId, List grades)
{
string gradeWhere = string.Join(" OR ", grades.Select(t => $"T1.grade_id = {t}"));
string selectSql = $@"
SELECT
T1.id AS exam_student_id,
T1.`name` AS exam_student_name,
T1.certificate_type,
UPPER(T1.id_number) AS id_number,
T1.sys_org_id,
ORG.`code` AS sys_org_code,
T1.sys_org_branch_id,
T1.grade_id,
T1.school_class_id,
T1.class_number,
T1.exam_number,
T2.course_count,
T2.total_score,
CASE WHEN ISNULL(T3.id) THEN 0 ELSE 1 END as is_special_student
FROM exam_student AS T1
LEFT JOIN
(
-- 往期总分
SELECT T1.sys_org_id, T1.school_class_id, T2.certificate_type, UPPER(T2.id_number) AS id_number, COUNT(T1.id) AS course_count, SUM(T1.score) AS total_score
FROM exam_score AS T1
JOIN exam_student AS T2 ON T1.exam_plan_id = T2.exam_plan_id AND T1.exam_student_id = T2.id
WHERE T1.exam_plan_id = @examScoreRefExamPlanId
GROUP BY T1.sys_org_id, T1.school_class_id, T2.certificate_type, T2.id_number
) AS T2
ON T1.sys_org_id = T2.sys_org_id AND T1.school_class_id = T2.school_class_id AND UPPER(T1.id_number) = T2.id_number
JOIN
(
SELECT DISTINCT exam_plan_id, sys_org_id
FROM exam_org
WHERE exam_plan_id = @examPlanId AND is_required_exam = 1
) AS EO ON T1.exam_plan_id = EO.exam_plan_id AND T1.sys_org_id = EO.sys_org_id
JOIN sys_org AS ORG ON T1.sys_org_id = ORG.id
LEFT JOIN
(
SELECT id, exam_plan_id, sys_org_id, certificate_type, id_number
FROM exam_special_student
WHERE exam_plan_id = @examPlanId AND `status` = 3
) AS T3 ON T1.exam_plan_id = T3.exam_plan_id AND T1.sys_org_id = T3.sys_org_id AND UPPER(T1.id_number) = UPPER(T3.id_number)
WHERE T1.exam_plan_id = @examPlanId AND ({gradeWhere})
ORDER BY T1.sys_org_id, T1.grade_id, T1.school_class_id, T2.total_score DESC, T1.id
";
if (!config.SpecialStudentMustApproved)
{
selectSql = $@"
SELECT
T1.id AS exam_student_id,
T1.`name` AS exam_student_name,
T1.certificate_type,
UPPER(T1.id_number) AS id_number,
T1.sys_org_id,
ORG.`code` AS sys_org_code,
T1.sys_org_branch_id,
T1.grade_id,
T1.school_class_id,
T1.class_number,
T1.exam_number,
T2.course_count,
T2.total_score,
CASE WHEN ISNULL(T3.id) THEN 0 ELSE 1 END as is_special_student
FROM exam_student AS T1
LEFT JOIN
(
-- 往期总分
SELECT T1.sys_org_id, T1.school_class_id, T2.certificate_type, UPPER(T2.id_number) AS id_number, COUNT(T1.id) AS course_count, SUM(T1.score) AS total_score
FROM exam_score AS T1
JOIN exam_student AS T2 ON T1.exam_plan_id = T2.exam_plan_id AND T1.exam_student_id = T2.id
WHERE T1.exam_plan_id = @examScoreRefExamPlanId
GROUP BY T1.sys_org_id, T1.school_class_id, T2.certificate_type, T2.id_number
) AS T2
ON T1.sys_org_id = T2.sys_org_id AND T1.school_class_id = T2.school_class_id AND UPPER(T1.id_number) = T2.id_number
JOIN
(
SELECT DISTINCT exam_plan_id, sys_org_id
FROM exam_org
WHERE exam_plan_id = @examPlanId AND is_required_exam = 1
) AS EO ON T1.exam_plan_id = EO.exam_plan_id AND T1.sys_org_id = EO.sys_org_id
JOIN sys_org AS ORG ON T1.sys_org_id = ORG.id
LEFT JOIN
(
SELECT T1.id, T1.exam_plan_id, T1.sys_org_id, T1.certificate_type, T1.id_number
FROM exam_special_student AS T1
LEFT JOIN
(
SELECT sys_org_id FROM exam_org_data_report WHERE exam_plan_id = @examPlanId AND type = 2 AND `status` = 3
) AS T2 ON T1.sys_org_id = T2.sys_org_id
WHERE T1.exam_plan_id = @examPlanId AND ((T2.sys_org_id IS NULL AND T1.`status` = 3) OR (T2.sys_org_id IS NOT NULL AND (T1.`status` = 2 OR T1.`status` = 3)))
) AS T3 ON T1.exam_plan_id = T3.exam_plan_id AND T1.sys_org_id = T3.sys_org_id AND UPPER(T1.id_number) = UPPER(T3.id_number)
WHERE T1.exam_plan_id = @examPlanId AND ({gradeWhere})
ORDER BY T1.sys_org_id, T1.grade_id, T1.school_class_id, T2.total_score DESC, T1.id
";
}
var items = await rep.SqlQueriesAsync(selectSql, new
{
ExamScoreRefExamPlanId = examScoreRefExamPlanId ?? 0,
ExamPlanId = examPlanId,
});
return items;
}
#endregion
}