人员上报

This commit is contained in:
18631081161 2026-01-17 00:17:51 +08:00
parent f639840803
commit de9592be03
15 changed files with 332 additions and 67 deletions

View File

@ -139,7 +139,8 @@ public class PersonnelController : BaseApiController
ApprovedByUnitName = personnel.ApprovedByUnit?.Name,
Status = personnel.Status,
SubmittedAt = personnel.SubmittedAt,
ApprovedAt = personnel.ApprovedAt
ApprovedAt = personnel.ApprovedAt,
PendingUpgradeByUnitId = personnel.PendingUpgradeByUnitId
};
}
@ -738,4 +739,52 @@ public class PersonnelController : BaseApiController
}
}
/// <summary>
/// 发起向上申报请求
/// </summary>
[HttpPost("{id}/request-upgrade")]
[Authorize(Policy = "RegimentLevel")] // 团级及以上权限
public async Task<IActionResult> RequestUpgrade(int id)
{
var unitId = GetCurrentUnitId();
if (unitId == null)
{
return Unauthorized();
}
try
{
var personnel = await _personnelService.RequestUpgradeAsync(id, unitId.Value);
return Ok(MapToResponse(personnel));
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
}
}
/// <summary>
/// 审批向上申报请求
/// </summary>
[HttpPost("{id}/approve-upgrade")]
[Authorize(Policy = "RegimentLevel")] // 团级及以上权限
public async Task<IActionResult> ApproveUpgrade(int id)
{
var unitId = GetCurrentUnitId();
if (unitId == null)
{
return Unauthorized();
}
try
{
var personnel = await _personnelService.ApproveUpgradeAsync(id, unitId.Value);
return Ok(MapToResponse(personnel));
}
catch (ArgumentException ex)
{
return BadRequest(new { message = ex.Message });
}
}
}

View File

@ -159,6 +159,7 @@ public class PersonnelResponse
public PersonnelStatus Status { get; set; }
public DateTime SubmittedAt { get; set; }
public DateTime? ApprovedAt { get; set; }
public int? PendingUpgradeByUnitId { get; set; } // 待向上申报的申报单位ID
}
/// <summary>

View File

@ -40,4 +40,8 @@ public class Personnel
public PersonnelStatus Status { get; set; } = PersonnelStatus.Pending;
public DateTime SubmittedAt { get; set; } = DateTime.UtcNow;
public DateTime? ApprovedAt { get; set; }
// 向上申报相关字段
public int? PendingUpgradeByUnitId { get; set; } // 发起向上申报的单位ID
public OrganizationalUnit? PendingUpgradeByUnit { get; set; }
}

View File

@ -235,6 +235,9 @@ using (var scope = app.Services.CreateScope())
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('Personnel') AND name = 'Specialty')
ALTER TABLE Personnel ADD Specialty nvarchar(200) NULL;
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('Personnel') AND name = 'PendingUpgradeByUnitId')
ALTER TABLE Personnel ADD PendingUpgradeByUnitId INT NULL;
");
Console.WriteLine("Personnel 表列检查完成");
}

View File

