- 绩效台账页(order17):月/版本筛选 + 行展开计算快照 + 重算(原因 对话框,管理员/财务);核算入口在回款管理页(每笔回款行内触发) - 月结页(order18):生成快照(月份输入)+ 状态筛选 + 提交审批 + 详情抽屉(按人明细三口径/试用期说明/审批单号关联) - 均复用 usePagedList/AppPagination/--ys-* 惯例;金额 str 直出 - type-check 零错误;build 通过 Co-Authored-By: Claude Code <noreply@anthropic.com>
246 lines
7.4 KiB
Vue
246 lines
7.4 KiB
Vue
<script setup lang="ts">
|
||
import { reactive, ref } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
import {
|
||
generateSettlement,
|
||
getSettlement,
|
||
listSettlements,
|
||
settlementStatusLabel,
|
||
settlementStatusTagType,
|
||
submitSettlement,
|
||
type Settlement,
|
||
type SettlementDetail,
|
||
} from '@/api/settlements'
|
||
import AppPagination from '@/components/table/AppPagination.vue'
|
||
import { usePagedList } from '@/composables/usePagedList'
|
||
import { handledError } from '@/utils/error'
|
||
import { formatDateTime } from '@/utils/format'
|
||
|
||
/**
|
||
* 月度结算(M3 ④):按确认回款生成快照 → 提交审批 → 通过即锁定。
|
||
*
|
||
* 锁定后源数据改动被拦截(回款/发货写路径 400),调整走调整单。
|
||
* 详情抽屉展示按人明细(三口径净工资与试用期说明)。
|
||
*/
|
||
const filters = reactive({
|
||
status: undefined as number | undefined,
|
||
})
|
||
|
||
const { records, total, pageNum, pageSize, loading, load, search } = usePagedList<
|
||
Settlement,
|
||
{ status?: number }
|
||
>(listSettlements, () => ({ status: filters.status }))
|
||
|
||
function resetFilters(): void {
|
||
filters.status = undefined
|
||
search()
|
||
}
|
||
|
||
const generateMonth = ref('')
|
||
const generating = ref(false)
|
||
|
||
async function handleGenerate(): Promise<void> {
|
||
if (!/^\d{4}-\d{2}$/.test(generateMonth.value)) {
|
||
ElMessage.warning('请输入结算月份(YYYY-MM)')
|
||
return
|
||
}
|
||
generating.value = true
|
||
try {
|
||
await generateSettlement(generateMonth.value)
|
||
ElMessage.success('月结已生成(草稿)')
|
||
await search()
|
||
} catch (error) {
|
||
handledError(error)
|
||
} finally {
|
||
generating.value = false
|
||
}
|
||
}
|
||
|
||
const detailVisible = ref(false)
|
||
const detailLoading = ref(false)
|
||
const detail = ref<SettlementDetail | null>(null)
|
||
|
||
async function openDetail(row: Settlement): Promise<void> {
|
||
detailVisible.value = true
|
||
detailLoading.value = true
|
||
try {
|
||
detail.value = await getSettlement(row.id)
|
||
} catch (error) {
|
||
handledError(error)
|
||
detailVisible.value = false
|
||
} finally {
|
||
detailLoading.value = false
|
||
}
|
||
}
|
||
|
||
const submitting = ref(false)
|
||
|
||
async function handleSubmit(row: Settlement): Promise<void> {
|
||
try {
|
||
const out = await submitSettlement(row.id)
|
||
ElMessage.success(
|
||
out.status === 3 ? '审批通过,月结已锁定' : '已提交审批(待财务复核)',
|
||
)
|
||
detail.value = await getSettlement(row.id)
|
||
await load()
|
||
} catch (error) {
|
||
handledError(error)
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="page">
|
||
<div class="toolbar">
|
||
<div class="gen-row">
|
||
<el-input
|
||
v-model="generateMonth"
|
||
placeholder="结算月份 YYYY-MM"
|
||
style="width: 160px"
|
||
maxlength="7"
|
||
/>
|
||
<el-button type="primary" :loading="generating" @click="handleGenerate">
|
||
生成月结快照
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-form inline class="filters" @submit.prevent>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="filters.status" placeholder="全部" clearable style="width: 150px">
|
||
<el-option label="草稿" :value="1" />
|
||
<el-option label="待审批" :value="2" />
|
||
<el-option label="已审定(锁定)" :value="3" />
|
||
<el-option label="已驳回" :value="4" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="search">查询</el-button>
|
||
<el-button @click="resetFilters">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<el-table v-loading="loading" :data="records" stripe>
|
||
<el-table-column prop="settlement_month" label="结算月份" width="100" />
|
||
<el-table-column label="状态" width="130">
|
||
<template #default="{ row }">
|
||
<el-tag :type="settlementStatusTagType(row.status)" size="small" disable-transitions>
|
||
{{ settlementStatusLabel(row.status) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="total_performance" label="绩效合计" min-width="110" />
|
||
<el-table-column prop="total_payable" label="应付合计" min-width="110" />
|
||
<el-table-column prop="person_count" label="人数" width="70" />
|
||
<el-table-column prop="approval_order_no" label="审批单号" min-width="150" />
|
||
<el-table-column label="提交时间" min-width="150">
|
||
<template #default="{ row }">{{ formatDateTime(row.create_time) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="130" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button link type="primary" @click="openDetail(row)">详情</el-button>
|
||
<el-button
|
||
v-if="row.status === 1 || row.status === 4"
|
||
link
|
||
type="success"
|
||
:loading="submitting"
|
||
@click="handleSubmit(row)"
|
||
>
|
||
提交审批
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<AppPagination
|
||
v-model:page-num="pageNum"
|
||
v-model:page-size="pageSize"
|
||
:total="total"
|
||
@change="load"
|
||
/>
|
||
|
||
<el-drawer v-model="detailVisible" title="月结详情" size="560px">
|
||
<div v-loading="detailLoading">
|
||
<template v-if="detail">
|
||
<div class="detail-head">
|
||
<span class="detail-month">{{ detail.settlement_month }}</span>
|
||
<el-tag :type="settlementStatusTagType(detail.status)" size="small" disable-transitions>
|
||
{{ settlementStatusLabel(detail.status) }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="detail-meta">
|
||
<span>审批单号:{{ detail.approval_order_no || '—' }}</span>
|
||
<span>提交人:{{ detail.submitter_name }}</span>
|
||
<span v-if="detail.locked_by">锁定人:{{ detail.locked_by }}</span>
|
||
</div>
|
||
<el-table :data="detail.items" size="small" stripe>
|
||
<el-table-column prop="personnel_name" label="人员" min-width="90" />
|
||
<el-table-column label="类型" width="90">
|
||
<template #default="{ row }">
|
||
{{ row.calc_type === 1 ? '单笔回款' : '经理聚合' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="performance_sum" label="绩效" min-width="90" />
|
||
<el-table-column prop="payable_sum" label="应付" min-width="90" />
|
||
<el-table-column prop="net_amount" label="净工资" min-width="90" />
|
||
<el-table-column label="试用期" width="70">
|
||
<template #default="{ row }">{{ row.is_probation === 1 ? '是' : '否' }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<p v-if="detail.status === 1 || detail.status === 4" class="hint">
|
||
草稿/已驳回状态可重新生成快照后再次提交。
|
||
</p>
|
||
<p v-else-if="detail.status === 3" class="hint">已锁定:源数据改动只产生提示与调整单。</p>
|
||
</template>
|
||
</div>
|
||
</el-drawer>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.toolbar {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.gen-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.filters {
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.detail-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.detail-month {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: var(--ys-ink);
|
||
}
|
||
|
||
.detail-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
padding: 10px 0;
|
||
font-size: 12px;
|
||
color: var(--ys-ink-3);
|
||
}
|
||
|
||
.hint {
|
||
margin-top: 12px;
|
||
font-size: 12px;
|
||
color: var(--ys-ink-3);
|
||
}
|
||
</style>
|