This commit is contained in:
fuenmao
2026-08-24 14:26:11 +08:00
parent 01328d567c
commit a51d45372d
54 changed files with 2607 additions and 119 deletions
@@ -0,0 +1,9 @@
namespace SimpleAdmin.Application;
public class OnboardingAuditActionInput
{
[IdNotNull(ErrorMessage = "任务Id不能为空")]
public long TaskId { get; set; }
public string AuditOpinion { get; set; }
}
@@ -0,0 +1,7 @@
namespace SimpleAdmin.Application;
public interface IOnboardingAuditService : ITransient
{
Task Approve(OnboardingAuditActionInput input);
Task Reject(OnboardingAuditActionInput input);
}
@@ -0,0 +1,129 @@
namespace SimpleAdmin.Application;
public class OnboardingAuditService : IOnboardingAuditService
{
private readonly ISqlSugarClient _db;
private readonly IConfigService _config;
private readonly IOnboardingContractSnapshotService _snapshotService;
public OnboardingAuditService(
ISqlSugarClient db,
IConfigService config,
IOnboardingContractSnapshotService snapshotService)
{
_db = db;
_config = config;
_snapshotService = snapshotService;
}
public async Task Approve(OnboardingAuditActionInput input)
{
var task = await GetAuditableTask(input.TaskId);
var employeeInfo = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id)
?? throw Oops.Bah("候选人资料不存在");
var hasMaterialNeedSupplement = await _db.Queryable<ObTaskMaterial>()
.AnyAsync(item => item.TaskId == task.Id
&& item.ReviewStatus == TaskMaterialReviewStatus.NeedSupplement);
if (hasMaterialNeedSupplement)
{
throw Oops.Bah("仍有资料需要补充,不能审核通过");
}
var hasExistingEmployee = await _db.Queryable<ObEmployee>()
.AnyAsync(item => item.IdCardNumber == employeeInfo.IdCardNumber || item.Phone == employeeInfo.Phone);
if (hasExistingEmployee)
{
throw Oops.Bah("该身份证号或手机号已存在正式员工");
}
var extraValues = await _db.Queryable<ObTaskFormValue>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var employee = employeeInfo.Adapt<ObEmployee>();
employee.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
employee.CompanyId = task.CompanyId;
employee.PositionId = task.PositionId;
employee.ExtraInfoJson = global::System.Text.Json.JsonSerializer.Serialize(extraValues);
await _db.Ado.BeginTranAsync();
try
{
await _snapshotService.Build(task.Id, employeeInfo);
await _db.Insertable(employee).ExecuteCommandAsync();
task.EmployeeId = employee.Id;
task.TaskStatus = OnboardingTaskStatus.Approved;
task.AuditedAt = DateTime.Now;
await _db.Updateable(task).ExecuteCommandAsync();
await WriteAudit(task, OnboardingAuditAction.Approve, input.AuditOpinion);
await _db.Ado.CommitTranAsync();
}
catch
{
await _db.Ado.RollbackTranAsync();
throw;
}
}
public async Task Reject(OnboardingAuditActionInput input)
{
if (string.IsNullOrWhiteSpace(input.AuditOpinion))
{
throw Oops.Bah("请填写退回原因");
}
var task = await GetAuditableTask(input.TaskId);
task.TaskStatus = OnboardingTaskStatus.Rejected;
task.RejectReason = input.AuditOpinion;
await _db.Updateable(task).ExecuteCommandAsync();
await WriteAudit(task, OnboardingAuditAction.Reject, input.AuditOpinion);
}
private async Task<ObOnboardingTask> GetAuditableTask(long taskId)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(taskId)
?? throw Oops.Bah("入职任务不存在");
if (task.TaskStatus != OnboardingTaskStatus.Reviewing)
{
throw Oops.Bah("当前任务不在审核中");
}
var config = await _config.GetByConfigKey(CateGoryConst.CONFIG_BIZ_DEFINE, "onboard.reviewer.userIds")
?? throw Oops.Bah("请配置入职审核人");
using var configJson = global::System.Text.Json.JsonDocument.Parse(config.ConfigValue);
var reviewerIds = new List<long>();
if (configJson.RootElement.TryGetProperty("all", out var allReviewers))
{
reviewerIds.AddRange(allReviewers.EnumerateArray().Select(item => item.GetInt64()));
}
if (configJson.RootElement.TryGetProperty("company", out var companyReviewers)
&& companyReviewers.TryGetProperty(task.CompanyId.ToString(), out var reviewers))
{
reviewerIds.AddRange(reviewers.EnumerateArray().Select(item => item.GetInt64()));
}
if (!reviewerIds.Contains(UserManager.UserId))
{
throw Oops.Bah("您不是该入职任务的审核人");
}
return task;
}
private Task WriteAudit(ObOnboardingTask task, string action, string opinion)
{
return _db.Insertable(new ObTaskAudit
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
AuditRound = task.SubmitCount,
AuditAction = action,
AuditorUserId = UserManager.UserId,
AuditOpinion = opinion,
AuditedAt = DateTime.Now
}).ExecuteCommandAsync();
}
}
@@ -5,16 +5,27 @@ public class OnboardingCompanyService : DbRepository<ObCompany>, IOnboardingComp
public async Task<SqlSugarPagedList<ObCompany>> Page(OnboardingCompanyPageInput input) public async Task<SqlSugarPagedList<ObCompany>> Page(OnboardingCompanyPageInput input)
{ {
return await Context.Queryable<ObCompany>() return await Context.Queryable<ObCompany>()
.WhereIF(!string.IsNullOrWhiteSpace(input.CompanyName), it => it.CompanyName.Contains(input.CompanyName) || it.CompanyCode.Contains(input.CompanyName)) .WhereIF(
!string.IsNullOrWhiteSpace(input.CompanyName),
it => it.CompanyName.Contains(input.CompanyName) || it.CompanyCode.Contains(input.CompanyName))
.WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status) .WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status)
.OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}") .OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}")
.OrderBy(it => it.CreateTime) .OrderBy(it => it.CreateTime)
.ToPagedListAsync(input.PageNum, input.PageSize); .ToPagedListAsync(input.PageNum, input.PageSize);
} }
public Task<List<ObCompany>> ListEnabled() => Context.Queryable<ObCompany>().Where(it => it.Status == "enable").OrderBy(it => it.CompanyName).ToListAsync(); public Task<List<ObCompany>> ListEnabled()
{
return Context.Queryable<ObCompany>()
.Where(it => it.Status == "enable")
.OrderBy(it => it.CompanyName)
.ToListAsync();
}
public async Task<ObCompany> Detail(BaseIdInput input) => await GetByIdAsync(input.Id) ?? throw Oops.Bah("公司不存在"); public async Task<ObCompany> Detail(BaseIdInput input)
{
return await GetByIdAsync(input.Id) ?? throw Oops.Bah("公司不存在");
}
public async Task Add(OnboardingCompanyAddInput input) public async Task Add(OnboardingCompanyAddInput input)
{ {
@@ -31,15 +42,32 @@ public class OnboardingCompanyService : DbRepository<ObCompany>, IOnboardingComp
public async Task Delete(BaseIdListInput input) public async Task Delete(BaseIdListInput input)
{ {
if (input.Ids.Count == 0) return; if (input.Ids.Count == 0)
if (await Context.Queryable<ObPosition>().AnyAsync(it => input.Ids.Contains(it.CompanyId))) throw Oops.Bah("公司下仍有岗位,不能删除"); return;
if (await Context.Queryable<ObOnboardingTask>().AnyAsync(it => input.Ids.Contains(it.CompanyId))) throw Oops.Bah("公司已有入职任务,不能删除");
if (await Context.Queryable<ObEmployee>().AnyAsync(it => input.Ids.Contains(it.CompanyId))) throw Oops.Bah("公司已有正式员工,不能删除"); var hasPositions = await Context.Queryable<ObPosition>()
.AnyAsync(it => input.Ids.Contains(it.CompanyId));
if (hasPositions)
throw Oops.Bah("公司下仍有岗位,不能删除");
var hasTasks = await Context.Queryable<ObOnboardingTask>()
.AnyAsync(it => input.Ids.Contains(it.CompanyId));
if (hasTasks)
throw Oops.Bah("公司已有入职任务,不能删除");
var hasEmployees = await Context.Queryable<ObEmployee>()
.AnyAsync(it => input.Ids.Contains(it.CompanyId));
if (hasEmployees)
throw Oops.Bah("公司已有正式员工,不能删除");
await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray()); await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray());
} }
private async Task EnsureCodeUnique(string code, long id) private async Task EnsureCodeUnique(string code, long id)
{ {
if (await Context.Queryable<ObCompany>().AnyAsync(it => it.CompanyCode == code && it.Id != id)) throw Oops.Bah("公司编码已存在"); var exists = await Context.Queryable<ObCompany>()
.AnyAsync(it => it.CompanyCode == code && it.Id != id);
if (exists)
throw Oops.Bah("公司编码已存在");
} }
} }
@@ -0,0 +1,7 @@
namespace SimpleAdmin.Application;
public class OnboardingGenerateContractInput
{
[IdNotNull(ErrorMessage = "任务合同Id不能为空")]
public long TaskContractId { get; set; }
}
@@ -0,0 +1,2 @@
namespace SimpleAdmin.Application;
public interface IOnboardingContractDocumentService : ITransient { Task<long> Generate(OnboardingGenerateContractInput input); }
@@ -0,0 +1,97 @@
namespace SimpleAdmin.Application;
public class OnboardingContractDocumentService : IOnboardingContractDocumentService
{
private readonly ISqlSugarClient _db;
private readonly IFileService _fileService;
public OnboardingContractDocumentService(ISqlSugarClient db, IFileService fileService)
{
_db = db;
_fileService = fileService;
}
public async Task<long> Generate(OnboardingGenerateContractInput input)
{
var contract = await _db.Queryable<ObTaskContract>().InSingleAsync(input.TaskContractId)
?? throw Oops.Bah("任务合同不存在");
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(contract.TaskId)
?? throw Oops.Bah("入职任务不存在");
if (task.TaskStatus != OnboardingTaskStatus.Approved)
throw Oops.Bah("仅审核通过的任务可生成归档合同");
if (contract.GeneratedDocxFileId.HasValue)
return contract.GeneratedDocxFileId.Value;
var version = await _db.Queryable<ObContractTemplateVersion>().InSingleAsync(contract.TemplateVersionId)
?? throw Oops.Bah("合同版本不存在");
var source = await _db.Queryable<SysFile>().InSingleAsync(version.SourceDocxFileId)
?? throw Oops.Bah("合同源文件不存在");
var variables = await _db.Queryable<ObTaskContractVariable>()
.Where(x => x.TaskContractId == contract.Id)
.ToListAsync();
var sourceResult = await _fileService.Download(new BaseIdInput { Id = source.Id })
?? throw Oops.Bah("无法读取合同源文件");
await using var sourceStream = sourceResult.FileStream;
using var sourceBytes = new MemoryStream();
await sourceStream.CopyToAsync(sourceBytes);
var rendered = ReplaceDocxText(
sourceBytes.ToArray(),
variables.ToDictionary(x => x.VariableCode, x => x.VariableValue ?? string.Empty));
var name = $"{task.TaskNo}_{contract.ContractName}.docx";
var fileId = await _fileService.StoreGeneratedFile(source.Engine, name, rendered);
contract.GeneratedDocxFileId = fileId;
contract.GeneratedAt = DateTime.Now;
await _db.Updateable(contract).ExecuteCommandAsync();
return fileId;
}
private static byte[] ReplaceDocxText(byte[] docx, Dictionary<string, string> values)
{
using var input = new MemoryStream(docx);
using var archive = new global::System.IO.Compression.ZipArchive(
input,
global::System.IO.Compression.ZipArchiveMode.Read);
using var output = new MemoryStream();
using var target = new global::System.IO.Compression.ZipArchive(
output,
global::System.IO.Compression.ZipArchiveMode.Create,
true);
foreach (var entry in archive.Entries)
{
var next = target.CreateEntry(entry.FullName, global::System.IO.Compression.CompressionLevel.Optimal);
using var reader = entry.Open();
using var writer = next.Open();
var isWordTextXml = entry.FullName == "word/document.xml"
|| ((entry.FullName.StartsWith("word/header") || entry.FullName.StartsWith("word/footer"))
&& entry.FullName.EndsWith(".xml"));
if (!isWordTextXml)
{
reader.CopyTo(writer);
continue;
}
using var textReader = new StreamReader(reader);
var document = global::System.Xml.Linq.XDocument.Parse(textReader.ReadToEnd());
var word = global::System.Xml.Linq.XNamespace.Get("http://schemas.openxmlformats.org/wordprocessingml/2006/main");
foreach (var node in document.Descendants(word + "t"))
{
foreach (var item in values)
node.Value = node.Value.Replace("{{" + item.Key + "}}", item.Value ?? string.Empty);
}
using var textWriter = new StreamWriter(writer);
textWriter.Write(document.ToString(global::System.Xml.Linq.SaveOptions.DisableFormatting));
}
return output.ToArray();
}
}
@@ -0,0 +1,7 @@
namespace SimpleAdmin.Application;
public interface IOnboardingContractSnapshotService : ITransient
{
/// <summary>根据任务、公司与候选人资料刷新自动变量;人事已手工填写的变量不会被覆盖。</summary>
Task Build(long taskId, ObTaskEmployeeInfo employeeInfo = null);
}
@@ -0,0 +1,82 @@
namespace SimpleAdmin.Application;
/// <summary>冻结任务合同变量;DOCX 生成阶段只读取本快照,不再读取模板实时值。</summary>
public class OnboardingContractSnapshotService : IOnboardingContractSnapshotService
{
private readonly ISqlSugarClient _db;
public OnboardingContractSnapshotService(ISqlSugarClient db) => _db = db;
public async Task Build(long taskId, ObTaskEmployeeInfo employeeInfo = null)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(taskId) ?? throw Oops.Bah("入职任务不存在");
var company = await _db.Queryable<ObCompany>().InSingleAsync(task.CompanyId) ?? throw Oops.Bah("入职公司不存在");
employeeInfo ??= await _db.Queryable<ObTaskEmployeeInfo>().FirstAsync(x => x.TaskId == taskId);
var contracts = await _db.Queryable<ObTaskContract>().Where(x => x.TaskId == taskId).ToListAsync();
foreach (var contract in contracts)
{
if (contract.GeneratedDocxFileId.HasValue || contract.GeneratedPdfFileId.HasValue) throw Oops.Bah("合同已生成,不能重建变量快照");
var definitions = await _db.Queryable<ObTemplateVariable>().Where(x => x.TemplateVersionId == contract.TemplateVersionId).OrderBy(x => x.SortCode).ToListAsync();
var oldRows = await _db.Queryable<ObTaskContractVariable>().Where(x => x.TaskContractId == contract.Id).ToListAsync();
var rows = definitions.Select(definition =>
{
var old = oldRows.FirstOrDefault(x => x.VariableCode == definition.VariableCode);
var preserveManual = old?.IsManualOverride == true;
return new ObTaskContractVariable
{
Id = old?.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskContractId = contract.Id,
VariableCode = definition.VariableCode,
VariableName = definition.VariableName,
VariableType = definition.VariableType,
DataType = definition.DataType,
VariableValue = preserveManual ? old.VariableValue : ResolveValue(definition, employeeInfo, company, task),
SourceType = preserveManual ? ContractVariableSourceType.HrInput : GetSourceType(definition.VariableType),
IsManualOverride = preserveManual
};
}).ToList();
await _db.Deleteable<ObTaskContractVariable>().Where(x => x.TaskContractId == contract.Id).ExecuteCommandAsync();
if (rows.Count > 0) await _db.Insertable(rows).ExecuteCommandAsync();
}
}
private static string GetSourceType(string variableType) => variableType switch
{
ContractVariableType.Employee => ContractVariableSourceType.Employee,
ContractVariableType.Company => ContractVariableSourceType.Company,
ContractVariableType.System => ContractVariableSourceType.System,
_ => ContractVariableSourceType.HrInput
};
private static string ResolveValue(ObTemplateVariable definition, ObTaskEmployeeInfo info, ObCompany company, ObOnboardingTask task)
{
if (definition.VariableType == ContractVariableType.Employee)
return GetPropertyValue(info, definition.EmployeeField) ?? definition.DefaultValue;
if (definition.VariableType == ContractVariableType.Company)
return GetPropertyValue(company, ResolveCompanyField(definition)) ?? definition.DefaultValue;
if (definition.VariableType == ContractVariableType.System && string.Equals(definition.VariableCode, "TASK_NO", StringComparison.OrdinalIgnoreCase))
return task.TaskNo;
return definition.DefaultValue;
}
private static string GetPropertyValue(object source, string propertyName)
{
if (source == null || string.IsNullOrWhiteSpace(propertyName)) return null;
return source.GetType().GetProperty(propertyName, global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.Instance | global::System.Reflection.BindingFlags.IgnoreCase)?.GetValue(source)?.ToString();
}
private static string ResolveCompanyField(ObTemplateVariable definition)
{
if (!string.IsNullOrWhiteSpace(definition.EmployeeField)) return definition.EmployeeField;
return definition.VariableCode.ToUpperInvariant() switch
{
"COMPANY_CODE" => nameof(ObCompany.CompanyCode),
"COMPANY_NAME" => nameof(ObCompany.CompanyName),
"LEGAL_NAME" or "COMPANY_LEGAL_NAME" => nameof(ObCompany.LegalName),
"UNIFIED_SOCIAL_CREDIT_CODE" => nameof(ObCompany.UnifiedSocialCreditCode),
"LEGAL_REPRESENTATIVE" => nameof(ObCompany.LegalRepresentative),
"REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress),
"CONTACT_PHONE" => nameof(ObCompany.ContactPhone),
_ => definition.VariableCode
};
}
}
@@ -1,12 +1,16 @@
namespace SimpleAdmin.Application; namespace SimpleAdmin.Application;
/// <summary>合同模板主档案;具体 DOCX 文件、变量与适用范围均挂在此档案下。</summary> /// <summary>
/// 合同模板主档案;具体 DOCX 文件、变量与适用范围均挂在此档案下。
/// </summary>
public class OnboardingContractTemplateService : DbRepository<ObContractTemplate>, IOnboardingContractTemplateService public class OnboardingContractTemplateService : DbRepository<ObContractTemplate>, IOnboardingContractTemplateService
{ {
public async Task<SqlSugarPagedList<ObContractTemplate>> Page(OnboardingContractTemplatePageInput input) public async Task<SqlSugarPagedList<ObContractTemplate>> Page(OnboardingContractTemplatePageInput input)
{ {
return await Context.Queryable<ObContractTemplate>() return await Context.Queryable<ObContractTemplate>()
.WhereIF(!string.IsNullOrWhiteSpace(input.TemplateName), it => it.TemplateName.Contains(input.TemplateName) || it.TemplateCode.Contains(input.TemplateName)) .WhereIF(
!string.IsNullOrWhiteSpace(input.TemplateName),
it => it.TemplateName.Contains(input.TemplateName) || it.TemplateCode.Contains(input.TemplateName))
.WhereIF(!string.IsNullOrWhiteSpace(input.ContractType), it => it.ContractType == input.ContractType) .WhereIF(!string.IsNullOrWhiteSpace(input.ContractType), it => it.ContractType == input.ContractType)
.WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status) .WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status)
.OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}") .OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}")
@@ -14,7 +18,10 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
.ToPagedListAsync(input.PageNum, input.PageSize); .ToPagedListAsync(input.PageNum, input.PageSize);
} }
public async Task<ObContractTemplate> Detail(BaseIdInput input) => await GetByIdAsync(input.Id) ?? throw Oops.Bah("合同模板不存在"); public async Task<ObContractTemplate> Detail(BaseIdInput input)
{
return await GetByIdAsync(input.Id) ?? throw Oops.Bah("合同模板不存在");
}
public async Task Add(OnboardingContractTemplateAddInput input) public async Task Add(OnboardingContractTemplateAddInput input)
{ {
@@ -31,46 +38,83 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
public async Task Delete(BaseIdListInput input) public async Task Delete(BaseIdListInput input)
{ {
if (input.Ids.Count == 0) return; if (input.Ids.Count == 0)
if (await Context.Queryable<ObContractTemplateVersion>().AnyAsync(it => input.Ids.Contains(it.TemplateId))) throw Oops.Bah("模板已有版本,不能删除"); return;
if (await Context.Queryable<ObContractScope>().AnyAsync(it => input.Ids.Contains(it.TemplateId))) throw Oops.Bah("模板已配置适用范围,不能删除");
if (await Context.Queryable<ObTaskContract>().AnyAsync(it => input.Ids.Contains(it.TemplateId))) throw Oops.Bah("模板已生成入职合同,不能删除"); var hasVersions = await Context.Queryable<ObContractTemplateVersion>()
.AnyAsync(it => input.Ids.Contains(it.TemplateId));
if (hasVersions)
throw Oops.Bah("模板已有版本,不能删除");
var hasScopes = await Context.Queryable<ObContractScope>()
.AnyAsync(it => input.Ids.Contains(it.TemplateId));
if (hasScopes)
throw Oops.Bah("模板已配置适用范围,不能删除");
var hasContracts = await Context.Queryable<ObTaskContract>()
.AnyAsync(it => input.Ids.Contains(it.TemplateId));
if (hasContracts)
throw Oops.Bah("模板已生成入职合同,不能删除");
await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray()); await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray());
} }
private async Task EnsureCodeUnique(string code, long id)
{
if (await Context.Queryable<ObContractTemplate>().AnyAsync(it => it.TemplateCode == code && it.Id != id)) throw Oops.Bah("模板编码已存在");
}
public async Task<SqlSugarPagedList<ObContractTemplateVersion>> VersionPage(OnboardingContractVersionPageInput input) public async Task<SqlSugarPagedList<ObContractTemplateVersion>> VersionPage(OnboardingContractVersionPageInput input)
{ {
return await Context.Queryable<ObContractTemplateVersion>().Where(it => it.TemplateId == input.TemplateId) return await Context.Queryable<ObContractTemplateVersion>()
.Where(it => it.TemplateId == input.TemplateId)
.WhereIF(!string.IsNullOrWhiteSpace(input.VersionStatus), it => it.VersionStatus == input.VersionStatus) .WhereIF(!string.IsNullOrWhiteSpace(input.VersionStatus), it => it.VersionStatus == input.VersionStatus)
.OrderByDescending(it => it.CreateTime).ToPagedListAsync(input.PageNum, input.PageSize); .OrderByDescending(it => it.CreateTime)
.ToPagedListAsync(input.PageNum, input.PageSize);
} }
public async Task AddVersion(OnboardingContractVersionAddInput input) public async Task AddVersion(OnboardingContractVersionAddInput input)
{ {
await Detail(new BaseIdInput { Id = input.TemplateId }); await Detail(new BaseIdInput { Id = input.TemplateId });
var file = await Context.Queryable<SysFile>().InSingleAsync(input.SourceDocxFileId) ?? throw Oops.Bah("DOCX源文件不存在");
if (!string.Equals(file.Suffix, "docx", StringComparison.OrdinalIgnoreCase)) throw Oops.Bah("合同模板仅支持DOCX文件"); var file = await Context.Queryable<SysFile>().InSingleAsync(input.SourceDocxFileId)
if (await Context.Queryable<ObContractTemplateVersion>().AnyAsync(it => it.TemplateId == input.TemplateId && it.VersionNo == input.VersionNo)) throw Oops.Bah("该模板版本号已存在"); ?? throw Oops.Bah("DOCX源文件不存在");
if (!string.Equals(file.Suffix, "docx", StringComparison.OrdinalIgnoreCase))
throw Oops.Bah("合同模板仅支持DOCX文件");
var versionExists = await Context.Queryable<ObContractTemplateVersion>()
.AnyAsync(it => it.TemplateId == input.TemplateId && it.VersionNo == input.VersionNo);
if (versionExists)
throw Oops.Bah("该模板版本号已存在");
await Context.Insertable(input.Adapt<ObContractTemplateVersion>()).ExecuteCommandAsync(); await Context.Insertable(input.Adapt<ObContractTemplateVersion>()).ExecuteCommandAsync();
} }
public async Task SetVersionStatus(OnboardingContractVersionStatusInput input) public async Task SetVersionStatus(OnboardingContractVersionStatusInput input)
{ {
if (input.VersionStatus != ContractTemplateVersionStatus.Enabled && input.VersionStatus != ContractTemplateVersionStatus.Disabled) throw Oops.Bah("版本状态仅支持启用或停用"); if (input.VersionStatus != ContractTemplateVersionStatus.Enabled
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(input.Id) ?? throw Oops.Bah("模板版本不存在"); && input.VersionStatus != ContractTemplateVersionStatus.Disabled)
if (input.VersionStatus == ContractTemplateVersionStatus.Enabled && !await Context.Queryable<ObTemplateVariable>().AnyAsync(it => it.TemplateVersionId == input.Id)) throw Oops.Bah("请先配置模板变量后再启用"); {
throw Oops.Bah("版本状态仅支持启用或停用");
}
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(input.Id)
?? throw Oops.Bah("模板版本不存在");
if (input.VersionStatus == ContractTemplateVersionStatus.Enabled)
{
var hasVariables = await Context.Queryable<ObTemplateVariable>()
.AnyAsync(it => it.TemplateVersionId == input.Id);
if (!hasVariables)
throw Oops.Bah("请先配置模板变量后再启用");
}
version.VersionStatus = input.VersionStatus; version.VersionStatus = input.VersionStatus;
await Context.Updateable(version).ExecuteCommandAsync(); await Context.Updateable(version).ExecuteCommandAsync();
} }
public async Task<SqlSugarPagedList<ObTemplateVariable>> VariablePage(OnboardingTemplateVariablePageInput input) public async Task<SqlSugarPagedList<ObTemplateVariable>> VariablePage(OnboardingTemplateVariablePageInput input)
{ {
return await Context.Queryable<ObTemplateVariable>().Where(it => it.TemplateVersionId == input.TemplateVersionId) return await Context.Queryable<ObTemplateVariable>()
.OrderBy(it => it.SortCode).OrderBy(it => it.VariableCode).ToPagedListAsync(input.PageNum, input.PageSize); .Where(it => it.TemplateVersionId == input.TemplateVersionId)
.OrderBy(it => it.SortCode)
.OrderBy(it => it.VariableCode)
.ToPagedListAsync(input.PageNum, input.PageSize);
} }
public async Task AddVariable(OnboardingTemplateVariableAddInput input) public async Task AddVariable(OnboardingTemplateVariableAddInput input)
@@ -82,8 +126,11 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
public async Task EditVariable(OnboardingTemplateVariableEditInput input) public async Task EditVariable(OnboardingTemplateVariableEditInput input)
{ {
var variable = await Context.Queryable<ObTemplateVariable>().InSingleAsync(input.Id) ?? throw Oops.Bah("模板变量不存在"); var variable = await Context.Queryable<ObTemplateVariable>().InSingleAsync(input.Id)
if (variable.TemplateVersionId != input.TemplateVersionId) throw Oops.Bah("不允许变更变量所属版本"); ?? throw Oops.Bah("模板变量不存在");
if (variable.TemplateVersionId != input.TemplateVersionId)
throw Oops.Bah("不允许变更变量所属版本");
await EnsureVersionDraft(input.TemplateVersionId); await EnsureVersionDraft(input.TemplateVersionId);
await EnsureVariableUnique(input.TemplateVersionId, input.VariableCode, input.Id); await EnsureVariableUnique(input.TemplateVersionId, input.VariableCode, input.Id);
await Context.Updateable(input.Adapt<ObTemplateVariable>()).ExecuteCommandAsync(); await Context.Updateable(input.Adapt<ObTemplateVariable>()).ExecuteCommandAsync();
@@ -91,28 +138,28 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
public async Task DeleteVariable(BaseIdListInput input) public async Task DeleteVariable(BaseIdListInput input)
{ {
if (input.Ids.Count == 0) return; if (input.Ids.Count == 0)
var versionIds = await Context.Queryable<ObTemplateVariable>().Where(it => input.Ids.Contains(it.Id)).Select(it => it.TemplateVersionId).ToListAsync(); return;
foreach (var versionId in versionIds.Distinct()) await EnsureVersionDraft(versionId);
var versionIds = await Context.Queryable<ObTemplateVariable>()
.Where(it => input.Ids.Contains(it.Id))
.Select(it => it.TemplateVersionId)
.ToListAsync();
foreach (var versionId in versionIds.Distinct())
await EnsureVersionDraft(versionId);
await Context.Deleteable<ObTemplateVariable>().In(input.Ids).ExecuteCommandAsync(); await Context.Deleteable<ObTemplateVariable>().In(input.Ids).ExecuteCommandAsync();
} }
private async Task EnsureVersionDraft(long versionId) public async Task<SqlSugarPagedList<ObContractScope>> ScopePage(OnboardingContractScopePageInput input)
{
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(versionId) ?? throw Oops.Bah("模板版本不存在");
if (version.VersionStatus != ContractTemplateVersionStatus.Draft) throw Oops.Bah("只有草稿版本可维护变量");
}
private async Task EnsureVariableUnique(long versionId, string code, long id)
{
if (await Context.Queryable<ObTemplateVariable>().AnyAsync(it => it.TemplateVersionId == versionId && it.VariableCode == code && it.Id != id)) throw Oops.Bah("该版本变量编码已存在");
} public async Task<SqlSugarPagedList<ObContractScope>> ScopePage(OnboardingContractScopePageInput input)
{ {
return await Context.Queryable<ObContractScope>() return await Context.Queryable<ObContractScope>()
.WhereIF(input.CompanyId.HasValue, it => it.CompanyId == input.CompanyId.Value) .WhereIF(input.CompanyId.HasValue, it => it.CompanyId == input.CompanyId.Value)
.WhereIF(input.PositionId.HasValue, it => it.PositionId == input.PositionId.Value) .WhereIF(input.PositionId.HasValue, it => it.PositionId == input.PositionId.Value)
.WhereIF(input.TemplateId.HasValue, it => it.TemplateId == input.TemplateId.Value) .WhereIF(input.TemplateId.HasValue, it => it.TemplateId == input.TemplateId.Value)
.OrderBy(it => it.SortCode).ToPagedListAsync(input.PageNum, input.PageSize); .OrderBy(it => it.SortCode)
.ToPagedListAsync(input.PageNum, input.PageSize);
} }
public async Task AddScope(OnboardingContractScopeAddInput input) public async Task AddScope(OnboardingContractScopeAddInput input)
@@ -124,7 +171,10 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
public async Task EditScope(OnboardingContractScopeEditInput input) public async Task EditScope(OnboardingContractScopeEditInput input)
{ {
if (!await Context.Queryable<ObContractScope>().AnyAsync(it => it.Id == input.Id)) throw Oops.Bah("合同适用范围不存在"); var scopeExists = await Context.Queryable<ObContractScope>().AnyAsync(it => it.Id == input.Id);
if (!scopeExists)
throw Oops.Bah("合同适用范围不存在");
await EnsureScopeValid(input.CompanyId, input.PositionId, input.TemplateId, input.TemplateVersionId); await EnsureScopeValid(input.CompanyId, input.PositionId, input.TemplateId, input.TemplateVersionId);
await EnsureScopeUnique(input.CompanyId, input.PositionId, input.TemplateVersionId, input.Id); await EnsureScopeUnique(input.CompanyId, input.PositionId, input.TemplateVersionId, input.Id);
await Context.Updateable(input.Adapt<ObContractScope>()).ExecuteCommandAsync(); await Context.Updateable(input.Adapt<ObContractScope>()).ExecuteCommandAsync();
@@ -132,18 +182,65 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
public async Task DeleteScope(BaseIdListInput input) public async Task DeleteScope(BaseIdListInput input)
{ {
if (input.Ids.Count > 0) await Context.Deleteable<ObContractScope>().In(input.Ids).ExecuteCommandAsync(); if (input.Ids.Count == 0)
return;
await Context.Deleteable<ObContractScope>().In(input.Ids).ExecuteCommandAsync();
}
private async Task EnsureCodeUnique(string code, long id)
{
var exists = await Context.Queryable<ObContractTemplate>()
.AnyAsync(it => it.TemplateCode == code && it.Id != id);
if (exists)
throw Oops.Bah("模板编码已存在");
}
private async Task EnsureVersionDraft(long versionId)
{
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(versionId)
?? throw Oops.Bah("模板版本不存在");
if (version.VersionStatus != ContractTemplateVersionStatus.Draft)
throw Oops.Bah("只有草稿版本可维护变量");
}
private async Task EnsureVariableUnique(long versionId, string code, long id)
{
var exists = await Context.Queryable<ObTemplateVariable>()
.AnyAsync(it => it.TemplateVersionId == versionId && it.VariableCode == code && it.Id != id);
if (exists)
throw Oops.Bah("该版本变量编码已存在");
} }
private async Task EnsureScopeValid(long companyId, long? positionId, long templateId, long versionId) private async Task EnsureScopeValid(long companyId, long? positionId, long templateId, long versionId)
{ {
if (!await Context.Queryable<ObCompany>().AnyAsync(it => it.Id == companyId && it.Status == "enable")) throw Oops.Bah("所属公司不存在或未启用"); var companyExists = await Context.Queryable<ObCompany>()
if (positionId.HasValue && !await Context.Queryable<ObPosition>().AnyAsync(it => it.Id == positionId.Value && it.CompanyId == companyId && it.Status == "enable")) throw Oops.Bah("岗位不存在、未启用或不属于该公司"); .AnyAsync(it => it.Id == companyId && it.Status == "enable");
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(versionId) ?? throw Oops.Bah("合同版本不存在"); if (!companyExists)
if (version.TemplateId != templateId || version.VersionStatus != ContractTemplateVersionStatus.Enabled) throw Oops.Bah("只能配置该模板的已启用合同版本"); throw Oops.Bah("所属公司不存在或未启用");
if (positionId.HasValue)
{
var positionExists = await Context.Queryable<ObPosition>()
.AnyAsync(it => it.Id == positionId.Value && it.CompanyId == companyId && it.Status == "enable");
if (!positionExists)
throw Oops.Bah("岗位不存在、未启用或不属于该公司");
}
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(versionId)
?? throw Oops.Bah("合同版本不存在");
if (version.TemplateId != templateId || version.VersionStatus != ContractTemplateVersionStatus.Enabled)
throw Oops.Bah("只能配置该模板的已启用合同版本");
} }
private async Task EnsureScopeUnique(long companyId, long? positionId, long versionId, long id) private async Task EnsureScopeUnique(long companyId, long? positionId, long versionId, long id)
{ {
if (await Context.Queryable<ObContractScope>().AnyAsync(it => it.CompanyId == companyId && it.PositionId == positionId && it.TemplateVersionId == versionId && it.Id != id)) throw Oops.Bah("该公司、岗位和合同版本的适用范围已存在"); var exists = await Context.Queryable<ObContractScope>()
}} .AnyAsync(it => it.CompanyId == companyId
&& it.PositionId == positionId
&& it.TemplateVersionId == versionId
&& it.Id != id);
if (exists)
throw Oops.Bah("该公司、岗位和合同版本的适用范围已存在");
}
}
@@ -0,0 +1,17 @@
namespace SimpleAdmin.Application;
public class OnboardingEmployeePageInput : BasePageInput
{
public string EmployeeName { get; set; }
public string Phone { get; set; }
public long? CompanyId { get; set; }
public long? PositionId { get; set; }
}
public class OnboardingEmployeeDetailOutput
{
public ObEmployee Employee { get; set; }
public ObOnboardingTask Task { get; set; }
public List<ObTaskContract> Contracts { get; set; } = new List<ObTaskContract>();
public List<ObTaskAudit> Audits { get; set; } = new List<ObTaskAudit>();
}
@@ -0,0 +1,7 @@
namespace SimpleAdmin.Application;
public interface IOnboardingEmployeeService : ITransient
{
Task<SqlSugarPagedList<ObEmployee>> Page(OnboardingEmployeePageInput input);
Task<OnboardingEmployeeDetailOutput> Detail(BaseIdInput input);
}
@@ -0,0 +1,24 @@
namespace SimpleAdmin.Application;
/// <summary>仅展示审核通过后创建的正式员工档案;入职资料的来源任务可追溯。</summary>
public class OnboardingEmployeeService : IOnboardingEmployeeService
{
private readonly ISqlSugarClient _db;
public OnboardingEmployeeService(ISqlSugarClient db) => _db = db;
public Task<SqlSugarPagedList<ObEmployee>> Page(OnboardingEmployeePageInput input) => _db.Queryable<ObEmployee>()
.WhereIF(!string.IsNullOrWhiteSpace(input.EmployeeName), x => x.EmployeeName.Contains(input.EmployeeName) || x.IdCardNumber.Contains(input.EmployeeName))
.WhereIF(!string.IsNullOrWhiteSpace(input.Phone), x => x.Phone.Contains(input.Phone))
.WhereIF(input.CompanyId.HasValue, x => x.CompanyId == input.CompanyId.Value)
.WhereIF(input.PositionId.HasValue, x => x.PositionId == input.PositionId.Value)
.OrderByDescending(x => x.CreateTime).ToPagedListAsync(input.PageNum, input.PageSize);
public async Task<OnboardingEmployeeDetailOutput> Detail(BaseIdInput input)
{
var employee = await _db.Queryable<ObEmployee>().InSingleAsync(input.Id) ?? throw Oops.Bah("正式员工不存在");
var task = await _db.Queryable<ObOnboardingTask>().FirstAsync(x => x.EmployeeId == employee.Id);
var contracts = task == null ? new List<ObTaskContract>() : await _db.Queryable<ObTaskContract>().Where(x => x.TaskId == task.Id).ToListAsync();
var audits = task == null ? new List<ObTaskAudit>() : await _db.Queryable<ObTaskAudit>().Where(x => x.TaskId == task.Id).OrderByDescending(x => x.AuditedAt).ToListAsync();
return new OnboardingEmployeeDetailOutput { Employee = employee, Task = task, Contracts = contracts, Audits = audits };
}
}
@@ -0,0 +1,68 @@
namespace SimpleAdmin.Application;
public class OnboardingFormFieldSaveInput
{
public long? Id { get; set; }
[global::System.ComponentModel.DataAnnotations.Required(ErrorMessage = "字段编码不能为空")]
public string FieldCode { get; set; }
[global::System.ComponentModel.DataAnnotations.Required(ErrorMessage = "字段名称不能为空")]
public string FieldName { get; set; }
[global::System.ComponentModel.DataAnnotations.Required(ErrorMessage = "数据类型不能为空")]
public string DataType { get; set; }
public string OptionsJson { get; set; }
public string ValidationRule { get; set; }
public string Placeholder { get; set; }
public string Status { get; set; } = "enable";
}
public class OnboardingMaterialTypeSaveInput
{
public long? Id { get; set; }
[global::System.ComponentModel.DataAnnotations.Required(ErrorMessage = "资料编码不能为空")]
public string MaterialCode { get; set; }
[global::System.ComponentModel.DataAnnotations.Required(ErrorMessage = "资料名称不能为空")]
public string MaterialName { get; set; }
public string AllowedExtensions { get; set; }
public int MaxFileCount { get; set; } = 1;
public int? MaxFileSizeMb { get; set; }
public string Status { get; set; } = "enable";
}
public class OnboardingFormRequirementSaveInput
{
public long? Id { get; set; }
[IdNotNull(ErrorMessage = "公司Id不能为空")]
public long CompanyId { get; set; }
public long? PositionId { get; set; }
[IdNotNull(ErrorMessage = "字段Id不能为空")]
public long FieldId { get; set; }
public bool Required { get; set; }
public int? SortCode { get; set; }
}
public class OnboardingMaterialRequirementSaveInput
{
public long? Id { get; set; }
[IdNotNull(ErrorMessage = "公司Id不能为空")]
public long CompanyId { get; set; }
public long? PositionId { get; set; }
[IdNotNull(ErrorMessage = "资料类型Id不能为空")]
public long MaterialTypeId { get; set; }
public bool Required { get; set; }
public int? SortCode { get; set; }
}
@@ -0,0 +1,17 @@
namespace SimpleAdmin.Application;
public interface IOnboardingEntryConfigService : ITransient
{
Task<List<ObFormField>> ListFormFields();
Task SaveFormField(OnboardingFormFieldSaveInput input);
Task DeleteFormFields(BaseIdListInput input);
Task<List<ObMaterialType>> ListMaterialTypes();
Task SaveMaterialType(OnboardingMaterialTypeSaveInput input);
Task DeleteMaterialTypes(BaseIdListInput input);
Task<List<ObFormRequirement>> ListFormRequirements(long companyId);
Task SaveFormRequirement(OnboardingFormRequirementSaveInput input);
Task DeleteFormRequirements(BaseIdListInput input);
Task<List<ObMaterialRequirement>> ListMaterialRequirements(long companyId);
Task SaveMaterialRequirement(OnboardingMaterialRequirementSaveInput input);
Task DeleteMaterialRequirements(BaseIdListInput input);
}
@@ -0,0 +1,245 @@
namespace SimpleAdmin.Application;
/// <summary>
/// 维护员工端动态字段、材料类型及公司岗位要求。
/// </summary>
public class OnboardingEntryConfigService : IOnboardingEntryConfigService
{
private readonly ISqlSugarClient _db;
public OnboardingEntryConfigService(ISqlSugarClient db)
{
_db = db;
}
public Task<List<ObFormField>> ListFormFields()
{
return _db.Queryable<ObFormField>()
.OrderBy(x => x.FieldCode)
.ToListAsync();
}
public async Task SaveFormField(OnboardingFormFieldSaveInput input)
{
var fieldCodeExists = await _db.Queryable<ObFormField>()
.AnyAsync(x => x.FieldCode == input.FieldCode && x.Id != input.Id);
if (fieldCodeExists)
throw Oops.Bah("字段编码已存在");
var entity = new ObFormField
{
Id = input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
FieldCode = input.FieldCode,
FieldName = input.FieldName,
DataType = input.DataType,
OptionsJson = input.OptionsJson,
ValidationRule = input.ValidationRule,
Placeholder = input.Placeholder,
Status = input.Status
};
if (input.Id.HasValue)
{
var exists = await _db.Queryable<ObFormField>().AnyAsync(x => x.Id == input.Id.Value);
if (!exists)
throw Oops.Bah("字段不存在");
await _db.Updateable(entity).ExecuteCommandAsync();
return;
}
await _db.Insertable(entity).ExecuteCommandAsync();
}
public async Task DeleteFormFields(BaseIdListInput input)
{
if (input.Ids.Count == 0)
return;
var isReferenced = await _db.Queryable<ObFormRequirement>()
.AnyAsync(x => input.Ids.Contains(x.FieldId));
if (isReferenced)
throw Oops.Bah("字段已被填写要求引用,不能删除");
await _db.Deleteable<ObFormField>().In(input.Ids).ExecuteCommandAsync();
}
public Task<List<ObMaterialType>> ListMaterialTypes()
{
return _db.Queryable<ObMaterialType>()
.OrderBy(x => x.MaterialCode)
.ToListAsync();
}
public async Task SaveMaterialType(OnboardingMaterialTypeSaveInput input)
{
if (input.MaxFileCount <= 0)
throw Oops.Bah("最大文件数量必须大于0");
var materialCodeExists = await _db.Queryable<ObMaterialType>()
.AnyAsync(x => x.MaterialCode == input.MaterialCode && x.Id != input.Id);
if (materialCodeExists)
throw Oops.Bah("资料编码已存在");
var entity = new ObMaterialType
{
Id = input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
MaterialCode = input.MaterialCode,
MaterialName = input.MaterialName,
AllowedExtensions = input.AllowedExtensions,
MaxFileCount = input.MaxFileCount,
MaxFileSizeMb = input.MaxFileSizeMb,
Status = input.Status
};
if (input.Id.HasValue)
{
var exists = await _db.Queryable<ObMaterialType>().AnyAsync(x => x.Id == input.Id.Value);
if (!exists)
throw Oops.Bah("资料类型不存在");
await _db.Updateable(entity).ExecuteCommandAsync();
return;
}
await _db.Insertable(entity).ExecuteCommandAsync();
}
public async Task DeleteMaterialTypes(BaseIdListInput input)
{
if (input.Ids.Count == 0)
return;
var isReferenced = await _db.Queryable<ObMaterialRequirement>()
.AnyAsync(x => input.Ids.Contains(x.MaterialTypeId));
if (isReferenced)
throw Oops.Bah("资料类型已被资料要求引用,不能删除");
var hasUploads = await _db.Queryable<ObTaskMaterial>()
.AnyAsync(x => input.Ids.Contains(x.MaterialTypeId));
if (hasUploads)
throw Oops.Bah("资料类型已有上传记录,不能删除");
await _db.Deleteable<ObMaterialType>().In(input.Ids).ExecuteCommandAsync();
}
public Task<List<ObFormRequirement>> ListFormRequirements(long companyId)
{
return _db.Queryable<ObFormRequirement>()
.Where(x => x.CompanyId == companyId)
.OrderBy(x => x.SortCode)
.ToListAsync();
}
public async Task SaveFormRequirement(OnboardingFormRequirementSaveInput input)
{
await EnsureCompanyAndPosition(input.CompanyId, input.PositionId);
var fieldExists = await _db.Queryable<ObFormField>()
.AnyAsync(x => x.Id == input.FieldId && x.Status == "enable");
if (!fieldExists)
throw Oops.Bah("动态字段不存在或未启用");
var requirementExists = await _db.Queryable<ObFormRequirement>()
.AnyAsync(x => x.CompanyId == input.CompanyId
&& x.PositionId == input.PositionId
&& x.FieldId == input.FieldId
&& x.Id != input.Id);
if (requirementExists)
throw Oops.Bah("该公司、岗位和字段的要求已存在");
var entity = new ObFormRequirement
{
Id = input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
CompanyId = input.CompanyId,
PositionId = input.PositionId,
FieldId = input.FieldId,
Required = input.Required,
SortCode = input.SortCode
};
if (input.Id.HasValue)
{
await _db.Updateable(entity).ExecuteCommandAsync();
return;
}
await _db.Insertable(entity).ExecuteCommandAsync();
}
public Task DeleteFormRequirements(BaseIdListInput input)
{
if (input.Ids.Count == 0)
return Task.CompletedTask;
return _db.Deleteable<ObFormRequirement>().In(input.Ids).ExecuteCommandAsync();
}
public Task<List<ObMaterialRequirement>> ListMaterialRequirements(long companyId)
{
return _db.Queryable<ObMaterialRequirement>()
.Where(x => x.CompanyId == companyId)
.OrderBy(x => x.SortCode)
.ToListAsync();
}
public async Task SaveMaterialRequirement(OnboardingMaterialRequirementSaveInput input)
{
await EnsureCompanyAndPosition(input.CompanyId, input.PositionId);
var materialTypeExists = await _db.Queryable<ObMaterialType>()
.AnyAsync(x => x.Id == input.MaterialTypeId && x.Status == "enable");
if (!materialTypeExists)
throw Oops.Bah("资料类型不存在或未启用");
var requirementExists = await _db.Queryable<ObMaterialRequirement>()
.AnyAsync(x => x.CompanyId == input.CompanyId
&& x.PositionId == input.PositionId
&& x.MaterialTypeId == input.MaterialTypeId
&& x.Id != input.Id);
if (requirementExists)
throw Oops.Bah("该公司、岗位和资料类型的要求已存在");
var entity = new ObMaterialRequirement
{
Id = input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
CompanyId = input.CompanyId,
PositionId = input.PositionId,
MaterialTypeId = input.MaterialTypeId,
Required = input.Required,
SortCode = input.SortCode
};
if (input.Id.HasValue)
{
await _db.Updateable(entity).ExecuteCommandAsync();
return;
}
await _db.Insertable(entity).ExecuteCommandAsync();
}
public Task DeleteMaterialRequirements(BaseIdListInput input)
{
if (input.Ids.Count == 0)
return Task.CompletedTask;
return _db.Deleteable<ObMaterialRequirement>().In(input.Ids).ExecuteCommandAsync();
}
private async Task EnsureCompanyAndPosition(long companyId, long? positionId)
{
var companyExists = await _db.Queryable<ObCompany>()
.AnyAsync(x => x.Id == companyId && x.Status == "enable");
if (!companyExists)
throw Oops.Bah("公司不存在或未启用");
if (!positionId.HasValue)
return;
var positionExists = await _db.Queryable<ObPosition>()
.AnyAsync(x => x.Id == positionId.Value && x.CompanyId == companyId && x.Status == "enable");
if (!positionExists)
throw Oops.Bah("岗位不存在、未启用或不属于该公司");
}
}
@@ -0,0 +1,2 @@
namespace SimpleAdmin.Application;
public interface IOnboardingTaskLifecycleService : ITransient { Task Archive(OnboardingAuditActionInput input); Task Revoke(OnboardingAuditActionInput input); }
@@ -0,0 +1,90 @@
namespace SimpleAdmin.Application;
public class OnboardingTaskLifecycleService : IOnboardingTaskLifecycleService
{
private readonly ISqlSugarClient _db;
private readonly IConfigService _config;
public OnboardingTaskLifecycleService(ISqlSugarClient db, IConfigService config)
{
_db = db;
_config = config;
}
public async Task Archive(OnboardingAuditActionInput input)
{
var task = await Authorize(input.TaskId);
if (task.TaskStatus != OnboardingTaskStatus.Approved)
{
throw Oops.Bah("仅审核通过任务可归档");
}
task.TaskStatus = OnboardingTaskStatus.Archived;
task.ArchivedAt = DateTime.Now;
await _db.Updateable(task).ExecuteCommandAsync();
await Log(task, OnboardingAuditAction.Archive, input.AuditOpinion);
}
public async Task Revoke(OnboardingAuditActionInput input)
{
var task = await Authorize(input.TaskId);
if (task.TaskStatus == OnboardingTaskStatus.Approved || task.TaskStatus == OnboardingTaskStatus.Archived)
{
throw Oops.Bah("已通过或已归档任务不能撤销");
}
task.TaskStatus = OnboardingTaskStatus.Revoked;
await _db.Updateable(task).ExecuteCommandAsync();
await _db.Updateable<ObTaskLink>()
.SetColumns(item => new ObTaskLink
{
LinkStatus = OnboardingLinkStatus.Revoked,
RevokedAt = DateTime.Now
})
.Where(item => item.TaskId == task.Id && item.LinkStatus == OnboardingLinkStatus.Active)
.ExecuteCommandAsync();
await Log(task, OnboardingAuditAction.Revoke, input.AuditOpinion);
}
private async Task<ObOnboardingTask> Authorize(long taskId)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(taskId)
?? throw Oops.Bah("入职任务不存在");
var config = await _config.GetByConfigKey(CateGoryConst.CONFIG_BIZ_DEFINE, "onboard.reviewer.userIds")
?? throw Oops.Bah("请配置入职审核人");
using var configJson = global::System.Text.Json.JsonDocument.Parse(config.ConfigValue);
var reviewerIds = new List<long>();
if (configJson.RootElement.TryGetProperty("all", out var allReviewers))
{
reviewerIds.AddRange(allReviewers.EnumerateArray().Select(item => item.GetInt64()));
}
if (configJson.RootElement.TryGetProperty("company", out var companyReviewers)
&& companyReviewers.TryGetProperty(task.CompanyId.ToString(), out var reviewers))
{
reviewerIds.AddRange(reviewers.EnumerateArray().Select(item => item.GetInt64()));
}
if (!reviewerIds.Contains(UserManager.UserId))
{
throw Oops.Bah("您不是该入职任务的审核人");
}
return task;
}
private Task Log(ObOnboardingTask task, string action, string opinion)
{
return _db.Insertable(new ObTaskAudit
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
AuditRound = task.SubmitCount,
AuditAction = action,
AuditorUserId = UserManager.UserId,
AuditOpinion = opinion,
AuditedAt = DateTime.Now
}).ExecuteCommandAsync();
}
}
@@ -0,0 +1,10 @@
namespace SimpleAdmin.Application;
public class OnboardingMaterialReviewInput
{
[IdNotNull(ErrorMessage = "任务资料Id不能为空")]
public long TaskMaterialId { get; set; }
public string ReviewStatus { get; set; }
public string ReviewRemark { get; set; }
}
@@ -0,0 +1,2 @@
namespace SimpleAdmin.Application;
public interface IOnboardingMaterialReviewService : ITransient { Task Review(OnboardingMaterialReviewInput input); }
@@ -0,0 +1,65 @@
namespace SimpleAdmin.Application;
public class OnboardingMaterialReviewService : IOnboardingMaterialReviewService
{
private readonly ISqlSugarClient _db;
private readonly IConfigService _config;
public OnboardingMaterialReviewService(ISqlSugarClient db, IConfigService config)
{
_db = db;
_config = config;
}
public async Task Review(OnboardingMaterialReviewInput input)
{
if (input.ReviewStatus != TaskMaterialReviewStatus.Passed
&& input.ReviewStatus != TaskMaterialReviewStatus.NeedSupplement)
{
throw Oops.Bah("资料审核状态不正确");
}
if (input.ReviewStatus == TaskMaterialReviewStatus.NeedSupplement
&& string.IsNullOrWhiteSpace(input.ReviewRemark))
{
throw Oops.Bah("请填写需补充原因");
}
var material = await _db.Queryable<ObTaskMaterial>().InSingleAsync(input.TaskMaterialId)
?? throw Oops.Bah("任务资料不存在");
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(material.TaskId)
?? throw Oops.Bah("入职任务不存在");
if (task.TaskStatus != OnboardingTaskStatus.Reviewing)
{
throw Oops.Bah("当前任务不在审核中");
}
await EnsureReviewer(task);
material.ReviewStatus = input.ReviewStatus;
material.ReviewRemark = input.ReviewRemark;
await _db.Updateable(material).ExecuteCommandAsync();
}
private async Task EnsureReviewer(ObOnboardingTask task)
{
var config = await _config.GetByConfigKey(CateGoryConst.CONFIG_BIZ_DEFINE, "onboard.reviewer.userIds")
?? throw Oops.Bah("请配置入职审核人");
using var configJson = global::System.Text.Json.JsonDocument.Parse(config.ConfigValue);
var reviewerIds = new List<long>();
if (configJson.RootElement.TryGetProperty("all", out var allReviewers))
{
reviewerIds.AddRange(allReviewers.EnumerateArray().Select(item => item.GetInt64()));
}
if (configJson.RootElement.TryGetProperty("company", out var companyReviewers)
&& companyReviewers.TryGetProperty(task.CompanyId.ToString(), out var reviewers))
{
reviewerIds.AddRange(reviewers.EnumerateArray().Select(item => item.GetInt64()));
}
if (!reviewerIds.Contains(UserManager.UserId))
{
throw Oops.Bah("您不是该入职任务的审核人");
}
}
}
@@ -6,16 +6,27 @@ public class OnboardingPositionService : DbRepository<ObPosition>, IOnboardingPo
{ {
return await Context.Queryable<ObPosition>() return await Context.Queryable<ObPosition>()
.WhereIF(input.CompanyId.HasValue, it => it.CompanyId == input.CompanyId.Value) .WhereIF(input.CompanyId.HasValue, it => it.CompanyId == input.CompanyId.Value)
.WhereIF(!string.IsNullOrWhiteSpace(input.PositionName), it => it.PositionName.Contains(input.PositionName) || it.PositionCode.Contains(input.PositionName)) .WhereIF(
!string.IsNullOrWhiteSpace(input.PositionName),
it => it.PositionName.Contains(input.PositionName) || it.PositionCode.Contains(input.PositionName))
.WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status) .WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status)
.OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}") .OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}")
.OrderBy(it => it.PositionName) .OrderBy(it => it.PositionName)
.ToPagedListAsync(input.PageNum, input.PageSize); .ToPagedListAsync(input.PageNum, input.PageSize);
} }
public Task<List<ObPosition>> ListEnabled(long companyId) => Context.Queryable<ObPosition>().Where(it => it.CompanyId == companyId && it.Status == "enable").OrderBy(it => it.PositionName).ToListAsync(); public Task<List<ObPosition>> ListEnabled(long companyId)
{
return Context.Queryable<ObPosition>()
.Where(it => it.CompanyId == companyId && it.Status == "enable")
.OrderBy(it => it.PositionName)
.ToListAsync();
}
public async Task<ObPosition> Detail(BaseIdInput input) => await GetByIdAsync(input.Id) ?? throw Oops.Bah("岗位不存在"); public async Task<ObPosition> Detail(BaseIdInput input)
{
return await GetByIdAsync(input.Id) ?? throw Oops.Bah("岗位不存在");
}
public async Task Add(OnboardingPositionAddInput input) public async Task Add(OnboardingPositionAddInput input)
{ {
@@ -34,19 +45,34 @@ public class OnboardingPositionService : DbRepository<ObPosition>, IOnboardingPo
public async Task Delete(BaseIdListInput input) public async Task Delete(BaseIdListInput input)
{ {
if (input.Ids.Count == 0) return; if (input.Ids.Count == 0)
if (await Context.Queryable<ObOnboardingTask>().AnyAsync(it => input.Ids.Contains(it.PositionId))) throw Oops.Bah("岗位已有入职任务,不能删除"); return;
if (await Context.Queryable<ObEmployee>().AnyAsync(it => input.Ids.Contains(it.PositionId))) throw Oops.Bah("岗位已有正式员工,不能删除");
var hasTasks = await Context.Queryable<ObOnboardingTask>()
.AnyAsync(it => input.Ids.Contains(it.PositionId));
if (hasTasks)
throw Oops.Bah("岗位已有入职任务,不能删除");
var hasEmployees = await Context.Queryable<ObEmployee>()
.AnyAsync(it => input.Ids.Contains(it.PositionId));
if (hasEmployees)
throw Oops.Bah("岗位已有正式员工,不能删除");
await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray()); await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray());
} }
private async Task EnsureCompanyExists(long companyId) private async Task EnsureCompanyExists(long companyId)
{ {
if (!await Context.Queryable<ObCompany>().AnyAsync(it => it.Id == companyId)) throw Oops.Bah("所属公司不存在或已删除"); var exists = await Context.Queryable<ObCompany>().AnyAsync(it => it.Id == companyId);
if (!exists)
throw Oops.Bah("所属公司不存在或已删除");
} }
private async Task EnsureCodeUnique(long companyId, string code, long id) private async Task EnsureCodeUnique(long companyId, string code, long id)
{ {
if (await Context.Queryable<ObPosition>().AnyAsync(it => it.CompanyId == companyId && it.PositionCode == code && it.Id != id)) throw Oops.Bah("该公司下岗位编码已存在"); var exists = await Context.Queryable<ObPosition>()
.AnyAsync(it => it.CompanyId == companyId && it.PositionCode == code && it.Id != id);
if (exists)
throw Oops.Bah("该公司下岗位编码已存在");
} }
} }
@@ -0,0 +1,9 @@
namespace SimpleAdmin.Application;
public class OnboardingMaterialUploadValidationInput
{
public string Token { get; set; }
public long MaterialTypeId { get; set; }
public string FileName { get; set; }
public long FileLength { get; set; }
}
@@ -0,0 +1,38 @@
namespace SimpleAdmin.Application;
public class OnboardingDynamicValueInput
{
public long FieldId { get; set; }
public string FieldValue { get; set; }
}
public class OnboardingSaveDynamicValuesInput
{
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
public List<OnboardingDynamicValueInput> Values { get; set; } = new();
}
public class OnboardingSaveMaterialInput
{
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
public long MaterialTypeId { get; set; }
public long FileId { get; set; }
}
public class OnboardingConfirmContractInput
{
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
public long TaskContractId { get; set; }
}
public class OnboardingSubmitInput
{
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
}
@@ -0,0 +1,10 @@
namespace SimpleAdmin.Application;
public interface IOnboardingPublicSubmissionService : ITransient
{
Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input);
Task SaveMaterial(OnboardingSaveMaterialInput input);
Task ConfirmContract(OnboardingConfirmContractInput input);
Task Submit(OnboardingSubmitInput input);
Task ValidateMaterialUpload(OnboardingMaterialUploadValidationInput input);
}
@@ -0,0 +1,265 @@
namespace SimpleAdmin.Application;
public class OnboardingPublicSubmissionService : IOnboardingPublicSubmissionService
{
private readonly ISqlSugarClient _db;
private readonly IOnboardingContractSnapshotService _snapshotService;
public OnboardingPublicSubmissionService(
ISqlSugarClient db,
IOnboardingContractSnapshotService snapshotService)
{
_db = db;
_snapshotService = snapshotService;
}
public async Task SaveDynamicValues(OnboardingSaveDynamicValuesInput input)
{
var task = await GetEditableTask(input.Token);
var requirements = await GetEffectiveFormRequirements(task);
var fieldIds = input.Values.Select(item => item.FieldId).Distinct().ToList();
var hasInapplicableField = fieldIds.Any(id => requirements.All(requirement => requirement.FieldId != id));
if (hasInapplicableField)
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 old = await _db.Queryable<ObTaskFormValue>()
.FirstAsync(item => item.TaskId == task.Id && item.FieldId == field.Id);
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,
FieldValue = value.FieldValue,
SubmitRound = task.SubmitCount + 1
};
if (old == null)
{
await _db.Insertable(row).ExecuteCommandAsync();
}
else
{
await _db.Updateable(row).ExecuteCommandAsync();
}
}
}
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 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("该资料已达到最大上传数量");
await _db.Insertable(new ObTaskMaterial
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
MaterialTypeId = material.Id,
MaterialName = material.MaterialName,
FileId = input.FileId,
SubmitRound = task.SubmitCount + 1,
UploadedAt = DateTime.Now
}).ExecuteCommandAsync();
}
public async Task ConfirmContract(OnboardingConfirmContractInput input)
{
var task = await GetEditableTask(input.Token);
var contract = await _db.Queryable<ObTaskContract>()
.FirstAsync(item => item.Id == input.TaskContractId && item.TaskId == task.Id)
?? throw Oops.Bah("合同不存在");
contract.ReadAt ??= DateTime.Now;
contract.ConfirmedAt = DateTime.Now;
contract.ContractStatus = TaskContractStatus.Confirmed;
await _db.Updateable(contract).ExecuteCommandAsync();
}
public async Task Submit(OnboardingSubmitInput input)
{
var task = await GetEditableTask(input.Token);
var info = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id);
var basicInfoIncomplete = info == null
|| string.IsNullOrWhiteSpace(info.EmployeeName)
|| string.IsNullOrWhiteSpace(info.Phone)
|| string.IsNullOrWhiteSpace(info.IdCardNumber);
if (basicInfoIncomplete)
throw Oops.Bah("请先完整填写基本信息");
await _snapshotService.Build(task.Id, info);
var requiredFields = (await GetEffectiveFormRequirements(task))
.Where(item => item.Required)
.Select(item => item.FieldId)
.ToList();
var values = await _db.Queryable<ObTaskFormValue>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var hasMissingRequiredField = requiredFields.Any(
id => values.All(item => item.FieldId != id || string.IsNullOrWhiteSpace(item.FieldValue)));
if (hasMissingRequiredField)
throw Oops.Bah("仍有必填动态信息未填写");
var requiredMaterials = (await GetEffectiveMaterialRequirements(task))
.Where(item => item.Required)
.Select(item => item.MaterialTypeId)
.ToList();
var materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var hasMissingRequiredMaterial = requiredMaterials.Any(
id => materials.All(item => item.MaterialTypeId != id));
if (hasMissingRequiredMaterial)
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("请确认阅读全部必签合同");
task.TaskStatus = OnboardingTaskStatus.Reviewing;
task.SubmitCount++;
task.LastSubmittedAt = DateTime.Now;
task.RejectReason = null;
await _db.Updateable(task).ExecuteCommandAsync();
await _db.Insertable(new ObTaskAudit
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
AuditRound = task.SubmitCount,
AuditAction = OnboardingAuditAction.Submit,
AuditOpinion = "员工提交入职资料",
SnapshotJson = global::System.Text.Json.JsonSerializer.Serialize(new { info, values, materials }),
AuditedAt = DateTime.Now
}).ExecuteCommandAsync();
}
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 suffix = Path.GetExtension(input.FileName).TrimStart('.').ToLowerInvariant();
var allowedExtensions = (material.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("该资料不允许上传此文件类型");
var fileTooLarge = material.MaxFileSizeMb.HasValue
&& input.FileLength > material.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("该资料已达到最大上传数量");
}
private async Task EnsureRequiredContractVariables(ObOnboardingTask task)
{
var contracts = await _db.Queryable<ObTaskContract>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var contractIds = contracts.Select(contract => contract.Id).ToList();
var templateVersionIds = contracts.Select(contract => contract.TemplateVersionId).ToList();
var variables = await _db.Queryable<ObTaskContractVariable>()
.Where(item => contractIds.Contains(item.TaskContractId))
.ToListAsync();
var definitions = await _db.Queryable<ObTemplateVariable>()
.Where(item => templateVersionIds.Contains(item.TemplateVersionId) && item.Required)
.ToListAsync();
var missing = contracts
.SelectMany(contract => definitions
.Where(definition => definition.TemplateVersionId == contract.TemplateVersionId)
.Select(definition => new { contract, definition }))
.FirstOrDefault(item => string.IsNullOrWhiteSpace(
variables.FirstOrDefault(variable => variable.TaskContractId == item.contract.Id
&& variable.VariableCode == item.definition.VariableCode)
?.VariableValue));
if (missing != null)
throw Oops.Bah($"合同“{missing.contract.ContractName}”的必填变量“{missing.definition.VariableName}”尚未填写");
}
private async Task<List<ObFormRequirement>> GetEffectiveFormRequirements(ObOnboardingTask task)
{
var requirements = await _db.Queryable<ObFormRequirement>()
.Where(item => item.CompanyId == task.CompanyId
&& (item.PositionId == null || item.PositionId == task.PositionId))
.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)
{
var requirements = await _db.Queryable<ObMaterialRequirement>()
.Where(item => item.CompanyId == task.CompanyId
&& (item.PositionId == null || item.PositionId == task.PositionId))
.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)
{
var hash = Convert.ToHexString(
global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token)));
var link = await _db.Queryable<ObTaskLink>().FirstAsync(item => item.TokenHash == hash)
?? throw Oops.Bah("链接不存在");
var linkUnavailable = link.LinkStatus != OnboardingLinkStatus.Active || link.ExpiresAt <= DateTime.Now;
if (linkUnavailable)
throw Oops.Bah("链接已失效");
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(link.TaskId)
?? throw Oops.Bah("入职任务不存在");
var taskNotEditable = task.TaskStatus != OnboardingTaskStatus.PendingFill
&& task.TaskStatus != OnboardingTaskStatus.Rejected;
if (taskNotEditable)
throw Oops.Bah("当前任务不可编辑");
return task;
}
}
@@ -0,0 +1,8 @@
namespace SimpleAdmin.Application;
public interface IOnboardingPublicTaskService : ITransient
{
Task<OnboardingTaskPublicDetailOutput> Detail(OnboardingTaskPublicAccessInput input);
Task SaveEmployeeInfo(OnboardingTaskPublicSaveInput input);
Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input);
}
@@ -0,0 +1,156 @@
namespace SimpleAdmin.Application;
/// <summary>
/// 员工手机端入口;仅凭一次性任务链接访问,绝不创建正式员工。
/// </summary>
public class OnboardingPublicTaskService : IOnboardingPublicTaskService
{
private readonly ISqlSugarClient _db;
private readonly IOnboardingContractSnapshotService _snapshotService;
public OnboardingPublicTaskService(
ISqlSugarClient db,
IOnboardingContractSnapshotService snapshotService)
{
_db = db;
_snapshotService = snapshotService;
}
public async Task<OnboardingTaskPublicDetailOutput> Detail(OnboardingTaskPublicAccessInput input)
{
var link = await GetActiveLink(input.Token);
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(link.TaskId)
?? throw Oops.Bah("入职任务不存在");
link.AccessCount++;
link.LastAccessedAt = DateTime.Now;
await _db.Updateable(link).ExecuteCommandAsync();
var formRequirements = await GetEffectiveFormRequirements(task);
var materialRequirements = await GetEffectiveMaterialRequirements(task);
var fields = await _db.Queryable<ObFormField>()
.Where(x => formRequirements.Select(r => r.FieldId).Contains(x.Id) && x.Status == "enable")
.ToListAsync();
var materialTypes = await _db.Queryable<ObMaterialType>()
.Where(x => materialRequirements.Select(r => r.MaterialTypeId).Contains(x.Id) && x.Status == "enable")
.ToListAsync();
return new OnboardingTaskPublicDetailOutput
{
Task = task,
EmployeeInfo = await _db.Queryable<ObTaskEmployeeInfo>().FirstAsync(it => it.TaskId == task.Id),
Contracts = await _db.Queryable<ObTaskContract>()
.Where(it => it.TaskId == task.Id)
.ToListAsync(),
DynamicValues = await _db.Queryable<ObTaskFormValue>()
.Where(x => x.TaskId == task.Id)
.ToListAsync(),
Materials = await _db.Queryable<ObTaskMaterial>()
.Where(x => x.TaskId == task.Id)
.ToListAsync(),
FormFields = formRequirements
.OrderBy(x => x.SortCode)
.Select(requirement => fields.FirstOrDefault(field => field.Id == requirement.FieldId))
.Where(field => field != null)
.Select(field => new OnboardingPublicFormFieldOutput
{
FieldId = field.Id,
FieldCode = field.FieldCode,
FieldName = field.FieldName,
DataType = field.DataType,
OptionsJson = field.OptionsJson,
Required = formRequirements.First(requirement => requirement.FieldId == field.Id).Required
})
.ToList(),
MaterialRequirements = materialRequirements
.OrderBy(x => x.SortCode)
.Select(requirement => materialTypes.FirstOrDefault(material => material.Id == requirement.MaterialTypeId))
.Where(material => material != null)
.Select(material => new OnboardingPublicMaterialOutput
{
MaterialTypeId = material.Id,
MaterialName = material.MaterialName,
AllowedExtensions = material.AllowedExtensions,
MaxFileCount = material.MaxFileCount,
Required = materialRequirements.First(requirement => requirement.MaterialTypeId == material.Id).Required
})
.ToList()
};
}
public async Task SaveEmployeeInfo(OnboardingTaskPublicSaveInput input)
{
var link = await GetActiveLink(input.Token);
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(link.TaskId)
?? throw Oops.Bah("入职任务不存在");
if (task.TaskStatus != OnboardingTaskStatus.PendingFill && task.TaskStatus != OnboardingTaskStatus.Rejected)
throw Oops.Bah("当前任务不可编辑");
var old = await _db.Queryable<ObTaskEmployeeInfo>().FirstAsync(it => it.TaskId == task.Id);
var entity = input.Adapt<ObTaskEmployeeInfo>();
entity.TaskId = task.Id;
if (old == null)
{
entity.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
await _db.Insertable(entity).ExecuteCommandAsync();
}
else
{
entity.Id = old.Id;
await _db.Updateable(entity).ExecuteCommandAsync();
}
await _snapshotService.Build(task.Id, entity);
}
public async Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input)
{
var link = await GetActiveLink(input.Token);
var contract = await _db.Queryable<ObTaskContract>()
.FirstAsync(x => x.Id == input.TaskContractId && x.TaskId == link.TaskId)
?? throw Oops.Bah("合同不存在");
var version = await _db.Queryable<ObContractTemplateVersion>().InSingleAsync(contract.TemplateVersionId)
?? throw Oops.Bah("合同模板版本不存在");
return version.SourceDocxFileId;
}
private async Task<List<ObFormRequirement>> GetEffectiveFormRequirements(ObOnboardingTask task)
{
var requirements = await _db.Queryable<ObFormRequirement>()
.Where(x => x.CompanyId == task.CompanyId && (x.PositionId == null || x.PositionId == task.PositionId))
.ToListAsync();
return requirements
.GroupBy(x => x.FieldId)
.Select(group => group.FirstOrDefault(x => x.PositionId == task.PositionId) ?? group.First())
.ToList();
}
private async Task<List<ObMaterialRequirement>> GetEffectiveMaterialRequirements(ObOnboardingTask task)
{
var requirements = await _db.Queryable<ObMaterialRequirement>()
.Where(x => x.CompanyId == task.CompanyId && (x.PositionId == null || x.PositionId == task.PositionId))
.ToListAsync();
return requirements
.GroupBy(x => x.MaterialTypeId)
.Select(group => group.FirstOrDefault(x => x.PositionId == task.PositionId) ?? group.First())
.ToList();
}
private async Task<ObTaskLink> GetActiveLink(string token)
{
var hash = Convert.ToHexString(
global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token)));
var link = await _db.Queryable<ObTaskLink>().FirstAsync(it => it.TokenHash == hash)
?? throw Oops.Bah("链接不存在");
if (link.LinkStatus != OnboardingLinkStatus.Active || link.ExpiresAt <= DateTime.Now)
throw Oops.Bah("链接已失效");
return link;
}
}
@@ -1,11 +1,61 @@
namespace SimpleAdmin.Application; namespace SimpleAdmin.Application;
public class OnboardingTaskPublicAccessInput { [global::System.ComponentModel.DataAnnotations.Required] public string Token { get; set; } } public class OnboardingTaskPublicAccessInput
{
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
}
public class OnboardingPublicContractSourceInput
{
[global::System.ComponentModel.DataAnnotations.Required]
public string Token { get; set; }
[IdNotNull(ErrorMessage = "任务合同Id不能为空")]
public long TaskContractId { get; set; }
}
public class OnboardingTaskPublicSaveInput : ObTaskEmployeeInfo public class OnboardingTaskPublicSaveInput : ObTaskEmployeeInfo
{ {
[global::System.ComponentModel.DataAnnotations.Required] public string Token { get; set; } [global::System.ComponentModel.DataAnnotations.Required]
[global::System.ComponentModel.DataAnnotations.Required] public new string EmployeeName { get; set; } public string Token { get; set; }
[global::System.ComponentModel.DataAnnotations.Required] public new string Phone { get; set; }
[global::System.ComponentModel.DataAnnotations.Required] public new string IdCardNumber { get; set; } [global::System.ComponentModel.DataAnnotations.Required]
public new string EmployeeName { get; set; }
[global::System.ComponentModel.DataAnnotations.Required]
public new string Phone { get; set; }
[global::System.ComponentModel.DataAnnotations.Required]
public new string IdCardNumber { get; set; }
}
public class OnboardingPublicFormFieldOutput
{
public long FieldId { get; set; }
public string FieldCode { get; set; }
public string FieldName { get; set; }
public string DataType { get; set; }
public string OptionsJson { get; set; }
public bool Required { get; set; }
}
public class OnboardingPublicMaterialOutput
{
public long MaterialTypeId { get; set; }
public string MaterialName { get; set; }
public string AllowedExtensions { get; set; }
public int MaxFileCount { get; set; }
public bool Required { get; set; }
}
public class OnboardingTaskPublicDetailOutput
{
public ObOnboardingTask Task { get; set; }
public ObTaskEmployeeInfo EmployeeInfo { get; set; }
public List<ObTaskContract> Contracts { get; set; }
public List<ObTaskFormValue> DynamicValues { get; set; }
public List<OnboardingPublicFormFieldOutput> FormFields { get; set; }
public List<OnboardingPublicMaterialOutput> MaterialRequirements { get; set; }
public List<ObTaskMaterial> Materials { get; set; }
} }
public class OnboardingTaskPublicDetailOutput { public ObOnboardingTask Task { get; set; } public ObTaskEmployeeInfo EmployeeInfo { get; set; } public List<ObTaskContract> Contracts { get; set; } }
@@ -4,31 +4,124 @@ public class OnboardingTaskService : IOnboardingTaskService
{ {
private readonly ISqlSugarClient _db; private readonly ISqlSugarClient _db;
private readonly IConfigService _configService; private readonly IConfigService _configService;
public OnboardingTaskService(ISqlSugarClient db, IConfigService configService) { _db = db; _configService = configService; } private readonly IOnboardingContractSnapshotService _snapshotService;
public OnboardingTaskService(
ISqlSugarClient db,
IConfigService configService,
IOnboardingContractSnapshotService snapshotService)
{
_db = db;
_configService = configService;
_snapshotService = snapshotService;
}
public async Task<OnboardingTaskCreatedOutput> Create(OnboardingTaskCreateInput input) public async Task<OnboardingTaskCreatedOutput> Create(OnboardingTaskCreateInput input)
{ {
if (!await _db.Queryable<ObCompany>().AnyAsync(it => it.Id == input.CompanyId && it.Status == "enable")) throw Oops.Bah("公司不存在或未启用"); var companyEnabled = await _db.Queryable<ObCompany>()
if (!await _db.Queryable<ObPosition>().AnyAsync(it => it.Id == input.PositionId && it.CompanyId == input.CompanyId && it.Status == "enable")) throw Oops.Bah("岗位不存在、未启用或不属于该公司"); .AnyAsync(item => item.Id == input.CompanyId && item.Status == "enable");
var scopes = await _db.Queryable<ObContractScope>().Where(it => it.CompanyId == input.CompanyId && (it.PositionId == null || it.PositionId == input.PositionId)).ToListAsync(); if (!companyEnabled)
if (scopes.Count == 0) throw Oops.Bah("该公司和岗位尚未配置合同适用范围"); {
throw Oops.Bah("公司不存在或未启用");
}
var positionEnabled = await _db.Queryable<ObPosition>()
.AnyAsync(item => item.Id == input.PositionId
&& item.CompanyId == input.CompanyId
&& item.Status == "enable");
if (!positionEnabled)
{
throw Oops.Bah("岗位不存在、未启用或不属于该公司");
}
var scopes = await _db.Queryable<ObContractScope>()
.Where(item => item.CompanyId == input.CompanyId
&& (item.PositionId == null || item.PositionId == input.PositionId))
.ToListAsync();
// 同一版本同时存在“全公司”和“指定岗位”范围时,岗位专属配置优先,避免同一合同生成两次。
scopes = scopes
.GroupBy(item => item.TemplateVersionId)
.Select(group => group.FirstOrDefault(item => item.PositionId == input.PositionId) ?? group.First())
.ToList();
if (scopes.Count == 0)
{
throw Oops.Bah("该公司和岗位尚未配置合同适用范围");
}
var taskId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); var taskId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
var expiryConfig = await _configService.GetByConfigKey(CateGoryConst.CONFIG_BIZ_DEFINE, "onboard.link.expireHours"); var expiryConfig = await _configService.GetByConfigKey(
if (expiryConfig == null || !int.TryParse(expiryConfig.ConfigValue, out var expiryHours) || expiryHours <= 0) throw Oops.Bah("请在系统配置中设置 onboard.link.expireHours(正整数小时)"); CateGoryConst.CONFIG_BIZ_DEFINE,
"onboard.link.expireHours");
if (expiryConfig == null
|| !int.TryParse(expiryConfig.ConfigValue, out var expiryHours)
|| expiryHours <= 0)
{
throw Oops.Bah("请在系统配置中设置 onboard.link.expireHours(正整数小时)");
}
var expiresAt = DateTime.Now.AddHours(expiryHours); var expiresAt = DateTime.Now.AddHours(expiryHours);
var token = Guid.NewGuid().ToString("N"); var token = Guid.NewGuid().ToString("N");
var task = new ObOnboardingTask { Id = taskId, TaskNo = $"OB{DateTime.Now:yyyyMMddHHmmss}{taskId.ToString()[^4..]}", CompanyId = input.CompanyId, PositionId = input.PositionId, AssignedReviewerUserId = input.AssignedReviewerUserId, LinkExpireAt = expiresAt, TaskStatus = OnboardingTaskStatus.PendingFill, Remark = input.Remark }; var task = new ObOnboardingTask
{
Id = taskId,
TaskNo = $"OB{DateTime.Now:yyyyMMddHHmmss}{taskId.ToString()[^4..]}",
CompanyId = input.CompanyId,
PositionId = input.PositionId,
AssignedReviewerUserId = input.AssignedReviewerUserId,
LinkExpireAt = expiresAt,
TaskStatus = OnboardingTaskStatus.PendingFill,
Remark = input.Remark
};
await _db.Ado.BeginTranAsync(); await _db.Ado.BeginTranAsync();
try try
{ {
await _db.Insertable(task).ExecuteCommandAsync(); await _db.Insertable(task).ExecuteCommandAsync();
await _db.Insertable(new ObTaskLink { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskId = taskId, TokenHash = Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(global::System.Text.Encoding.UTF8.GetBytes(token))), ExpiresAt = expiresAt }).ExecuteCommandAsync(); await _db.Insertable(new ObTaskLink
var templateIds = scopes.Select(it => it.TemplateId).Distinct().ToList(); {
var templates = await _db.Queryable<ObContractTemplate>().Where(it => templateIds.Contains(it.Id)).ToListAsync(); Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
await _db.Insertable(scopes.Select(it => new ObTaskContract { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskId = taskId, TemplateId = it.TemplateId, TemplateVersionId = it.TemplateVersionId, ContractName = templates.First(x => x.Id == it.TemplateId).TemplateName, ContractType = templates.First(x => x.Id == it.TemplateId).ContractType, Required = it.Required }).ToList()).ExecuteCommandAsync(); TaskId = taskId,
TokenHash = Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token))),
ExpiresAt = expiresAt
}).ExecuteCommandAsync();
var templateIds = scopes.Select(item => item.TemplateId).Distinct().ToList();
var templates = await _db.Queryable<ObContractTemplate>()
.Where(item => templateIds.Contains(item.Id))
.ToListAsync();
var contracts = scopes.Select(scope =>
{
var template = templates.First(item => item.Id == scope.TemplateId);
return new ObTaskContract
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = taskId,
TemplateId = scope.TemplateId,
TemplateVersionId = scope.TemplateVersionId,
ContractName = template.TemplateName,
ContractType = template.ContractType,
Required = scope.Required
};
}).ToList();
await _db.Insertable(contracts).ExecuteCommandAsync();
await _snapshotService.Build(taskId);
await _db.Ado.CommitTranAsync(); await _db.Ado.CommitTranAsync();
} }
catch { await _db.Ado.RollbackTranAsync(); throw; } catch
return new OnboardingTaskCreatedOutput { TaskId = taskId, TaskNo = task.TaskNo, LinkToken = token, ExpiresAt = expiresAt }; {
await _db.Ado.RollbackTranAsync();
throw;
}
return new OnboardingTaskCreatedOutput
{
TaskId = taskId,
TaskNo = task.TaskNo,
LinkToken = token,
ExpiresAt = expiresAt
};
} }
} }
@@ -0,0 +1,43 @@
namespace SimpleAdmin.Application;
public class OnboardingTaskContractVariableQueryInput
{
[IdNotNull(ErrorMessage = "入职任务Id不能为空")]
public long TaskId { get; set; }
}
public class OnboardingTaskContractVariableSaveInput
{
[IdNotNull(ErrorMessage = "任务合同Id不能为空")]
public long TaskContractId { get; set; }
[global::System.ComponentModel.DataAnnotations.Required(ErrorMessage = "变量不能为空")]
public List<OnboardingTaskContractVariableValueInput> Values { get; set; } = new List<OnboardingTaskContractVariableValueInput>();
}
public class OnboardingTaskContractVariableValueInput
{
[IdNotNull(ErrorMessage = "变量Id不能为空")]
public long Id { get; set; }
public string VariableValue { get; set; }
}
public class OnboardingTaskContractVariableOutput
{
public long TaskContractId { get; set; }
public string ContractName { get; set; }
public string ContractType { get; set; }
public List<OnboardingTaskContractVariableItemOutput> Variables { get; set; } = new List<OnboardingTaskContractVariableItemOutput>();
}
public class OnboardingTaskContractVariableItemOutput
{
public long Id { get; set; }
public string VariableCode { get; set; }
public string VariableName { get; set; }
public string VariableType { get; set; }
public string DataType { get; set; }
public string VariableValue { get; set; }
public bool Required { get; set; }
public bool HrEditable { get; set; }
public bool Editable { get; set; }
}
@@ -0,0 +1,7 @@
namespace SimpleAdmin.Application;
public interface IOnboardingTaskContractVariableService : ITransient
{
Task<List<OnboardingTaskContractVariableOutput>> List(OnboardingTaskContractVariableQueryInput input);
Task Save(OnboardingTaskContractVariableSaveInput input);
}
@@ -0,0 +1,139 @@
namespace SimpleAdmin.Application;
/// <summary>
/// 管理端在链接发送前填写合同薪资、补贴等人事变量。
/// </summary>
public class OnboardingTaskContractVariableService : IOnboardingTaskContractVariableService
{
private readonly ISqlSugarClient _db;
public OnboardingTaskContractVariableService(ISqlSugarClient db)
=> _db = db;
public async Task<List<OnboardingTaskContractVariableOutput>> List(
OnboardingTaskContractVariableQueryInput input)
{
var task = await _db.Queryable<ObOnboardingTask>()
.InSingleAsync(input.TaskId)
?? throw Oops.Bah("入职任务不存在");
var contracts = await _db.Queryable<ObTaskContract>()
.Where(x => x.TaskId == task.Id)
.ToListAsync();
var contractIds = contracts.Select(x => x.Id).ToList();
var templateVersionIds = contracts.Select(x => x.TemplateVersionId).ToList();
var variables = await _db.Queryable<ObTaskContractVariable>()
.Where(x => contractIds.Contains(x.TaskContractId))
.ToListAsync();
var definitions = await _db.Queryable<ObTemplateVariable>()
.Where(x => templateVersionIds.Contains(x.TemplateVersionId))
.ToListAsync();
return contracts.Select(contract => new OnboardingTaskContractVariableOutput
{
TaskContractId = contract.Id,
ContractName = contract.ContractName,
ContractType = contract.ContractType,
Variables = variables
.Where(variable => variable.TaskContractId == contract.Id)
.OrderBy(variable => variable.VariableCode)
.Select(variable =>
{
var definition = definitions.FirstOrDefault(x =>
x.TemplateVersionId == contract.TemplateVersionId &&
x.VariableCode == variable.VariableCode);
var hrEditable = definition?.HrEditable == true;
return new OnboardingTaskContractVariableItemOutput
{
Id = variable.Id,
VariableCode = variable.VariableCode,
VariableName = variable.VariableName,
VariableType = variable.VariableType,
DataType = variable.DataType,
VariableValue = variable.VariableValue,
Required = definition?.Required == true,
HrEditable = hrEditable,
Editable =
variable.VariableType == ContractVariableType.Contract ||
(variable.VariableType == ContractVariableType.Company && hrEditable)
};
})
.ToList()
}).ToList();
}
public async Task Save(OnboardingTaskContractVariableSaveInput input)
{
var contract = await _db.Queryable<ObTaskContract>()
.InSingleAsync(input.TaskContractId)
?? throw Oops.Bah("任务合同不存在");
var task = await _db.Queryable<ObOnboardingTask>()
.InSingleAsync(contract.TaskId)
?? throw Oops.Bah("入职任务不存在");
if (task.TaskStatus != OnboardingTaskStatus.PendingFill &&
task.TaskStatus != OnboardingTaskStatus.Rejected)
{
throw Oops.Bah("任务已提交审核,不能再修改合同参数");
}
var valueIds = input.Values
.Select(x => x.Id)
.Distinct()
.ToList();
var rows = await _db.Queryable<ObTaskContractVariable>()
.Where(x =>
x.TaskContractId == contract.Id &&
valueIds.Contains(x.Id))
.ToListAsync();
if (rows.Count != valueIds.Count)
{
throw Oops.Bah("包含不属于该合同的变量");
}
var variableCodes = rows
.Select(x => x.VariableCode)
.ToList();
var definitions = await _db.Queryable<ObTemplateVariable>()
.Where(x =>
x.TemplateVersionId == contract.TemplateVersionId &&
variableCodes.Contains(x.VariableCode))
.ToListAsync();
foreach (var row in rows)
{
var definition = definitions.FirstOrDefault(x =>
x.VariableCode == row.VariableCode)
?? throw Oops.Bah("合同变量定义不存在");
if (row.VariableType != ContractVariableType.Contract &&
!(row.VariableType == ContractVariableType.Company &&
definition.HrEditable))
{
throw Oops.Bah($"变量“{row.VariableName}”不允许人事手工修改");
}
var inputValue = input.Values.First(x => x.Id == row.Id);
row.VariableValue = inputValue.VariableValue;
row.SourceType = ContractVariableSourceType.HrInput;
row.IsManualOverride = true;
}
if (rows.Count > 0)
{
await _db.Updateable(rows)
.ExecuteCommandAsync();
}
}
}
@@ -0,0 +1,17 @@
namespace SimpleAdmin.Application;
public class OnboardingTaskPageInput : BasePageInput
{
public string TaskNo { get; set; }
public string TaskStatus { get; set; }
public long? CompanyId { get; set; }
}
public class OnboardingTaskDetailOutput
{
public ObOnboardingTask Task { get; set; }
public ObTaskEmployeeInfo EmployeeInfo { get; set; }
public List<ObTaskContract> Contracts { get; set; }
public List<ObTaskMaterial> Materials { get; set; }
public List<ObTaskAudit> Audits { get; set; }
}
@@ -0,0 +1,8 @@
namespace SimpleAdmin.Application;
public interface IOnboardingTaskQueryService : ITransient
{
Task<SqlSugarPagedList<ObOnboardingTask>> Page(OnboardingTaskPageInput input);
Task<OnboardingTaskDetailOutput> Detail(BaseIdInput input);
}
@@ -0,0 +1,44 @@
namespace SimpleAdmin.Application;
public class OnboardingTaskQueryService : IOnboardingTaskQueryService
{
private readonly ISqlSugarClient _db;
public OnboardingTaskQueryService(ISqlSugarClient db)
{
_db = db;
}
public Task<SqlSugarPagedList<ObOnboardingTask>> Page(OnboardingTaskPageInput input)
{
return _db.Queryable<ObOnboardingTask>()
.WhereIF(!string.IsNullOrWhiteSpace(input.TaskNo), item => item.TaskNo.Contains(input.TaskNo))
.WhereIF(!string.IsNullOrWhiteSpace(input.TaskStatus), item => item.TaskStatus == input.TaskStatus)
.WhereIF(input.CompanyId.HasValue, item => item.CompanyId == input.CompanyId.Value)
.OrderByDescending(item => item.CreateTime)
.ToPagedListAsync(input.PageNum, input.PageSize);
}
public async Task<OnboardingTaskDetailOutput> Detail(BaseIdInput input)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(input.Id)
?? throw Oops.Bah("入职任务不存在");
return new OnboardingTaskDetailOutput
{
Task = task,
EmployeeInfo = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id),
Contracts = await _db.Queryable<ObTaskContract>()
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
Materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
Audits = await _db.Queryable<ObTaskAudit>()
.Where(item => item.TaskId == task.Id)
.OrderByDescending(item => item.AuditedAt)
.ToListAsync()
};
}
}
@@ -99,8 +99,32 @@
系统配置 系统配置
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.IOnboardingContractSnapshotService.Build(System.Int64,SimpleAdmin.Application.ObTaskEmployeeInfo)">
<summary>根据任务、公司与候选人资料刷新自动变量;人事已手工填写的变量不会被覆盖。</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingContractSnapshotService">
<summary>冻结任务合同变量;DOCX 生成阶段只读取本快照,不再读取模板实时值。</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingContractTemplateService"> <member name="T:SimpleAdmin.Application.OnboardingContractTemplateService">
<summary>合同模板主档案;具体 DOCX 文件、变量与适用范围均挂在此档案下。</summary> <summary>
合同模板主档案;具体 DOCX 文件、变量与适用范围均挂在此档案下。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingEmployeeService">
<summary>仅展示审核通过后创建的正式员工档案;入职资料的来源任务可追溯。</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingEntryConfigService">
<summary>
维护员工端动态字段、材料类型及公司岗位要求。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingPublicTaskService">
<summary>
员工手机端入口;仅凭一次性任务链接访问,绝不创建正式员工。
</summary>
</member>
<member name="T:SimpleAdmin.Application.OnboardingTaskContractVariableService">
<summary>管理端在链接发送前填写合同薪资、补贴等人事变量。</summary>
</member> </member>
<member name="T:SimpleAdmin.Application.IOrgService"> <member name="T:SimpleAdmin.Application.IOrgService">
<summary> <summary>
@@ -1,4 +1,4 @@
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 // Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
// //
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款: // SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
// 1.请不要删除和修改根目录下的LICENSE文件。 // 1.请不要删除和修改根目录下的LICENSE文件。
@@ -100,6 +100,16 @@ public class FileService : DbRepository<SysFile>, IFileService
return null; return null;
} }
public async Task<long> StoreGeneratedFile(string engine, string fileName, byte[] content)
{
await using var stream = new MemoryStream(content);
IFormFile file = new FormFile(stream, 0, content.Length, "file", fileName)
{
Headers = new HeaderDictionary(),
ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
};
return await StorageFile(engine, file);
}
#region 方法 #region 方法
/// <summary> /// <summary>
@@ -1,4 +1,4 @@
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛 // Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
// //
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款: // SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
// 1.请不要删除和修改根目录下的LICENSE文件。 // 1.请不要删除和修改根目录下的LICENSE文件。
@@ -60,4 +60,5 @@ public interface IFileService : ITransient
/// <param name="file">文件</param> /// <param name="file">文件</param>
/// <returns></returns> /// <returns></returns>
Task<long> UploadFile(string engine, IFormFile file); Task<long> UploadFile(string engine, IFormFile file);
Task<long> StoreGeneratedFile(string engine, string fileName, byte[] content);
} }
@@ -0,0 +1,26 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职审核")]
[Route("biz/onboarding/audit")]
[RolePermission]
public class OnboardingAuditController
{
private readonly IOnboardingAuditService _service;
public OnboardingAuditController(IOnboardingAuditService service)
{
_service = service;
}
[HttpPost("approve")]
public async Task Approve([FromBody] OnboardingAuditActionInput input)
{
await _service.Approve(input);
}
[HttpPost("reject")]
public async Task Reject([FromBody] OnboardingAuditActionInput input)
{
await _service.Reject(input);
}
}
@@ -6,12 +6,45 @@ namespace SimpleAdmin.Web.Core;
public class OnboardingCompanyController public class OnboardingCompanyController
{ {
private readonly IOnboardingCompanyService _service; private readonly IOnboardingCompanyService _service;
public OnboardingCompanyController(IOnboardingCompanyService service) => _service = service;
[HttpGet("page")] public async Task<dynamic> Page([FromQuery] OnboardingCompanyPageInput input) => await _service.Page(input); public OnboardingCompanyController(IOnboardingCompanyService service)
[HttpGet("list-enabled")] public async Task<dynamic> ListEnabled() => await _service.ListEnabled(); {
[HttpGet("detail")] public async Task<dynamic> Detail([FromQuery] BaseIdInput input) => await _service.Detail(input); _service = service;
[HttpPost("add")] public async Task Add([FromBody] OnboardingCompanyAddInput input) => await _service.Add(input); }
[HttpPost("edit")] public async Task Edit([FromBody] OnboardingCompanyEditInput input) => await _service.Edit(input);
[HttpPost("delete")] public async Task Delete([FromBody] BaseIdListInput input) => await _service.Delete(input); [HttpGet("page")]
} public async Task<dynamic> Page([FromQuery] OnboardingCompanyPageInput input)
{
return await _service.Page(input);
}
[HttpGet("list-enabled")]
public async Task<dynamic> ListEnabled()
{
return await _service.ListEnabled();
}
[HttpGet("detail")]
public async Task<dynamic> Detail([FromQuery] BaseIdInput input)
{
return await _service.Detail(input);
}
[HttpPost("add")]
public async Task Add([FromBody] OnboardingCompanyAddInput input)
{
await _service.Add(input);
}
[HttpPost("edit")]
public async Task Edit([FromBody] OnboardingCompanyEditInput input)
{
await _service.Edit(input);
}
[HttpPost("delete")]
public async Task Delete([FromBody] BaseIdListInput input)
{
await _service.Delete(input);
}
}
@@ -0,0 +1,25 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职合同文件")]
[Route("biz/onboarding/contract-document")]
[RolePermission]
public class OnboardingContractDocumentController
{
private readonly IOnboardingContractDocumentService _service;
public OnboardingContractDocumentController(
IOnboardingContractDocumentService service)
{
_service = service;
}
/// <summary>
/// 生成入职合同文件
/// </summary>
[HttpPost("generate")]
public async Task<long> Generate(
[FromBody] OnboardingGenerateContractInput input)
{
return await _service.Generate(input);
}
}
@@ -6,20 +6,105 @@ namespace SimpleAdmin.Web.Core;
public class OnboardingContractTemplateController public class OnboardingContractTemplateController
{ {
private readonly IOnboardingContractTemplateService _service; private readonly IOnboardingContractTemplateService _service;
public OnboardingContractTemplateController(IOnboardingContractTemplateService service) => _service = service;
[HttpGet("page")] public async Task<dynamic> Page([FromQuery] OnboardingContractTemplatePageInput input) => await _service.Page(input); public OnboardingContractTemplateController(IOnboardingContractTemplateService service)
[HttpGet("detail")] public async Task<dynamic> Detail([FromQuery] BaseIdInput input) => await _service.Detail(input); {
[HttpPost("add")] public async Task Add([FromBody] OnboardingContractTemplateAddInput input) => await _service.Add(input); _service = service;
[HttpPost("edit")] public async Task Edit([FromBody] OnboardingContractTemplateEditInput input) => await _service.Edit(input); }
[HttpPost("delete")] public async Task Delete([FromBody] BaseIdListInput input) => await _service.Delete(input);
[HttpGet("version/page")] public async Task<dynamic> VersionPage([FromQuery] OnboardingContractVersionPageInput input) => await _service.VersionPage(input); [HttpGet("page")]
[HttpPost("version/add")] public async Task AddVersion([FromBody] OnboardingContractVersionAddInput input) => await _service.AddVersion(input); public async Task<dynamic> Page([FromQuery] OnboardingContractTemplatePageInput input)
[HttpPost("version/set-status")] public async Task SetVersionStatus([FromBody] OnboardingContractVersionStatusInput input) => await _service.SetVersionStatus(input); {
[HttpGet("variable/page")] public async Task<dynamic> VariablePage([FromQuery] OnboardingTemplateVariablePageInput input) => await _service.VariablePage(input); return await _service.Page(input);
[HttpPost("variable/add")] public async Task AddVariable([FromBody] OnboardingTemplateVariableAddInput input) => await _service.AddVariable(input); }
[HttpPost("variable/edit")] public async Task EditVariable([FromBody] OnboardingTemplateVariableEditInput input) => await _service.EditVariable(input);
[HttpPost("variable/delete")] public async Task DeleteVariable([FromBody] BaseIdListInput input) => await _service.DeleteVariable(input); [HttpGet("scope/page")] public async Task<dynamic> ScopePage([FromQuery] OnboardingContractScopePageInput input) => await _service.ScopePage(input); [HttpGet("detail")]
[HttpPost("scope/add")] public async Task AddScope([FromBody] OnboardingContractScopeAddInput input) => await _service.AddScope(input); public async Task<dynamic> Detail([FromQuery] BaseIdInput input)
[HttpPost("scope/edit")] public async Task EditScope([FromBody] OnboardingContractScopeEditInput input) => await _service.EditScope(input); {
[HttpPost("scope/delete")] public async Task DeleteScope([FromBody] BaseIdListInput input) => await _service.DeleteScope(input);} return await _service.Detail(input);
}
[HttpPost("add")]
public async Task Add([FromBody] OnboardingContractTemplateAddInput input)
{
await _service.Add(input);
}
[HttpPost("edit")]
public async Task Edit([FromBody] OnboardingContractTemplateEditInput input)
{
await _service.Edit(input);
}
[HttpPost("delete")]
public async Task Delete([FromBody] BaseIdListInput input)
{
await _service.Delete(input);
}
[HttpGet("version/page")]
public async Task<dynamic> VersionPage([FromQuery] OnboardingContractVersionPageInput input)
{
return await _service.VersionPage(input);
}
[HttpPost("version/add")]
public async Task AddVersion([FromBody] OnboardingContractVersionAddInput input)
{
await _service.AddVersion(input);
}
[HttpPost("version/set-status")]
public async Task SetVersionStatus([FromBody] OnboardingContractVersionStatusInput input)
{
await _service.SetVersionStatus(input);
}
[HttpGet("variable/page")]
public async Task<dynamic> VariablePage([FromQuery] OnboardingTemplateVariablePageInput input)
{
return await _service.VariablePage(input);
}
[HttpPost("variable/add")]
public async Task AddVariable([FromBody] OnboardingTemplateVariableAddInput input)
{
await _service.AddVariable(input);
}
[HttpPost("variable/edit")]
public async Task EditVariable([FromBody] OnboardingTemplateVariableEditInput input)
{
await _service.EditVariable(input);
}
[HttpPost("variable/delete")]
public async Task DeleteVariable([FromBody] BaseIdListInput input)
{
await _service.DeleteVariable(input);
}
[HttpGet("scope/page")]
public async Task<dynamic> ScopePage([FromQuery] OnboardingContractScopePageInput input)
{
return await _service.ScopePage(input);
}
[HttpPost("scope/add")]
public async Task AddScope([FromBody] OnboardingContractScopeAddInput input)
{
await _service.AddScope(input);
}
[HttpPost("scope/edit")]
public async Task EditScope([FromBody] OnboardingContractScopeEditInput input)
{
await _service.EditScope(input);
}
[HttpPost("scope/delete")]
public async Task DeleteScope([FromBody] BaseIdListInput input)
{
await _service.DeleteScope(input);
}
}
@@ -0,0 +1,26 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职正式员工档案")]
[Route("biz/onboarding/employee")]
[RolePermission]
public class OnboardingEmployeeController
{
private readonly IOnboardingEmployeeService _service;
public OnboardingEmployeeController(IOnboardingEmployeeService service)
{
_service = service;
}
[HttpGet("page")]
public async Task<dynamic> Page([FromQuery] OnboardingEmployeePageInput input)
{
return await _service.Page(input);
}
[HttpGet("detail")]
public async Task<dynamic> Detail([FromQuery] BaseIdInput input)
{
return await _service.Detail(input);
}
}
@@ -0,0 +1,86 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职填写与材料配置")]
[Route("biz/onboarding/entry-config")]
[RolePermission]
public class OnboardingEntryConfigController
{
private readonly IOnboardingEntryConfigService _service;
public OnboardingEntryConfigController(IOnboardingEntryConfigService service)
{
_service = service;
}
[HttpGet("form-field/list")]
public async Task<dynamic> ListFormFields()
{
return await _service.ListFormFields();
}
[HttpPost("form-field/save")]
public async Task SaveFormField([FromBody] OnboardingFormFieldSaveInput input)
{
await _service.SaveFormField(input);
}
[HttpPost("form-field/delete")]
public async Task DeleteFormFields([FromBody] BaseIdListInput input)
{
await _service.DeleteFormFields(input);
}
[HttpGet("material-type/list")]
public async Task<dynamic> ListMaterialTypes()
{
return await _service.ListMaterialTypes();
}
[HttpPost("material-type/save")]
public async Task SaveMaterialType([FromBody] OnboardingMaterialTypeSaveInput input)
{
await _service.SaveMaterialType(input);
}
[HttpPost("material-type/delete")]
public async Task DeleteMaterialTypes([FromBody] BaseIdListInput input)
{
await _service.DeleteMaterialTypes(input);
}
[HttpGet("form-requirement/list")]
public async Task<dynamic> ListFormRequirements([FromQuery] long companyId)
{
return await _service.ListFormRequirements(companyId);
}
[HttpPost("form-requirement/save")]
public async Task SaveFormRequirement([FromBody] OnboardingFormRequirementSaveInput input)
{
await _service.SaveFormRequirement(input);
}
[HttpPost("form-requirement/delete")]
public async Task DeleteFormRequirements([FromBody] BaseIdListInput input)
{
await _service.DeleteFormRequirements(input);
}
[HttpGet("material-requirement/list")]
public async Task<dynamic> ListMaterialRequirements([FromQuery] long companyId)
{
return await _service.ListMaterialRequirements(companyId);
}
[HttpPost("material-requirement/save")]
public async Task SaveMaterialRequirement([FromBody] OnboardingMaterialRequirementSaveInput input)
{
await _service.SaveMaterialRequirement(input);
}
[HttpPost("material-requirement/delete")]
public async Task DeleteMaterialRequirements([FromBody] BaseIdListInput input)
{
await _service.DeleteMaterialRequirements(input);
}
}
@@ -0,0 +1,20 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职资料审核")]
[Route("biz/onboarding/material")]
[RolePermission]
public class OnboardingMaterialReviewController
{
private readonly IOnboardingMaterialReviewService _service;
public OnboardingMaterialReviewController(IOnboardingMaterialReviewService service)
{
_service = service;
}
[HttpPost("review")]
public async Task Review([FromBody] OnboardingMaterialReviewInput input)
{
await _service.Review(input);
}
}
@@ -6,12 +6,45 @@ namespace SimpleAdmin.Web.Core;
public class OnboardingPositionController public class OnboardingPositionController
{ {
private readonly IOnboardingPositionService _service; private readonly IOnboardingPositionService _service;
public OnboardingPositionController(IOnboardingPositionService service) => _service = service;
[HttpGet("page")] public async Task<dynamic> Page([FromQuery] OnboardingPositionPageInput input) => await _service.Page(input); public OnboardingPositionController(IOnboardingPositionService service)
[HttpGet("list-enabled")] public async Task<dynamic> ListEnabled([FromQuery] long companyId) => await _service.ListEnabled(companyId); {
[HttpGet("detail")] public async Task<dynamic> Detail([FromQuery] BaseIdInput input) => await _service.Detail(input); _service = service;
[HttpPost("add")] public async Task Add([FromBody] OnboardingPositionAddInput input) => await _service.Add(input); }
[HttpPost("edit")] public async Task Edit([FromBody] OnboardingPositionEditInput input) => await _service.Edit(input);
[HttpPost("delete")] public async Task Delete([FromBody] BaseIdListInput input) => await _service.Delete(input); [HttpGet("page")]
} public async Task<dynamic> Page([FromQuery] OnboardingPositionPageInput input)
{
return await _service.Page(input);
}
[HttpGet("list-enabled")]
public async Task<dynamic> ListEnabled([FromQuery] long companyId)
{
return await _service.ListEnabled(companyId);
}
[HttpGet("detail")]
public async Task<dynamic> Detail([FromQuery] BaseIdInput input)
{
return await _service.Detail(input);
}
[HttpPost("add")]
public async Task Add([FromBody] OnboardingPositionAddInput input)
{
await _service.Add(input);
}
[HttpPost("edit")]
public async Task Edit([FromBody] OnboardingPositionEditInput input)
{
await _service.Edit(input);
}
[HttpPost("delete")]
public async Task Delete([FromBody] BaseIdListInput input)
{
await _service.Delete(input);
}
}
@@ -0,0 +1,67 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "员工入职提交")]
[Route("open/onboarding")]
[AllowAnonymous]
public class OnboardingPublicSubmissionController
{
private readonly IOnboardingPublicSubmissionService _service;
private readonly IFileService _fileService;
public OnboardingPublicSubmissionController(IOnboardingPublicSubmissionService service, IFileService fileService)
{
_service = service;
_fileService = fileService;
}
[HttpPost("save-dynamic-values")]
public async Task SaveDynamicValues([FromBody] OnboardingSaveDynamicValuesInput input)
{
await _service.SaveDynamicValues(input);
}
[HttpPost("save-material")]
public async Task SaveMaterial([FromBody] OnboardingSaveMaterialInput input)
{
await _service.SaveMaterial(input);
}
[HttpPost("confirm-contract")]
public async Task ConfirmContract([FromBody] OnboardingConfirmContractInput input)
{
await _service.ConfirmContract(input);
}
[HttpPost("submit")]
public async Task Submit([FromBody] OnboardingSubmitInput input)
{
await _service.Submit(input);
}
[HttpPost("upload-material")]
[Consumes("multipart/form-data")]
[RequestSizeLimit(20 * 1024 * 1024)]
public async Task UploadMaterial([FromForm] string token, [FromForm] long materialTypeId, IFormFile file)
{
if (file == null || file.Length == 0)
{
throw new ArgumentException("请选择需要上传的文件");
}
await _service.ValidateMaterialUpload(new OnboardingMaterialUploadValidationInput
{
Token = token,
MaterialTypeId = materialTypeId,
FileName = file.FileName,
FileLength = file.Length
});
var fileId = await _fileService.UploadFile(SysDictConst.FILE_ENGINE_LOCAL, file);
await _service.SaveMaterial(new OnboardingSaveMaterialInput
{
Token = token,
MaterialTypeId = materialTypeId,
FileId = fileId
});
}
}
@@ -0,0 +1,35 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "员工入职填写")]
[Route("open/onboarding")]
[AllowAnonymous]
public class OnboardingPublicTaskController
{
private readonly IOnboardingPublicTaskService _service;
private readonly IFileService _fileService;
public OnboardingPublicTaskController(IOnboardingPublicTaskService service, IFileService fileService)
{
_service = service;
_fileService = fileService;
}
[HttpPost("detail")]
public async Task<dynamic> Detail([FromBody] OnboardingTaskPublicAccessInput input)
{
return await _service.Detail(input);
}
[HttpPost("save-employee-info")]
public async Task SaveEmployeeInfo([FromBody] OnboardingTaskPublicSaveInput input)
{
await _service.SaveEmployeeInfo(input);
}
[HttpGet("contract-source")]
public async Task<FileStreamResult> ContractSource([FromQuery] OnboardingPublicContractSourceInput input)
{
var fileId = await _service.GetContractSourceFileId(input);
return await _fileService.Download(new BaseIdInput { Id = fileId });
}
}
@@ -0,0 +1,26 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职任务合同参数")]
[Route("biz/onboarding/task-contract-variable")]
[RolePermission]
public class OnboardingTaskContractVariableController
{
private readonly IOnboardingTaskContractVariableService _service;
public OnboardingTaskContractVariableController(IOnboardingTaskContractVariableService service)
{
_service = service;
}
[HttpGet("list")]
public async Task<dynamic> List([FromQuery] OnboardingTaskContractVariableQueryInput input)
{
return await _service.List(input);
}
[HttpPost("save")]
public async Task Save([FromBody] OnboardingTaskContractVariableSaveInput input)
{
await _service.Save(input);
}
}
@@ -6,6 +6,15 @@ namespace SimpleAdmin.Web.Core;
public class OnboardingTaskController public class OnboardingTaskController
{ {
private readonly IOnboardingTaskService _service; private readonly IOnboardingTaskService _service;
public OnboardingTaskController(IOnboardingTaskService service) => _service = service;
[HttpPost("create")] public async Task<dynamic> Create([FromBody] OnboardingTaskCreateInput input) => await _service.Create(input); public OnboardingTaskController(IOnboardingTaskService service)
} {
_service = service;
}
[HttpPost("create")]
public async Task<dynamic> Create([FromBody] OnboardingTaskCreateInput input)
{
return await _service.Create(input);
}
}
@@ -0,0 +1,26 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职任务归档")]
[Route("biz/onboarding/task")]
[RolePermission]
public class OnboardingTaskLifecycleController
{
private readonly IOnboardingTaskLifecycleService _service;
public OnboardingTaskLifecycleController(IOnboardingTaskLifecycleService service)
{
_service = service;
}
[HttpPost("archive")]
public async Task Archive([FromBody] OnboardingAuditActionInput input)
{
await _service.Archive(input);
}
[HttpPost("revoke")]
public async Task Revoke([FromBody] OnboardingAuditActionInput input)
{
await _service.Revoke(input);
}
}
@@ -0,0 +1,26 @@
namespace SimpleAdmin.Web.Core;
[ApiDescriptionSettings("Application", Tag = "入职任务查询")]
[Route("biz/onboarding/task")]
[RolePermission]
public class OnboardingTaskQueryController
{
private readonly IOnboardingTaskQueryService _service;
public OnboardingTaskQueryController(IOnboardingTaskQueryService service)
{
_service = service;
}
[HttpGet("page")]
public async Task<dynamic> Page([FromQuery] OnboardingTaskPageInput input)
{
return await _service.Page(input);
}
[HttpGet("detail")]
public async Task<dynamic> Detail([FromQuery] BaseIdInput input)
{
return await _service.Detail(input);
}
}
@@ -26,6 +26,11 @@
模拟 Configure 模拟 Configure
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Web.Core.OnboardingContractDocumentController.Generate(SimpleAdmin.Application.OnboardingGenerateContractInput)">
<summary>
生成入职合同文件
</summary>
</member>
<member name="T:SimpleAdmin.Web.Core.BizOrgController"> <member name="T:SimpleAdmin.Web.Core.BizOrgController">
<summary> <summary>
机构管理控制器 机构管理控制器