逻辑调整

This commit is contained in:
fuenmao
2026-08-25 10:20:53 +08:00
parent 4679bd6c13
commit 085bf5e2ab
36 changed files with 2770 additions and 1209 deletions
@@ -1,92 +1,95 @@
namespace SimpleAdmin.Application;
/// <summary>
/// 实现员工端入职资料提交业务能力。
/// 实现员工端动态资料、上传材料、合同确认和最终提交能力。
/// </summary>
public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionService
{
private readonly SqlSugarScope _db;
private readonly IOnboardingContractSnapshotService _snapshotService;
public OnboardingPublicSubmissionService(
IOnboardingContractSnapshotService snapshotService)
public OnboardingPublicSubmissionService(IOnboardingContractSnapshotService snapshotService)
{
_db = DbContext.DB;
_snapshotService = snapshotService;
}
/// <summary>
/// 保存候选人填写的动态字段值。
/// 保存候选人填写的动态补充字段值。
/// </summary>
public async Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input)
{
var task = await GetEditableTask(input.Token);
var requirements = await GetEffectiveFormRequirements(task);
var requirements = await GetFormRequirements(task.Id);
var fieldIds = input.Values.Select(item => item.FieldId).Distinct().ToList();
var hasInapplicableField = fieldIds.Any(id => requirements.All(requirement => requirement.FieldId != id));
var hasInapplicableField = fieldIds.Any(id => requirements.All(item => item.FieldId != id));
if (hasInapplicableField)
throw Oops.Bah("填写字段不适用于当前公司或岗位");
throw Oops.Bah("填写字段不属于当前入职模板");
var fields = await _db.Queryable<ObFormField>()
.Where(field => fieldIds.Contains(field.Id) && field.Status == "enable")
.ToListAsync();
foreach (var value in input.Values)
{
var field = fields.FirstOrDefault(item => item.Id == value.FieldId)
?? throw Oops.Bah("填写字段不存在或已停用");
var requirement = requirements.FirstOrDefault(item => item.FieldId == value.FieldId)
?? throw Oops.Bah("动态补充字段不存在");
var old = await _db.Queryable<ObTaskFormValue>()
.FirstAsync(item => item.TaskId == task.Id && item.FieldId == field.Id);
.FirstAsync(item => item.TaskId == task.Id && item.FieldId == requirement.FieldId);
var row = new ObTaskFormValue
{
Id = old?.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
FieldId = field.Id,
FieldCode = field.FieldCode,
FieldName = field.FieldName,
FieldId = requirement.FieldId,
FieldCode = requirement.FieldCode,
FieldName = requirement.FieldName,
FieldValue = value.FieldValue,
SubmitRound = task.SubmitCount + 1
};
if (old == null)
{
await _db.Insertable(row).ExecuteCommandAsync();
}
else
{
await _db.Updateable(row).ExecuteCommandAsync();
}
}
await _snapshotService.Build(task.Id, invalidateGeneratedPreview: true);
}
/// <summary>
/// 关联候选人已经上传的系统文件与材料类型。
/// 关联已上传 OSS 文件与任务材料要求。
/// </summary>
public async Task SaveMaterial(OnboardingSaveMaterialInput input)
{
var task = await GetEditableTask(input.Token);
var requirements = await GetEffectiveMaterialRequirements(task);
var materialIsApplicable = requirements.Any(requirement => requirement.MaterialTypeId == input.MaterialTypeId);
if (!materialIsApplicable)
throw Oops.Bah("资料类型不适用于当前公司或岗位");
var material = await _db.Queryable<ObMaterialType>()
.FirstAsync(item => item.Id == input.MaterialTypeId && item.Status == "enable")
?? throw Oops.Bah("资料类型不存在或已停用");
var requirement = await _db.Queryable<ObTaskMaterialRequirement>()
.FirstAsync(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId)
?? throw Oops.Bah("材料类型不属于当前入职模板");
var fileExists = await _db.Queryable<SysFile>().AnyAsync(item => item.Id == input.FileId);
if (!fileExists)
throw Oops.Bah("上传文件不存在");
var materialCount = await _db.Queryable<ObTaskMaterial>()
.CountAsync(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId);
if (materialCount >= material.MaxFileCount)
throw Oops.Bah("该资料已达到最大上传数量");
var existingMaterials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId)
.ToListAsync();
var supplementMaterial = existingMaterials.FirstOrDefault(
item => item.ReviewStatus == TaskMaterialReviewStatus.NeedSupplement);
if (supplementMaterial != null)
{
supplementMaterial.FileId = input.FileId;
supplementMaterial.SubmitRound = task.SubmitCount + 1;
supplementMaterial.UploadedAt = DateTime.Now;
supplementMaterial.ReviewStatus = TaskMaterialReviewStatus.Pending;
supplementMaterial.ReviewRemark = null;
await _db.Updateable(supplementMaterial).ExecuteCommandAsync();
return;
}
if (existingMaterials.Count >= requirement.MaxFileCount)
throw Oops.Bah("该材料已达到最大上传数量");
await _db.Insertable(new ObTaskMaterial
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
MaterialTypeId = material.Id,
MaterialName = material.MaterialName,
MaterialTypeId = requirement.MaterialTypeId,
MaterialName = requirement.MaterialName,
FileId = input.FileId,
SubmitRound = task.SubmitCount + 1,
UploadedAt = DateTime.Now
@@ -94,7 +97,7 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
}
/// <summary>
/// 记录候选人已阅读并确认指定任务合同。
/// 记录候选人已经查看并确认动态生成的合同。
/// </summary>
public async Task ConfirmContract(OnboardingConfirmContractInput input)
{
@@ -102,15 +105,16 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var contract = await _db.Queryable<ObTaskContract>()
.FirstAsync(item => item.Id == input.TaskContractId && item.TaskId == task.Id)
?? throw Oops.Bah("合同不存在");
if (!contract.GeneratedDocxFileId.HasValue || !contract.ReadAt.HasValue)
throw Oops.Bah("请先查看已经生成的合同内容");
contract.ReadAt ??= DateTime.Now;
contract.ConfirmedAt = DateTime.Now;
contract.ContractStatus = TaskContractStatus.Confirmed;
await _db.Updateable(contract).ExecuteCommandAsync();
}
/// <summary>
/// 校验全部必填资料后提交入职任务进入审核。
/// 校验固定信息、动态字段、材料、合同变量和合同确认后提交审核。
/// </summary>
public async Task Submit(OnboardingSubmitInput input)
{
@@ -126,8 +130,7 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
await _snapshotService.Build(task.Id, info);
// 提交时统一完成动态字段、材料、合同变量和合同确认校验,保存阶段只负责暂存。
var requiredFields = (await GetEffectiveFormRequirements(task))
var requiredFields = (await GetFormRequirements(task.Id))
.Where(item => item.Required)
.Select(item => item.FieldId)
.ToList();
@@ -137,9 +140,9 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var hasMissingRequiredField = requiredFields.Any(
id => values.All(item => item.FieldId != id || string.IsNullOrWhiteSpace(item.FieldValue)));
if (hasMissingRequiredField)
throw Oops.Bah("仍有必填动态信息未填写");
throw Oops.Bah("仍有必填补充信息未填写");
var requiredMaterials = (await GetEffectiveMaterialRequirements(task))
var requiredMaterials = (await GetMaterialRequirements(task.Id))
.Where(item => item.Required)
.Select(item => item.MaterialTypeId)
.ToList();
@@ -149,14 +152,14 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
var hasMissingRequiredMaterial = requiredMaterials.Any(
id => materials.All(item => item.MaterialTypeId != id));
if (hasMissingRequiredMaterial)
throw Oops.Bah("仍有必传资料未上传");
throw Oops.Bah("仍有必传材料未上传");
await EnsureRequiredContractVariables(task);
var hasUnconfirmedRequiredContract = await _db.Queryable<ObTaskContract>()
.AnyAsync(item => item.TaskId == task.Id && item.Required && item.ConfirmedAt == null);
if (hasUnconfirmedRequiredContract)
throw Oops.Bah("请确认阅读全部必签合同");
throw Oops.Bah("请查看并确认全部必签合同");
task.TaskStatus = OnboardingTaskStatus.Reviewing;
task.SubmitCount++;
@@ -177,36 +180,34 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
}
/// <summary>
/// 在保存文件前校验材料适用范围、扩展名、大小和数量。
/// 上传 OSS 前按照任务材料快照校验扩展名、文件大小和数量。
/// </summary>
public async Task ValidateMaterialUpload(OnboardingMaterialUploadValidationInput input)
{
var task = await GetEditableTask(input.Token);
var requirements = await GetEffectiveMaterialRequirements(task);
var materialIsApplicable = requirements.Any(requirement => requirement.MaterialTypeId == input.MaterialTypeId);
if (!materialIsApplicable)
throw Oops.Bah("资料类型不适用于当前公司或岗位");
var material = await _db.Queryable<ObMaterialType>()
.FirstAsync(item => item.Id == input.MaterialTypeId && item.Status == "enable")
?? throw Oops.Bah("资料类型不存在或已停用");
var requirement = await _db.Queryable<ObTaskMaterialRequirement>()
.FirstAsync(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId)
?? throw Oops.Bah("材料类型不属于当前入职模板");
var suffix = Path.GetExtension(input.FileName).TrimStart('.').ToLowerInvariant();
var allowedExtensions = (material.AllowedExtensions ?? string.Empty)
var allowedExtensions = (requirement.AllowedExtensions ?? string.Empty)
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(item => item.TrimStart('.').ToLowerInvariant());
var suffixNotAllowed = allowedExtensions.Any() && !allowedExtensions.Contains(suffix);
if (suffixNotAllowed)
throw Oops.Bah("该资料不允许上传此文件类型");
throw Oops.Bah("该材料不允许上传此文件类型");
var fileTooLarge = material.MaxFileSizeMb.HasValue
&& input.FileLength > material.MaxFileSizeMb.Value * 1024L * 1024L;
var fileTooLarge = requirement.MaxFileSizeMb.HasValue
&& input.FileLength > requirement.MaxFileSizeMb.Value * 1024L * 1024L;
if (fileTooLarge)
throw Oops.Bah("文件超过允许的大小");
var materialCount = await _db.Queryable<ObTaskMaterial>()
.CountAsync(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId);
if (materialCount >= material.MaxFileCount)
throw Oops.Bah("该资料已达到最大上传数量");
var materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id && item.MaterialTypeId == input.MaterialTypeId)
.ToListAsync();
var canReplaceSupplement = materials.Any(
item => item.ReviewStatus == TaskMaterialReviewStatus.NeedSupplement);
if (materials.Count >= requirement.MaxFileCount && !canReplaceSupplement)
throw Oops.Bah("该材料已达到最大上传数量");
}
private async Task EnsureRequiredContractVariables(ObOnboardingTask task)
@@ -234,30 +235,20 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
throw Oops.Bah($"合同“{missing.contract.ContractName}”的必填变量“{missing.definition.VariableName}”尚未填写");
}
private async Task<List<ObFormRequirement>> GetEffectiveFormRequirements(ObOnboardingTask task)
private Task<List<ObTaskFormRequirement>> GetFormRequirements(long taskId)
{
var requirements = await _db.Queryable<ObFormRequirement>()
.Where(item => item.CompanyId == task.CompanyId
&& (item.PositionId == null || item.PositionId == task.PositionId))
return _db.Queryable<ObTaskFormRequirement>()
.Where(item => item.TaskId == taskId)
.OrderBy(item => item.SortCode)
.ToListAsync();
return requirements
.GroupBy(item => item.FieldId)
.Select(group => group.FirstOrDefault(item => item.PositionId == task.PositionId) ?? group.First())
.ToList();
}
private async Task<List<ObMaterialRequirement>> GetEffectiveMaterialRequirements(ObOnboardingTask task)
private Task<List<ObTaskMaterialRequirement>> GetMaterialRequirements(long taskId)
{
var requirements = await _db.Queryable<ObMaterialRequirement>()
.Where(item => item.CompanyId == task.CompanyId
&& (item.PositionId == null || item.PositionId == task.PositionId))
return _db.Queryable<ObTaskMaterialRequirement>()
.Where(item => item.TaskId == taskId)
.OrderBy(item => item.SortCode)
.ToListAsync();
return requirements
.GroupBy(item => item.MaterialTypeId)
.Select(group => group.FirstOrDefault(item => item.PositionId == task.PositionId) ?? group.First())
.ToList();
}
private async Task<ObOnboardingTask> GetEditableTask(string token)
@@ -283,4 +274,4 @@ public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionServ
return task;
}
}
}