@ -85,6 +85,7 @@ public class PersonnelService : IPersonnelService
{
var personnel = await _context.Personnel
.Include(p => p.SubmittedByUnit)
.Include(p => p.ApprovedByUnit)
.FirstOrDefaultAsync(p => p.Id == personnelId);
if (personnel == null)
throw new ArgumentException("人员记录不存在");
@ -93,7 +94,9 @@ public class PersonnelService : IPersonnelService
if (approvedByUnit == null)
throw new ArgumentException("审批单位不存在");
// 人员等级变更为审批单位的等级
var previousStatus = personnel.Status;
var previousLevel = personnel.ApprovedLevel;
PersonnelLevel actualLevel;
if (level.HasValue)
{
@ -101,19 +104,40 @@ public class PersonnelService : IPersonnelService
}
else
{
// 根据审批单位的层级确定人员等级
actualLevel = (PersonnelLevel)(int)approvedByUnit.Level;
// 区分首次审批和向上申报两种场景
if (previousStatus == PersonnelStatus.Pending)
{
// 首次审批:人员等级根据提交单位的层级确定
// 例如:营级单位提交的人才,团级审批后应为"营级人才"
actualLevel = (PersonnelLevel)(int)personnel.SubmittedByUnit!.Level;
// 验证审批单位层级必须高于提交单位层级(数值越小层级越高)
var unitLevelValue = (int)approvedByUnit.Level;
var submittedUnitLevelValue = (int)personnel.SubmittedByUnit!.Level;
if (unitLevelValue >= submittedUnitLevelValue)
throw new ArgumentException("审批单位层级必须高于提交单位层级");
}
else if (previousStatus == PersonnelStatus.Approved)
{
// 向上申报:人员等级升级为上报单位(即之前的审批单位)的层级
// 例如:营级人才由团级向上申报,师级审批后应为"团级人才"
if (personnel.ApprovedByUnit == null)
throw new ArgumentException("无法确定上报单位");
actualLevel = (PersonnelLevel)(int)personnel.ApprovedByUnit.Level;
// 验证审批单位层级必须高于当前审批单位层级(数值越小层级越高)
var unitLevelValue = (int)approvedByUnit.Level;
var currentApprovedUnitLevelValue = (int)personnel.ApprovedByUnit.Level;
if (unitLevelValue >= currentApprovedUnitLevelValue)
throw new ArgumentException("审批单位层级必须高于当前审批单位层级");
}
else
{
throw new ArgumentException("该人员状态不允许审批");
}
}
// 验证审批单位层级必须高于或等于人员等级(数值越小层级越高)
var unitLevelValue = (int)approvedByUnit.Level;
var personnelLevelValue = (int)actualLevel;
if (unitLevelValue > personnelLevelValue)
throw new ArgumentException("审批单位层级不足以审批该等级人才");
var previousStatus = personnel.Status;
var previousLevel = personnel.ApprovedLevel;
personnel.Status = PersonnelStatus.Approved;
personnel.ApprovedByUnitId = approvedByUnitId;
personnel.ApprovedLevel = actualLevel;
@ -569,15 +593,20 @@ public class PersonnelService : IPersonnelService
if (user.OrganizationalUnitId == personnel.SubmittedByUnitId)
return false;
// 如果是已审批的人员,检查当前用户单位是否比已审批单位层级更高
// 如果是已审批的人员(向上申报场景)
if (personnel.Status == PersonnelStatus.Approved && personnel.ApprovedByUnitId.HasValue)
{
// 用户单位层级必须高于已审批单位层级(数值越小层级越高)
if ((int)user.OrganizationalUnit!.Level >= (int)personnel.ApprovedByUnit!.Level)
return false;
// 向上申报场景:检查用户单位是否是当前审批单位的上级
var isParentOfApprovedUnit = await _organizationService.IsParentUnitAsync(
user.OrganizationalUnitId, personnel.ApprovedByUnitId.Value);
return isParentOfApprovedUnit;
}
// 检查用户的组织单位是否是提交单位的上级
// 首次审批场景:检查用户的组织单位是否是提交单位的上级
var isParent = await _organizationService.IsParentUnitAsync(user.OrganizationalUnitId, personnel.SubmittedByUnitId);
return isParent;
@ -619,4 +648,96 @@ public class PersonnelService : IPersonnelService
return user?.Id ?? 0; // 如果找不到用户返回0作为系统操作
}
/// <summary>
/// 发起向上申报请求
/// </summary>
public async Task<Personnel> RequestUpgradeAsync(int personnelId, int requestByUnitId)
{
var personnel = await _context.Personnel
.Include(p => p.SubmittedByUnit)
.Include(p => p.ApprovedByUnit)
.FirstOrDefaultAsync(p => p.Id == personnelId);
if (personnel == null)
throw new ArgumentException("人员记录不存在");
if (personnel.Status != PersonnelStatus.Approved)
throw new ArgumentException("只有已审批的人员才能向上申报");
if (personnel.PendingUpgradeByUnitId.HasValue)
throw new ArgumentException("该人员已有待处理的向上申报请求");
var requestByUnit = await _context.OrganizationalUnits.FindAsync(requestByUnitId);
if (requestByUnit == null)
throw new ArgumentException("申报单位不存在");
// 验证申报单位必须是当前审批单位
if (personnel.ApprovedByUnitId != requestByUnitId)
throw new ArgumentException("只有当前审批单位才能发起向上申报");
// 师级人才不能再向上申报
if (personnel.ApprovedLevel == PersonnelLevel.Division)
throw new ArgumentException("师级人才不能再向上申报");
// 标记为待向上申报
personnel.PendingUpgradeByUnitId = requestByUnitId;
await _context.SaveChangesAsync();
_logger.LogInformation("人员 {PersonnelId} 已由单位 {UnitId} 发起向上申报请求",
personnelId, requestByUnitId);
return personnel;
}
/// <summary>
/// 审批向上申报请求
/// </summary>
public async Task<Personnel> ApproveUpgradeAsync(int personnelId, int approvedByUnitId)
{
var personnel = await _context.Personnel
.Include(p => p.SubmittedByUnit)
.Include(p => p.ApprovedByUnit)
.Include(p => p.PendingUpgradeByUnit)
.FirstOrDefaultAsync(p => p.Id == personnelId);
if (personnel == null)
throw new ArgumentException("人员记录不存在");
if (!personnel.PendingUpgradeByUnitId.HasValue)
throw new ArgumentException("该人员没有待处理的向上申报请求");
var approvedByUnit = await _context.OrganizationalUnits.FindAsync(approvedByUnitId);
if (approvedByUnit == null)
throw new ArgumentException("审批单位不存在");
// 验证审批单位必须是申报单位的上级
var isParent = await _organizationService.IsParentUnitAsync(approvedByUnitId, personnel.PendingUpgradeByUnitId.Value);
if (!isParent)
throw new ArgumentException("审批单位必须是申报单位的上级");
var previousLevel = personnel.ApprovedLevel;
// 人才等级升级为申报单位的等级
var newLevel = (PersonnelLevel)(int)personnel.PendingUpgradeByUnit!.Level;
personnel.ApprovedLevel = newLevel;
personnel.ApprovedByUnitId = approvedByUnitId;
personnel.ApprovedAt = DateTime.UtcNow;
personnel.PendingUpgradeByUnitId = null; // 清除待申报标记
await _context.SaveChangesAsync();
// 记录审批历史
var userId = await GetUserIdByUnitAsync(approvedByUnitId);
await RecordApprovalHistoryAsync(personnelId, PersonnelApprovalAction.LevelUpgraded,
PersonnelStatus.Approved, PersonnelStatus.Approved, previousLevel, newLevel,
userId, approvedByUnitId, $"向上申报通过,等级从{GetLevelName(previousLevel)}升级为{GetLevelName(newLevel)}");
_logger.LogInformation("人员 {PersonnelId} 向上申报已被单位 {UnitId} 审批通过,等级升级为 {Level}",
personnelId, approvedByUnitId, newLevel);
return personnel;
}
}

