using Furion.DatabaseAccessor.Extensions;
using YBEE.EQM.Core;
namespace YBEE.EQM.Application;
///
/// 试卷管理服务
///
public class ExamPaperService : IExamPaperService, ITransient
{
private readonly IRepository _repExamPaper;
private readonly ISysRoleService _sysRoleService;
private readonly IExportExcelService _exportExcelService;
public ExamPaperService(IRepository repExamPaper, ISysRoleService sysRoleService, IExportExcelService exportExcelService)
{
_repExamPaper = repExamPaper;
_sysRoleService = sysRoleService;
_exportExcelService = exportExcelService;
}
///
/// 按监测计划初始化试卷
///
///
///
public async Task BatchInit(ExamPaperBatchInitInput input)
{
var examCourses = await _repExamPaper.Change().DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId).ProjectToType().ToListAsync();
if (!examCourses.Any())
{
}
var eitems = await _repExamPaper.DetachedEntities.Where(t => t.ExamPlanId == input.ExamPlanId).ToListAsync();
var ecs = from t in examCourses
where !(from p in eitems select new { p.GradeId, p.CourseId }).Contains(new { t.GradeId, t.CourseId })
select t;
if (ecs.Any())
{
foreach (var ec in ecs)
{
var p = new ExamPaper
{
ExamPlanId = input.ExamPlanId,
ExamGradeId = ec.ExamGradeId,
GradeId = ec.GradeId,
ExamCourseId = ec.Id,
CourseId = ec.CourseId,
Score = ec.TotalScore,
Name = $"{ec.ExamGrade.Grade.FullName}{ec.Course.Name}",
};
await _repExamPaper.InsertNowAsync(p);
if (ec.ScoreReportConfig.HeaderColumnNames.Count > ec.ScoreReportConfig.MinorQuestionColumnIndex + 1)
{
List mitems = new();
for (int i = ec.ScoreReportConfig.MinorQuestionColumnIndex; i < ec.ScoreReportConfig.HeaderColumnNames.Count; i++)
{
var columnName = ec.ScoreReportConfig.HeaderColumnNames[i];
var pmi = new ExamPaperQuestionMinor()
{
ExamPaperId = p.Id,
Sequence = i - ec.ScoreReportConfig.MinorQuestionColumnIndex + 1,
ColumnName = columnName,
Name = columnName.Replace("第", "").Replace("题", "").Trim(),
};
mitems.Add(pmi);
}
await _repExamPaper.Change().InsertNowAsync(mitems);
}
}
}
}
///
/// 分配双向细目表编制人
///
///
///
public async Task AssignTwclWriter(AssignExamPaperWriterInput input)
{
var items = await _repExamPaper.Where(t => input.Ids.Contains(t.Id)).ToListAsync();
foreach (var item in items)
{
item.TwclSysUserId = input.WriterSysUserId;
await item.UpdateIncludeNowAsync(new[] { nameof(item.TwclSysUserId) });
}
}
///
/// 分配问题建议撰写人
///
///
///
public async Task AssignSuggestionWriter(AssignExamPaperWriterInput input)
{
var items = await _repExamPaper.Where(t => input.Ids.Contains(t.Id)).ToListAsync();
foreach (var item in items)
{
item.SuggestionSysUserId = input.WriterSysUserId;
await item.UpdateIncludeNowAsync(new[] { nameof(item.SuggestionSysUserId) });
}
}
///
/// 根据ID获取试卷详情
///
///
///
public async Task GetById(int id)
{
var item = await _repExamPaper.DetachedEntities.ProjectToType().FirstOrDefaultAsync(t => t.Id == id);
return item;
}
///
/// 根据监测计划ID获取试卷列表(管理端)
///
///
///
public async Task> GetListByExamPlanId(int examPlanId)
{
var items = await _repExamPaper.DetachedEntities
.Where(t => t.ExamPlanId == examPlanId)
.OrderBy(t => t.GradeId).ThenBy(t => t.CourseId)
.ProjectToType().ToListAsync();
SetSubmitStatus(items);
return items;
}
///
/// 获取双向细目表监测计划列表(管理端)
///
///
///
public async Task> QueryExamPlanPageList(ExamPlanPageInput input)
{
List whereCause = new();
if (!string.IsNullOrEmpty(input.Name?.Trim()))
{
whereCause.Add("T3.full_name LIKE '%@name%'");
}
if (input.SemesterId.HasValue)
{
whereCause.Add("T3.semester_id = @semesterId");
}
if (input.Status.HasValue)
{
whereCause.Add("T3.status = @status");
}
string whereSql = $"WHERE (T3.`status` = {(short)ExamStatus.ACTIVE} OR T3.`status` = {(short)ExamStatus.STOPPED}) AND T3.is_deleted = 0";
if (whereCause.Count > 0)
{
whereSql = $"{whereSql} AND {string.Join(" AND ", whereCause)}";
}
var p = new
{
input.PageSize,
PageOffset = (input.PageIndex - 1) * input.PageSize,
input.SemesterId,
input.Status,
input.Name,
};
var totalCount = await _repExamPaper.SqlScalarAsync($@"
SELECT COUNT(1) AS total_count
FROM
(
SELECT T1.exam_plan_id, COUNT(T1.id) AS total_count
FROM exam_paper AS T1
JOIN exam_plan AS T3 ON T1.exam_plan_id = T3.id
{whereSql}
GROUP BY T1.exam_plan_id
) AS T
", p);
string select = $@"
SELECT
T1.exam_plan_id,
T3.full_name AS exam_plan_full_name,
T3.`name` AS exam_plan_name,
T3.semester_id,
T3.`status` AS exam_plan_status,
COUNT(T1.id) AS total_count,
COUNT(CASE WHEN T1.`twcl_status` = {(short)AuditStatus.UNSUBMIT} OR T1.`twcl_status` = {(short)AuditStatus.REJECTED} THEN T1.id ELSE NULL END) AS twcl_unsubmit_count,
COUNT(CASE WHEN T1.`twcl_status` = {(short)AuditStatus.AUDIT} OR T1.`twcl_status` = {(short)AuditStatus.APPROVED} OR T1.`twcl_status` = {(short)AuditStatus.APPROVE_CANCELED} THEN T1.id ELSE NULL END) AS twcl_submitted_count,
COUNT(CASE WHEN T1.`suggestion_status` = {(short)AuditStatus.UNSUBMIT} OR T1.`suggestion_status` = {(short)AuditStatus.REJECTED} THEN T1.id ELSE NULL END) AS suggestion_unsubmit_count,
COUNT(CASE WHEN T1.`suggestion_status` = {(short)AuditStatus.AUDIT} OR T1.`suggestion_status` = {(short)AuditStatus.APPROVED} OR T1.`suggestion_status` = {(short)AuditStatus.APPROVE_CANCELED} THEN T1.id ELSE NULL END) AS suggestion_submitted_count
FROM exam_paper AS T1
JOIN exam_plan AS T3 ON T1.exam_plan_id = T3.id
{whereSql}
GROUP BY T1.exam_plan_id, T3.full_name, T3.`name`, T3.semester_id, T3.`status`
ORDER BY T3.semester_id DESC, T1.exam_plan_id DESC
LIMIT @pageSize OFFSET @pageOffset
";
var items = await _repExamPaper.SqlQueriesAsync(select, p);
PageResult ret = new()
{
PageIndex = input.PageIndex,
PageSize = input.PageSize,
TotalCount = totalCount,
Items = items
};
return ret;
}
///
/// 导出TQES导入文件格式文件包
///
///
///
///
public async Task<(string, byte[])> ExportTqesFile(int examPlanId)
{
var examPlan = await _repExamPaper.Change().DetachedEntities.ProjectToType().FirstOrDefaultAsync(t => t.Id == examPlanId) ?? throw Oops.Oh(ErrorCode.E2001);
// 临时存放目录
string fileRoot = Path.Combine(FileUtil.GetTempFileRoot(), $"{Guid.NewGuid()}");
Directory.CreateDirectory(fileRoot);
string filePath = Path.Combine(fileRoot, $"{examPlan.Name}-双向细目表");
Directory.CreateDirectory(filePath);
try
{
// 定义EXCEL列
List> cols = new()
{
new() { Name = "大题号", Width = 10, GetCellValue = (r) => r.ExamPaperQuestionMajor?.Number ?? "" },
new() { Name = "小题号", Width = 18, GetCellValue = (r) => r.ColumnName, Align = ExportExcelCellAlign.LEFT },
new() { Name = "分值", Width = 8, GetCellValue = (r) => r.Score },
new() { Name = "题型", Width = 10, GetCellValue = (r) => r.QuestionCatalog?.GetDescription() ?? "" },
new() { Name = "知识模块", Width = 30, GetCellValue = (r) => r.KnowledgeModule, Align = ExportExcelCellAlign.LEFT },
new() { Name = "知识点", Width = 60, GetCellValue = (r) => r.KnowledgePoint, Align = ExportExcelCellAlign.LEFT },
new() { Name = "认知能力", Width = 10, GetCellValue = (r) => r.CognitiveAbility?.GetDescription() ?? "" },
new() { Name = "预估难度", Width = 10, GetCellValue = (r) => r.EstimatedDifficulty },
new() { Name = "是否必答", Width = 10, GetCellValue = (r) => r.IsChoose ? "否" : "是" },
new() { Name = "备注", Width = 20, GetCellValue = (r) => null, Align = ExportExcelCellAlign.LEFT },
};
var ps = await _repExamPaper.DetachedEntities.Where(t => t.ExamPlanId == examPlanId).ProjectToType().ToListAsync();
foreach (var p in ps)
{
var items = await _repExamPaper.Change().DetachedEntities.Where(t => t.ExamPaperId == p.Id && t.IsLeaf == true).ProjectToType().ToListAsync();
var bs = _exportExcelService.ExportExcel(new ExportExcelDto()
{
IsXlsx = false,
Title = null,
Columns = cols,
Items = items,
IncludeExportTime = false,
});
await File.WriteAllBytesAsync(Path.Combine(filePath, $"{examPlan.EducationStage.GetDescription()}-{p.Grade.Name}-{p.Course.Name}-双向细目表.xls"), bs);
}
string outFileName = $"{examPlan.Name}-双向细目表.zip";
string outFilePath = Path.Combine(fileRoot, outFileName);
ICSharpCode.SharpZipLib.Zip.FastZip zip = new();
zip.CreateZip(outFilePath, filePath, true, string.Empty);
var retBytes = await File.ReadAllBytesAsync(outFilePath);
return (outFileName, retBytes);
}
catch (Exception ex)
{
throw new Exception("导出错误", ex);
}
finally
{
Directory.Delete(fileRoot, true);
}
}
#region 编撰者
///
/// 分页查询编撰人监测计划列表
///
///
///
public async Task> QueryWriterExamPlanPageList(ExamPaperExamPlanPageInput input)
{
List whereCause = new();
if (!string.IsNullOrEmpty(input.Name?.Trim()))
{
whereCause.Add("T3.full_name LIKE '%@name%'");
}
if (input.SemesterId.HasValue)
{
whereCause.Add("T3.semester_id = @semesterId");
}
if (input.Status.HasValue)
{
whereCause.Add("T3.status = @status");
}
string prefix = input.WriterType == ExamPaperWriterType.TWCL ? "twcl" : "suggestion";
string whereSql = $"WHERE T1.{prefix}_sys_user_id = {CurrentSysUserInfo.SysUserId} AND (T3.`status` = {(short)ExamStatus.ACTIVE} OR T3.`status` = {(short)ExamStatus.STOPPED}) AND T3.is_deleted = 0";
if (whereCause.Count > 0)
{
whereSql = $"{whereSql} AND {string.Join(" AND ", whereCause)}";
}
var p = new
{
input.PageSize,
PageOffset = (input.PageIndex - 1) * input.PageSize,
input.SemesterId,
input.Status,
input.Name,
};
var totalCount = await _repExamPaper.SqlScalarAsync($@"
SELECT COUNT(1) AS total_count
FROM
(
SELECT T1.exam_plan_id, COUNT(T1.id) AS total_count
FROM exam_paper AS T1
JOIN exam_plan AS T3 ON T1.exam_plan_id = T3.id
{whereSql}
GROUP BY T1.exam_plan_id
) AS T
", p);
string select = $@"
SELECT
T1.exam_plan_id,
T3.full_name AS exam_plan_full_name,
T3.`name` AS exam_plan_name,
T3.semester_id,
T3.`status` AS exam_plan_status,
COUNT(T1.id) AS total_count,
COUNT(CASE WHEN T1.`{prefix}_status` = {(short)AuditStatus.UNSUBMIT} OR T1.`{prefix}_status` = {(short)AuditStatus.REJECTED} THEN T1.id ELSE NULL END) AS {prefix}_unsubmit_count,
COUNT(CASE WHEN T1.`{prefix}_status` = {(short)AuditStatus.AUDIT} OR T1.`{prefix}_status` = {(short)AuditStatus.APPROVED} OR T1.`{prefix}_status` = {(short)AuditStatus.APPROVE_CANCELED} THEN T1.id ELSE NULL END) AS {prefix}_submitted_count
FROM exam_paper AS T1
JOIN exam_plan AS T3 ON T1.exam_plan_id = T3.id
{whereSql}
GROUP BY T1.exam_plan_id, T3.full_name, T3.`name`, T3.semester_id, T3.`status`
ORDER BY T3.semester_id DESC, T1.exam_plan_id DESC
LIMIT @pageSize OFFSET @pageOffset
";
var items = await _repExamPaper.SqlQueriesAsync(select, p);
PageResult ret = new()
{
PageIndex = input.PageIndex,
PageSize = input.PageSize,
TotalCount = totalCount,
Items = items
};
return ret;
}
///
/// 根据监测计划ID获取待处理试卷列表
///
///
///
///
public async Task> GetWriterListByExamPlanId(int examPlanId, ExamPaperWriterType writerType)
{
var query = _repExamPaper.DetachedEntities.Where(t => t.ExamPlanId == examPlanId);
if (writerType == ExamPaperWriterType.TWCL)
{
query = query.Where(t => t.TwclSysUserId == CurrentSysUserInfo.SysUserId);
}
else
{
query = query.Where(t => t.SuggestionSysUserId == CurrentSysUserInfo.SysUserId);
}
var items = await query.ProjectToType().ToListAsync();
SetSubmitStatus(items);
return items;
}
///
/// 保存问题建议
///
///
///
public async Task SaveSuggestion(SaveExamPaperSuggestion input)
{
var item = await _repExamPaper.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001, "监测科目");
item.Questions = input.Questions;
item.Suggestions = input.Suggestions;
await item.UpdateIncludeAsync(new[] { nameof(item.Questions), nameof(item.Suggestions) });
}
///
/// 提交双向细目表
///
///
///
public async Task SubmitTwcl(BaseId input)
{
var item = await _repExamPaper.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001, "试卷");
var nc = await _repExamPaper.Change().CountAsync(t => t.ExamPaperId == input.Id && (!t.QuestionCatalog.HasValue || !t.CognitiveAbility.HasValue || t.EstimatedDifficulty <= 0 || t.KnowledgeModule.Trim() == "" || t.KnowledgePoint.Trim() == ""));
if (nc > 0)
{
throw Oops.Oh(ErrorCode.E3002, nc);
}
item.TwclStatus = AuditStatus.APPROVED;
item.TwclAudits = AuditUtil.InsertInto(item.TwclAudits, AuditUtil.CreateNew(AuditActionType.SUBMIT, item.TwclStatus));
await item.UpdateAsync();
}
///
/// 提交问题建议
///
///
///
public async Task SubmitSuggestion(BaseId input)
{
var item = await _repExamPaper.FirstOrDefaultAsync(t => t.Id == input.Id) ?? throw Oops.Oh(ErrorCode.E2001, "试卷");
if (item.Questions.Trim() == "" || item.Suggestions.Trim() == "")
{
throw Oops.Oh(ErrorCode.E3003);
}
item.SuggestionStatus = AuditStatus.APPROVED;
item.SuggestionAudits = AuditUtil.InsertInto(item.SuggestionAudits, AuditUtil.CreateNew(AuditActionType.SUBMIT, item.SuggestionStatus));
await item.UpdateAsync();
}
#endregion
private static void SetSubmitStatus(List items)
{
foreach (var item in items)
{
item.TwclSubmitted = item.TwclStatus == AuditStatus.AUDIT || item.TwclStatus == AuditStatus.APPROVED || item.TwclStatus == AuditStatus.APPROVE_CANCELED;
item.SuggestionSubmitted = item.SuggestionStatus == AuditStatus.AUDIT || item.SuggestionStatus == AuditStatus.APPROVED || item.SuggestionStatus == AuditStatus.APPROVE_CANCELED;
}
}
}