228 lines
10 KiB
C#
228 lines
10 KiB
C#
namespace SimpleAdmin.Application;
|
|
|
|
/// <summary>
|
|
/// 构建任务合同变量快照,合同生成阶段只读取本快照。
|
|
/// </summary>
|
|
public class OnboardingContractSnapshotService : IOnboardingContractSnapshotService
|
|
{
|
|
private readonly SqlSugarScope _db;
|
|
|
|
public OnboardingContractSnapshotService()
|
|
{
|
|
_db = DbContext.DB;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据任务、公司、员工固定信息和动态补充字段构建合同变量快照。
|
|
/// </summary>
|
|
public async Task Build(
|
|
long taskId,
|
|
ObTaskEmployeeInfo employeeInfo = null,
|
|
bool invalidateGeneratedPreview = false)
|
|
{
|
|
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(item => item.TaskId == taskId);
|
|
// 员工补充合同变量统一从 CustomVariablesJson 读取,不再访问已移除的旧动态字段表。
|
|
var dynamicValueMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
var contracts = await _db.Queryable<ObTaskContract>()
|
|
.Where(item => item.TaskId == taskId)
|
|
.ToListAsync();
|
|
|
|
foreach (var contract in contracts)
|
|
{
|
|
var definitions = await _db.Queryable<ObTemplateVariable>()
|
|
.Where(item => item.TemplateVersionId == contract.TemplateVersionId)
|
|
.OrderBy(item => item.SortCode)
|
|
.ToListAsync();
|
|
var oldRows = await _db.Queryable<ObTaskContractVariable>()
|
|
.Where(item => item.TaskContractId == contract.Id)
|
|
.ToListAsync();
|
|
var rows = definitions.Select(definition =>
|
|
{
|
|
var old = oldRows.FirstOrDefault(item => item.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,
|
|
SortCode = definition.SortCode ?? 100,
|
|
VariableValue = preserveManual
|
|
? old.VariableValue
|
|
: ResolveValue(definition, employeeInfo, dynamicValueMap, company, task),
|
|
SourceType = preserveManual
|
|
? ContractVariableSourceType.HrInput
|
|
: GetSourceType(definition.VariableType),
|
|
IsManualOverride = preserveManual
|
|
};
|
|
}).ToList();
|
|
|
|
// 仅当参与合同渲染的变量编码或变量值发生变化时,才作废已生成文件。
|
|
// 重复保存完全相同的员工资料不应导致员工刚查看的合同突然失效。
|
|
var snapshotChanged = oldRows.Count != rows.Count
|
|
|| rows.Any(row =>
|
|
{
|
|
var old = oldRows.FirstOrDefault(item =>
|
|
item.VariableCode == row.VariableCode);
|
|
return old == null
|
|
|| !string.Equals(
|
|
old.VariableValue,
|
|
row.VariableValue,
|
|
StringComparison.Ordinal);
|
|
});
|
|
|
|
await _db.Deleteable<ObTaskContractVariable>()
|
|
.Where(item => item.TaskContractId == contract.Id)
|
|
.ExecuteCommandAsync();
|
|
if (rows.Count > 0)
|
|
await _db.Insertable(rows).ExecuteCommandAsync();
|
|
|
|
if (invalidateGeneratedPreview
|
|
&& snapshotChanged
|
|
&& task.TaskStatus is OnboardingTaskStatus.PendingFill or OnboardingTaskStatus.Rejected)
|
|
{
|
|
contract.GeneratedDocxFileId = null;
|
|
contract.GeneratedPdfFileId = null;
|
|
contract.GeneratedAt = null;
|
|
contract.ReadAt = null;
|
|
contract.ConfirmedAt = null;
|
|
contract.ContractStatus = TaskContractStatus.PendingGenerate;
|
|
await _db.Updateable(contract).ExecuteCommandAsync();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GetSourceType(string variableType)
|
|
{
|
|
return variableType switch
|
|
{
|
|
ContractVariableType.Employee => ContractVariableSourceType.Employee,
|
|
ContractVariableType.Company => ContractVariableSourceType.Company,
|
|
ContractVariableType.System => ContractVariableSourceType.System,
|
|
_ => ContractVariableSourceType.HrInput
|
|
};
|
|
}
|
|
|
|
private static string ResolveValue(
|
|
ObTemplateVariable definition,
|
|
ObTaskEmployeeInfo employeeInfo,
|
|
IReadOnlyDictionary<string, string> dynamicValues,
|
|
ObCompany company,
|
|
ObOnboardingTask task)
|
|
{
|
|
var sourceField = GetSourceField(definition);
|
|
if (definition.VariableType == ContractVariableType.Employee)
|
|
{
|
|
var fixedValue = GetPropertyValue(employeeInfo, ResolveEmployeeField(sourceField), definition.FormatRule);
|
|
if (!string.IsNullOrWhiteSpace(fixedValue))
|
|
return fixedValue;
|
|
|
|
// 检查员工自定义补充变量 JSON
|
|
if (!string.IsNullOrWhiteSpace(employeeInfo?.CustomVariablesJson))
|
|
{
|
|
try
|
|
{
|
|
using var doc = global::System.Text.Json.JsonDocument.Parse(employeeInfo.CustomVariablesJson);
|
|
if (doc.RootElement.TryGetProperty(sourceField, out var prop)
|
|
|| doc.RootElement.TryGetProperty(definition.VariableCode, out prop))
|
|
{
|
|
var str = prop.GetString();
|
|
if (!string.IsNullOrWhiteSpace(str)) return str;
|
|
}
|
|
}
|
|
catch (global::System.Text.Json.JsonException)
|
|
{
|
|
throw Oops.Bah("员工补充变量数据格式异常,请重新保存入职登记表后再生成合同");
|
|
}
|
|
}
|
|
|
|
return dynamicValues.TryGetValue(sourceField, out var dynamicValue)
|
|
? dynamicValue
|
|
: definition.DefaultValue;
|
|
}
|
|
|
|
if (definition.VariableType == ContractVariableType.Company)
|
|
return GetPropertyValue(company, ResolveCompanyField(sourceField), definition.FormatRule) ?? definition.DefaultValue;
|
|
|
|
if (definition.VariableType == ContractVariableType.System)
|
|
{
|
|
return sourceField.ToUpperInvariant() switch
|
|
{
|
|
"TASKNO" or "TASK_NO" or "CONTRACTNO" or "CONTRACT_NO" => task.TaskNo,
|
|
"CURRENTDATE" or "CURRENT_DATE" or "SIGNDATE" or "SIGN_DATE" => DateTime.Now.ToString("yyyy年MM月dd日"),
|
|
_ => definition.DefaultValue
|
|
};
|
|
}
|
|
|
|
return definition.DefaultValue;
|
|
}
|
|
|
|
private static string GetSourceField(ObTemplateVariable definition)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(definition.EmployeeField))
|
|
return definition.EmployeeField;
|
|
var separatorIndex = definition.VariableCode?.IndexOf('.') ?? -1;
|
|
return separatorIndex >= 0
|
|
? definition.VariableCode[(separatorIndex + 1)..]
|
|
: definition.VariableCode;
|
|
}
|
|
|
|
private static string GetPropertyValue(object source, string propertyName, string formatRule = null)
|
|
{
|
|
if (source == null || string.IsNullOrWhiteSpace(propertyName))
|
|
return null;
|
|
|
|
var value = source.GetType()
|
|
.GetProperty(
|
|
propertyName,
|
|
global::System.Reflection.BindingFlags.Public
|
|
| global::System.Reflection.BindingFlags.Instance
|
|
| global::System.Reflection.BindingFlags.IgnoreCase)
|
|
?.GetValue(source);
|
|
if (value == null)
|
|
return null;
|
|
|
|
if (value is DateTime dateTime)
|
|
return dateTime.ToString(string.IsNullOrWhiteSpace(formatRule) ? "yyyy年MM月dd日" : formatRule);
|
|
if (value is DateTimeOffset dateTimeOffset)
|
|
return dateTimeOffset.ToString(string.IsNullOrWhiteSpace(formatRule) ? "yyyy年MM月dd日" : formatRule);
|
|
if (!string.IsNullOrWhiteSpace(formatRule) && value is IFormattable formattable)
|
|
return formattable.ToString(formatRule, null);
|
|
return value.ToString();
|
|
}
|
|
|
|
private static string ResolveEmployeeField(string sourceField)
|
|
{
|
|
return sourceField.ToUpperInvariant() switch
|
|
{
|
|
"FULLNAME" or "FULL_NAME" or "NAME" => nameof(ObTaskEmployeeInfo.EmployeeName),
|
|
"MOBILE" or "MOBILEPHONE" or "MOBILE_PHONE" => nameof(ObTaskEmployeeInfo.Phone),
|
|
"IDNUMBER" or "ID_NUMBER" or "IDENTITYNUMBER" => nameof(ObTaskEmployeeInfo.IdCardNumber),
|
|
_ => sourceField
|
|
};
|
|
}
|
|
private static string ResolveCompanyField(string sourceField)
|
|
{
|
|
return sourceField.ToUpperInvariant() switch
|
|
{
|
|
"COMPANYCODE" or "COMPANY_CODE" => nameof(ObCompany.CompanyCode),
|
|
"COMPANYNAME" or "COMPANY_NAME" => nameof(ObCompany.CompanyName),
|
|
"LEGALNAME" or "LEGAL_NAME" or "COMPANYLEGALNAME" => nameof(ObCompany.LegalName),
|
|
"UNIFIEDSOCIALCREDITCODE" or "UNIFIED_SOCIAL_CREDIT_CODE" => nameof(ObCompany.UnifiedSocialCreditCode),
|
|
"LEGALREPRESENTATIVE" or "LEGAL_REPRESENTATIVE" => nameof(ObCompany.LegalRepresentative),
|
|
"ADDRESS" or "COMPANYADDRESS" or "COMPANY_ADDRESS"
|
|
or "REGISTEREDADDRESS" or "REGISTERED_ADDRESS" => nameof(ObCompany.RegisteredAddress),
|
|
"PHONE" or "COMPANYPHONE" or "COMPANY_PHONE"
|
|
or "CONTACTPHONE" or "CONTACT_PHONE" => nameof(ObCompany.ContactPhone),
|
|
_ => sourceField
|
|
};
|
|
}
|
|
}
|