View File

@ -75,6 +75,16 @@ public interface IPersonnelService
/// 验证审批权限
/// </summary>
Task<bool> CanApprovePersonnelAsync(int userId, int personnelId);
/// <summary>
/// 发起向上申报请求
/// </summary>
Task<Personnel> RequestUpgradeAsync(int personnelId, int requestByUnitId);
/// <summary>
/// 审批向上申报请求
/// </summary>
Task<Personnel> ApproveUpgradeAsync(int personnelId, int approvedByUnitId);
}
/// <summary>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -55,5 +55,17 @@ export const personnelApi = {
async getPendingApprovals(): Promise<Personnel[]> {
const response = await apiClient.get<Personnel[]>('/personnel/pending')
return response.data
},
// 发起向上申报请求
async requestUpgrade(personnelId: number): Promise<Personnel> {
const response = await apiClient.post<Personnel>(`/personnel/${personnelId}/request-upgrade`)
return response.data
},
// 审批向上申报请求
async approveUpgrade(personnelId: number): Promise<Personnel> {
const response = await apiClient.post<Personnel>(`/personnel/${personnelId}/approve-upgrade`)
return response.data
}
}

View File

@ -31,22 +31,6 @@
<el-menu-item v-if="authStore.canCreateAllocations" index="/allocations/create">创建配额</el-menu-item>
</el-sub-menu>
<!-- 上报管理只对团级账号可见师团创建配额营部及以下用配额列表上报 -->
<el-sub-menu v-if="authStore.organizationalLevelNum === 2" index="reports">
<template #title>
<el-icon><DataAnalysis /></el-icon>
<span>上报管理</span>
</template>
<el-menu-item index="/reports">上报列表</el-menu-item>
<el-menu-item index="/reports/summary">数据汇总</el-menu-item>
</el-sub-menu>
<!-- 数据汇总师团级单独显示查看下级汇总数据 -->
<el-menu-item v-if="authStore.organizationalLevelNum === 1" index="/reports/summary">
<el-icon><DataAnalysis /></el-icon>
<span>数据汇总</span>
</el-menu-item>
<el-sub-menu index="personnel">
<template #title>
<el-icon><User /></el-icon>
@ -106,7 +90,6 @@ import {
HomeFilled,
OfficeBuilding,
Box,
DataAnalysis,
User,
Checked,
Document,

View File

@ -177,6 +177,7 @@ export interface Personnel {
status: PersonnelStatus
submittedAt: string
approvedAt: string | null
pendingUpgradeByUnitId: number | null // 待向上申报的申报单位ID
}
export interface CreatePersonnelRequest {

View File

@ -75,7 +75,7 @@
label-width="120px"
@submit.prevent="handleSubmit"
>
<!-- 连部显示上报目标选择 -->
<!-- 连部显示上报目标选择暂时注释
<el-form-item v-if="isCompanyLevel" label="上报到" prop="reportToLevel">
<el-radio-group v-model="form.reportToLevel">
<el-radio value="Battalion">
@ -88,6 +88,7 @@
</el-radio>
</el-radio-group>
</el-form-item>
-->
<el-form-item :label="isBattalionOrBelow ? '本次消耗数量' : '本次上报数量'" prop="actualCompletion">
<el-input-number
@ -272,12 +273,12 @@ const formRules = computed<FormRules>(() => {
]
}
//
if (isCompanyLevel.value) {
baseRules.reportToLevel = [
{ required: true, message: '请选择上报目标', trigger: 'change' }
]
}
//
// if (isCompanyLevel.value) {
// baseRules.reportToLevel = [
// { required: true, message: '', trigger: 'change' }
// ]
// }
// /
if (!isBattalionOrBelow.value) {
@ -342,10 +343,12 @@ async function handleSubmit() {
//
let confirmMessage = ''
if (isCompanyLevel.value) {
const targetName = form.reportToLevel === 'Battalion' ? '营部' : '团部'
confirmMessage = `本次消耗数量:${form.actualCompletion} ${allocation.value?.unit}\n上报目标${targetName}\n\n确认提交吗`
} else if (isBattalionOrBelow.value) {
//
// if (isCompanyLevel.value) {
// const targetName = form.reportToLevel === 'Battalion' ? '' : ''
// confirmMessage = `${form.actualCompletion} ${allocation.value?.unit}\n${targetName}\n\n`
// } else
if (isBattalionOrBelow.value) {
confirmMessage = `本次消耗数量:${form.actualCompletion} ${allocation.value?.unit}\n\n确认提交吗`
} else {
confirmMessage = `本次上报数量:${form.actualCompletion} ${allocation.value?.unit}\n上报后总数${newTotal} ${allocation.value?.unit}\n\n确认提交吗`
@ -365,12 +368,12 @@ async function handleSubmit() {
if (!distribution.value) return
//
//
let remarks = form.remarks || ''
if (isCompanyLevel.value && form.reportToLevel) {
const targetName = form.reportToLevel === 'Battalion' ? '营部' : '团部'
remarks = remarks ? `[上报到${targetName}] ${remarks}` : `[上报到${targetName}]`
}
// if (isCompanyLevel.value && form.reportToLevel) {
// const targetName = form.reportToLevel === 'Battalion' ? '' : ''
// remarks = remarks ? `[${targetName}] ${remarks}` : `[${targetName}]`
// }
//
await allocationsApi.updateDistribution(distribution.value.id, {

View File

@ -63,9 +63,12 @@
<el-table-column prop="position" label="职位" width="120" show-overflow-tooltip />
<el-table-column prop="rank" label="军衔" width="100" show-overflow-tooltip />
<el-table-column prop="submittedByUnitName" label="所属单位" min-width="120" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="100" align="center">
<el-table-column prop="status" label="状态" width="120" align="center">
<template #default="{ row }">
<el-tag :type="getStatusTagType(row.status)" effect="dark" size="small">
<el-tag v-if="row.pendingUpgradeByUnitId" type="primary" effect="dark" size="small">
待上级审批
</el-tag>
<el-tag v-else :type="getStatusTagType(row.status)" effect="dark" size="small">
{{ getStatusName(row.status) }}
</el-tag>
</template>
@ -91,16 +94,21 @@
<el-icon><View /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip v-if="row.status === 'Pending' && authStore.canApprove" content="审批" placement="top">
<el-tooltip v-if="row.status === 'Pending' && authStore.canApprove && canApprovePersonnel(row)" content="审批" placement="top">
<el-button type="success" link size="small" @click="handleApprove(row)">
<el-icon><Check /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip v-if="row.status === 'Approved' && authStore.canApprove && canUpgrade(row)" content="向上申报" placement="top">
<el-button type="warning" link size="small" @click="handleUpgrade(row)">
<el-button type="warning" link size="small" @click="handleRequestUpgrade(row)">
<el-icon><Top /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip v-if="row.status === 'Approved' && row.pendingUpgradeByUnitId && authStore.canApprove && canApproveUpgrade(row)" content="审批向上申报" placement="top">
<el-button type="success" link size="small" @click="handleApproveUpgrade(row)">
<el-icon><Check /></el-icon>
</el-button>
</el-tooltip>
<el-tooltip v-if="row.status === 'Pending'" content="编辑" placement="top">
<el-button type="warning" link size="small" @click="handleEdit(row)">
<el-icon><Edit /></el-icon>
@ -270,21 +278,45 @@ function handleEdit(person: Personnel) {
}
function canUpgrade(person: Personnel): boolean {
//
if (!authStore.user || !person.approvedLevel) return false
//
//
// 1.
// 2.
// 3.
// 4.
if (!authStore.user || !person.approvedLevel || !person.approvedByUnitId) return false
if (person.approvedLevel === PersonnelLevel.Division) return false
//
const userLevel = authStore.user.organizationalLevel
const personnelLevel = person.approvedLevel
// Division=1, Regiment=2, Battalion=3, Company=4
const levelOrder: Record<string, number> = {
if (person.pendingUpgradeByUnitId) return false //
//
return person.approvedByUnitId === authStore.user.organizationalUnitId
}
//
function canApproveUpgrade(person: Personnel): boolean {
if (!authStore.user || !person.pendingUpgradeByUnitId) return false
//
const userLevelNum = authStore.organizationalLevelNum
// = + 11
const personnelLevelNum = {
'Division': 1,
'Regiment': 2,
'Battalion': 3,
'Company': 4
}
return levelOrder[userLevel] < levelOrder[personnelLevel]
}[person.approvedLevel || 'Company'] || 4
const upgradeByUnitLevel = personnelLevelNum - 1 //
return userLevelNum < upgradeByUnitLevel
}
//
// 1. 2.
function canApprovePersonnel(person: Personnel): boolean {
if (!authStore.user) return false
//
if (person.submittedByUnitId === authStore.user.organizationalUnitId) return false
//
//
return true
}
function handleApprove(person: Personnel) {
@ -294,11 +326,57 @@ function handleApprove(person: Personnel) {
showApprovalDialog.value = true
}
function handleUpgrade(person: Personnel) {
// 使
selectedPerson.value = person
approvalForm.comments = ''
showApprovalDialog.value = true
//
async function handleRequestUpgrade(person: Personnel) {
try {
const levelName = getLevelName(person.approvedLevel!)
await ElMessageBox.confirm(
`确定要将 "${person.name}"${levelName})向上申报吗?\n申报后需要上级单位审批通过才能升级等级。`,
'确认向上申报',
{
type: 'warning',
confirmButtonText: '确认申报',
cancelButtonText: '取消'
}
)
await personnelApi.requestUpgrade(person.id)
ElMessage.success('向上申报请求已提交,等待上级审批')
await loadPersonnel()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.message || '向上申报失败')
}
}
}
//
async function handleApproveUpgrade(person: Personnel) {
try {
const currentLevel = getLevelName(person.approvedLevel!)
//
const levelOrder = ['Company', 'Battalion', 'Regiment', 'Division']
const currentIndex = levelOrder.indexOf(person.approvedLevel || 'Company')
const newLevel = currentIndex < levelOrder.length - 1 ? getLevelName(levelOrder[currentIndex + 1] as PersonnelLevel) : currentLevel
await ElMessageBox.confirm(
`确定要批准 "${person.name}" 的向上申报请求吗?\n批准后该人才将从${currentLevel}升级为${newLevel}`,
'确认审批向上申报',
{
type: 'warning',
confirmButtonText: '批准',
cancelButtonText: '取消'
}
)
await personnelApi.approveUpgrade(person.id)
ElMessage.success('向上申报已批准,人才等级已升级')
await loadPersonnel()
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.response?.data?.message || '审批失败')
}
}
}
async function submitApproval(approved: boolean) {