This commit is contained in:
Your Name
2026-05-13 16:54:54 +08:00
parent 1e8b5c4646
commit 0eaacbb4ce
9 changed files with 1373 additions and 98 deletions
+20 -1
View File
@@ -110,6 +110,13 @@ const COMMISSION_SETTLEMENT_TIMEOUT_MS = 120000
/** 提成结算业绩(独立于业绩看板 yejiStats) */
export function commissionSettlementOverview(params: {
settlement_month: string
/** 与 tcm.prescriptionOrder/lists 同源:create_time between */
start_time?: string
end_time?: string
/** 默认 3=履约完成,与列表 fulfillment_status 一致 */
fulfillment_status?: number
/** 传 1 时仅统计 is_system_auto=1;显式时段下默认不传(含手动) */
require_system_auto_prescription?: 0 | 1
dept_ids?: number[] | string
channel_code?: string
}) {
@@ -127,14 +134,21 @@ export function commissionSettlementChannelOptions() {
return request.get({ url: '/stats.commissionSettlement/channelOptions' })
}
/** 提成核对:订单明细分页 bucket: 空|current|deferred */
/** 提成核对:订单明细分页 bucket: 空|current|deferredappt_channel_value 可与 assistant_id/doctor_id 组合(0=未匹配挂号渠道) */
export function commissionSettlementOrderLines(params: {
settlement_month: string
start_time?: string
end_time?: string
fulfillment_status?: number
require_system_auto_prescription?: 0 | 1
dept_ids?: number[] | string
channel_code?: string
page?: number
page_size?: number
bucket?: string
assistant_id?: number
doctor_id?: number
appt_channel_value?: number
}) {
return request.get(
{ url: '/stats.commissionSettlement/orderLines', params, timeout: COMMISSION_SETTLEMENT_TIMEOUT_MS },
@@ -160,3 +174,8 @@ export function commissionSettlementConfirmFinalize(params: Record<string, any>)
{ ignoreCancelToken: true }
)
}
/** 撤回「确定本期业绩」:清除顺延结转,状态变为可再次核对/确定 */
export function commissionSettlementConfirmRevoke(params: Record<string, any>) {
return request.post({ url: '/stats.commissionSettlement/confirmRevoke', params })
}
+347 -37
View File
@@ -9,7 +9,11 @@
</span>
<h1 class="cs-masthead__title">提成结算业绩</h1>
<p class="cs-masthead__desc">
系统代开处方对应的已完成业务订单按结算月 7 日截止拆分本期提成与顺延下期可按部门汇总或下发至业绩归属医助与处方开方医生
订单池默认与<strong>处方订单列表</strong>同源<strong>创建时间 start_timeend_time</strong> +
<strong>fulfillment_status</strong>默认完成态 3账号<strong>列表可见性数据域</strong>一并生效
选结算月<strong>仍可</strong>套用物流签收<strong></strong>尾款支付<strong>均须在</strong><strong>结算月 7 24 </strong>拆分本期提成 / 顺延下期
仅有签收在范围内尾款晚于截止仍会顺延
展示部门渠道下拉与列表不同勾选后会与列表<strong>计数不同</strong>
</p>
</div>
<dl v-if="meta.settlement_month || meta.cutoff_end" class="cs-masthead__stats">
@@ -17,9 +21,9 @@
<dt class="cs-stat__label">结算月</dt>
<dd class="cs-stat__value">{{ meta.settlement_month || settlementMonth }}</dd>
</div>
<div v-if="meta.order_month" class="cs-stat">
<dt class="cs-stat__label">订单创建月</dt>
<dd class="cs-stat__value">{{ meta.order_month }}</dd>
<div v-if="meta.order_month || meta.order_start_time" class="cs-stat">
<dt class="cs-stat__label">订单时间窗</dt>
<dd class="cs-stat__value cs-stat__value--xs">{{ orderWindowLabel }}</dd>
</div>
<div v-if="meta.cutoff_end" class="cs-stat">
<dt class="cs-stat__label">截止时刻</dt>
@@ -42,6 +46,21 @@
class="filter-month"
/>
</div>
<div class="filter-field filter-field--rangewrap">
<span class="lbl">订单创建</span>
<el-date-picker
v-model="orderTimeRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
range-separator=""
start-placeholder="列表 start_time"
end-placeholder="列表 end_time"
class="filter-datetimerange"
/>
<el-checkbox v-model="requireSystemAutoOnly" class="filter-sa-check">
仅系统代开处方
</el-checkbox>
</div>
<div class="filter-field">
<span class="lbl">渠道来源</span>
<el-select
@@ -117,17 +136,26 @@
<div class="cs-alert__body">
<p v-if="meta.rule_note">{{ meta.rule_note }}</p>
<p v-else>
订单创建月为<strong>结算月的上一自然月</strong><strong>系统代开处方</strong>完成挂号等自动生成对应的<strong>履约已完成</strong>业务订单
<strong>签收</strong>时间取物流签收时间<strong>尾款支付</strong>取关联支付单已支付时间优先尾款/全部费用类型
签收与尾款均在<strong>结算月 7 24:00 </strong>完成的计入本期提成其余计入顺延下期
请选择结算月并设置<strong>订单创建时间范围</strong>与处方订单列表 URL 参数
<code>start_time</code>/<code>end_time</code>/<code>fulfillment_status</code>
一致切换结算月会自动带出<strong>上一自然月整段</strong>作为默认时段可按业务修改例如 2026-04-0204-30
仅系统代开处方勾选后与列表在未限制开方类型时的条数会不一致
</p>
<p v-if="meta.cutoff_end" class="cs-alert__meta">
当前结算月<strong>{{ meta.settlement_month }}</strong>
· 订单创建月<strong>{{ meta.order_month }}</strong>
· 订单时间<strong>{{ orderWindowLabel }}</strong>
· 履约状态<strong>{{ meta.fulfillment_status ?? 3 }}</strong>
· 截止时刻<strong>{{ meta.cutoff_end }}</strong>
<template v-if="meta.channel_name">
· 渠道<strong>{{ meta.channel_name }}</strong>
</template>
<template v-if="meta.settlement_month">
· 处方池<strong>{{
meta.require_system_auto_prescription ? '仅系统代开' : '含手动与系统'
}}</strong>
<template v-if="meta.explicit_order_time_window">
· 列表对齐<strong></strong></template>
</template>
</p>
</div>
</el-alert>
@@ -136,27 +164,43 @@
<div class="cs-reconcile">
<div class="cs-reconcile__head">
<div class="cs-reconcile__tags">
<el-tag v-if="confirmInfo?.status === 1" type="success" effect="dark">已确定业绩</el-tag>
<el-tag v-else type="warning" effect="plain">待确定业绩</el-tag>
<el-tag v-if="settlementConfirmedLocked" type="success" effect="dark">已确定业绩</el-tag>
<el-tag v-else-if="confirmInfo?.status === 2" type="warning" effect="plain">已撤回确定</el-tag>
<el-tag v-else type="info" effect="plain">待确定业绩</el-tag>
<span
v-if="confirmInfo?.status === 1 && confirmInfo.confirmed_at_text"
v-if="settlementConfirmedLocked && confirmInfo?.confirmed_at_text"
class="cs-reconcile__meta"
>
{{ confirmInfo.confirmed_admin_name || '—' }} · {{ confirmInfo.confirmed_at_text }}
</span>
<span
v-else-if="confirmInfo?.status === 2 && confirmInfo.revoked_at_text"
class="cs-reconcile__meta"
>
撤回{{ confirmInfo.revoked_admin_name || '—' }} · {{ confirmInfo.revoked_at_text }}
</span>
</div>
<div class="cs-reconcile__toolbar">
<el-button type="primary" plain @click="openReconcileDialog">业绩明细核对</el-button>
<el-button :disabled="confirmInfo?.status === 1 || reconcileSaving" @click="handleSaveReconcile">
<el-button :disabled="settlementConfirmedLocked || reconcileSaving" @click="handleSaveReconcile">
保存核对备注
</el-button>
<el-button
type="warning"
:disabled="confirmInfo?.status === 1 || confirmSubmitting"
:disabled="settlementConfirmedLocked || confirmSubmitting"
@click="handleConfirmFinalize"
>
确定本期业绩
</el-button>
<el-button
v-if="settlementConfirmedLocked"
type="danger"
plain
:loading="confirmRevoking"
@click="handleConfirmRevoke"
>
撤回确定
</el-button>
</div>
</div>
<el-input
@@ -166,7 +210,7 @@
maxlength="500"
show-word-limit
placeholder="核对备注(可与财务线下对齐后填写;确定业绩前可反复保存)"
:disabled="confirmInfo?.status === 1"
:disabled="settlementConfirmedLocked"
class="cs-reconcile__note"
/>
<p v-if="Number(totalAgg.carry_in_order_count) > 0" class="cs-reconcile__carry">
@@ -245,6 +289,8 @@
按诊单业绩归属医助 <strong>×</strong> 挂号渠道doctor_appointment.channels拆分到行
<strong>复诊2复诊3自媒体1等保持各自独立</strong>未关联挂号统一归到未匹配挂号渠道
同一医助多行<strong>自动合并医助 / 归属中心单元格</strong>顺延汇入为上期结转进入本期订单池的单数
<strong>展示部门</strong>有勾选时本表仅列出<strong>可归入该部门上下文</strong>的业绩医助与人事部门落在所选部门范围内的账号一致
<strong>点击挂号渠道</strong>查看该行对应的业务订单明细与上方部门筛选一致
</span>
</div>
<el-table
@@ -264,9 +310,10 @@
<template #default="{ row }">
<el-tag
size="small"
class="cs-channel-tag"
class="cs-channel-tag cs-channel-tag--clickable"
:class="channelTagClass(row.appt_channel_name)"
effect="plain"
@click.stop="openOrdersForAssistantChannel(row)"
>
{{ row.appt_channel_name }}
</el-tag>
@@ -293,6 +340,8 @@
<span class="cs-table-head__hint">
按处方创建账号开方人 <strong>×</strong> 挂号渠道拆分
可与医助维度交叉核对同一医生多行自动合并医生 / 归属中心单元格
<strong>展示部门</strong>有勾选时与医助表相同仅统计<strong>该部门上下文内</strong>订单对应的开方医生
<strong>点击挂号渠道</strong>查看该行对应的业务订单明细
</span>
</div>
<el-table
@@ -312,9 +361,10 @@
<template #default="{ row }">
<el-tag
size="small"
class="cs-channel-tag"
class="cs-channel-tag cs-channel-tag--clickable"
:class="channelTagClass(row.appt_channel_name)"
effect="plain"
@click.stop="openOrdersForDoctorChannel(row)"
>
{{ row.appt_channel_name }}
</el-tag>
@@ -341,7 +391,7 @@
<el-dialog
v-model="reconcileDialogVisible"
title="业绩明细核对"
width="1040px"
width="1280px"
destroy-on-close
class="cs-dialog"
@open="onReconcileDialogOpen"
@@ -354,7 +404,26 @@
</el-radio-group>
</div>
<el-table v-loading="linesLoading" :data="linesRows" border stripe size="small" max-height="440">
<el-table-column prop="order_no" label="业务订单号" min-width="116" show-overflow-tooltip />
<el-table-column prop="order_no" label="业务订单号" min-width="128" show-overflow-tooltip />
<el-table-column label="快递单号" min-width="132" show-overflow-tooltip>
<template #default="{ row }">{{ row.tracking_number || '—' }}</template>
</el-table-column>
<el-table-column label="挂号日期" min-width="124" show-overflow-tooltip>
<template #default="{ row }">
<span>{{ row.appointment_date_text || '—' }}</span>
<el-tag
v-if="row.appointment_status && Number(row.appointment_status) !== 3"
class="cs-tag-appt-muted"
type="info"
size="small"
effect="plain"
>未完结挂号</el-tag
>
</template>
</el-table-column>
<el-table-column label="挂号渠道" min-width="124" show-overflow-tooltip>
<template #default="{ row }">{{ row.appt_channel_name || '—' }}</template>
</el-table-column>
<el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
<el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
<el-table-column prop="primary_dept_name" label="归属部门" min-width="100" show-overflow-tooltip />
@@ -373,8 +442,20 @@
<span v-else class="cs-muted"></span>
</template>
</el-table-column>
<el-table-column prop="sign_time_text" label="签收时间" min-width="152" show-overflow-tooltip />
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip />
<el-table-column label="顺延说明" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.bucket === 'deferred' && row.bucket_defer_text" class="cs-muted">{{
row.bucket_defer_text
}}</span>
<span v-else class="cs-muted"></span>
</template>
</el-table-column>
<el-table-column label="物流签收" min-width="152" show-overflow-tooltip>
<template #default="{ row }">{{ row.sign_time_text || '—' }}</template>
</el-table-column>
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip>
<template #default="{ row }">{{ row.pay_time_text || '—' }}</template>
</el-table-column>
<el-table-column prop="create_time_text" label="订单创建" width="152" />
</el-table>
<div class="cs-dialog__pager">
@@ -390,8 +471,8 @@
<el-dialog
v-model="leafOrdersVisible"
:title="leafOrdersDept ? `已完成业绩订单 · ${leafOrdersDept.name}` : '已完成业绩订单'"
width="1040px"
:title="leafOrdersDialogTitle"
width="1280px"
destroy-on-close
class="cs-dialog"
>
@@ -403,12 +484,33 @@
</el-radio-group>
<span class="cs-dialog__hint">
结算月<strong>{{ meta.settlement_month || settlementMonth }}</strong>
<template v-if="meta.order_month"> · 订单创建月<strong>{{ meta.order_month }}</strong></template>
<template v-if="orderWindowLabel && orderWindowLabel !== '—'"
> · 订单创建<strong>{{ orderWindowLabel }}</strong></template
>
<template v-if="meta.channel_name"> · 渠道<strong>{{ meta.channel_name }}</strong></template>
</span>
</div>
<el-table v-loading="leafOrdersLoading" :data="leafOrdersRows" border stripe size="small" max-height="440">
<el-table-column prop="order_no" label="业务订单号" min-width="116" show-overflow-tooltip />
<el-table-column prop="order_no" label="业务订单号" min-width="128" show-overflow-tooltip />
<el-table-column label="快递单号" min-width="132" show-overflow-tooltip>
<template #default="{ row }">{{ row.tracking_number || '—' }}</template>
</el-table-column>
<el-table-column label="挂号日期" min-width="124" show-overflow-tooltip>
<template #default="{ row }">
<span>{{ row.appointment_date_text || '—' }}</span>
<el-tag
v-if="row.appointment_status && Number(row.appointment_status) !== 3"
class="cs-tag-appt-muted"
type="info"
size="small"
effect="plain"
>未完结挂号</el-tag
>
</template>
</el-table-column>
<el-table-column label="挂号渠道" min-width="124" show-overflow-tooltip>
<template #default="{ row }">{{ row.appt_channel_name || '—' }}</template>
</el-table-column>
<el-table-column prop="assistant_name" label="业绩医助" min-width="100" show-overflow-tooltip />
<el-table-column prop="doctor_name" label="开方医生" min-width="100" show-overflow-tooltip />
<el-table-column prop="primary_dept_name" label="归属部门" min-width="120" show-overflow-tooltip />
@@ -427,8 +529,20 @@
<span v-else class="cs-muted"></span>
</template>
</el-table-column>
<el-table-column prop="sign_time_text" label="签收时间" min-width="152" show-overflow-tooltip />
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip />
<el-table-column label="顺延说明" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.bucket === 'deferred' && row.bucket_defer_text" class="cs-muted">{{
row.bucket_defer_text
}}</span>
<span v-else class="cs-muted"></span>
</template>
</el-table-column>
<el-table-column label="物流签收" min-width="152" show-overflow-tooltip>
<template #default="{ row }">{{ row.sign_time_text || '—' }}</template>
</el-table-column>
<el-table-column prop="pay_time_text" label="尾款支付" min-width="152" show-overflow-tooltip>
<template #default="{ row }">{{ row.pay_time_text || '—' }}</template>
</el-table-column>
<el-table-column prop="create_time_text" label="订单创建" width="152" />
</el-table>
<div class="cs-dialog__pager">
@@ -445,12 +559,13 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { ArrowRight, ArrowUp, RefreshRight, Search, Tickets } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
commissionSettlementChannelOptions,
commissionSettlementConfirmFinalize,
commissionSettlementConfirmRevoke,
commissionSettlementDeptOptions,
commissionSettlementOrderLines,
commissionSettlementOverview,
@@ -565,6 +680,16 @@ interface CsDoctorRow {
deferred_period_amount: number
}
type LeafOrderDrillDept = { mode: 'dept'; dept_id: number; dept_name: string }
type LeafOrderDrillChannel = {
mode: 'channel'
title: string
assistant_id?: number
doctor_id?: number
appt_channel_value: number
}
type LeafOrderDrill = LeafOrderDrillDept | LeafOrderDrillChannel
interface CsAssistChannelRow extends CsAssistRow {
appt_channel_value: number
appt_channel_name: string
@@ -584,7 +709,27 @@ function defaultSettlementMonth(): string {
return `${y}-${m}`
}
function pad2(n: number): string {
return String(n).padStart(2, '0')
}
function formatDateTime(d: Date): string {
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
}
/** 订单池默认:结算月「上一自然月」整段(与未传 start/end 的后端行为一致) */
function defaultOrderTimeRangeForSettlementMonth(sm: string): [string, string] {
const m = /^(\d{4})-(\d{2})$/.exec(sm)
const y = m ? parseInt(m[1], 10) : new Date().getFullYear()
const monthNum = m ? parseInt(m[2], 10) : new Date().getMonth() + 1
const start = new Date(y, monthNum - 2, 1, 0, 0, 0)
const end = new Date(y, monthNum - 1, 0, 23, 59, 59)
return [formatDateTime(start), formatDateTime(end)]
}
const settlementMonth = ref<string>(defaultSettlementMonth())
const orderTimeRange = ref<[string, string] | null>(defaultOrderTimeRangeForSettlementMonth(settlementMonth.value))
const requireSystemAutoOnly = ref(false)
const loading = ref(false)
const deptOptions = ref<DeptOption[]>([])
const deptOptionsLoading = ref(false)
@@ -612,9 +757,14 @@ const totalAgg = ref<{
const meta = reactive({
settlement_month: '',
order_month: '',
order_start_time: '',
order_end_time: '',
fulfillment_status: 3,
explicit_order_time_window: false,
cutoff_end: '',
rule_note: '',
channel_name: '',
require_system_auto_prescription: false,
})
const confirmInfo = ref<{
@@ -622,10 +772,17 @@ const confirmInfo = ref<{
reconcile_note?: string
confirmed_admin_name?: string
confirmed_at_text?: string
revoked_admin_name?: string
revoked_at_text?: string
is_confirmed_locked?: boolean
} | null>(null)
const reconcileNote = ref('')
const reconcileSaving = ref(false)
const confirmSubmitting = ref(false)
const confirmRevoking = ref(false)
/** 仅 status=1 时锁定,禁止保存备注 / 重复确定;status=2 已撤回可再操作 */
const settlementConfirmedLocked = computed(() => Number(confirmInfo.value?.status) === 1)
const reconcileDialogVisible = ref(false)
const linesLoading = ref(false)
@@ -636,7 +793,7 @@ const linesPage = ref(1)
const linesPageSize = ref(20)
const leafOrdersVisible = ref(false)
const leafOrdersDept = ref<{ id: number; name: string } | null>(null)
const leafOrderDrill = ref<LeafOrderDrill | null>(null)
const leafOrdersBucket = ref<string>('all')
const leafOrdersRows = ref<Record<string, any>[]>([])
const leafOrdersTotal = ref(0)
@@ -644,6 +801,20 @@ const leafOrdersPage = ref(1)
const leafOrdersPageSize = ref(20)
const leafOrdersLoading = ref(false)
const leafOrdersDialogTitle = computed(() => {
const d = leafOrderDrill.value
if (!d) return '已完成业绩订单'
if (d.mode === 'dept') return `已完成业绩订单 · ${d.dept_name}`
return d.title
})
const orderWindowLabel = computed(() => {
if (meta.order_start_time && meta.order_end_time) {
return `${meta.order_start_time}${meta.order_end_time}`
}
return meta.order_month || '—'
})
const deptChildrenMap = computed(() => {
const map = new Map<number, number[]>()
for (const d of deptOptions.value) {
@@ -718,7 +889,45 @@ function openOrderLinesForDept(deptId: number, deptName: string) {
ElMessage.warning('请选择结算月')
return
}
leafOrdersDept.value = { id: deptId, name: deptName }
leafOrderDrill.value = { mode: 'dept', dept_id: deptId, dept_name: deptName }
leafOrdersBucket.value = 'all'
leafOrdersPage.value = 1
leafOrdersRows.value = []
leafOrdersTotal.value = 0
leafOrdersVisible.value = true
loadLeafOrders(1)
}
function openOrdersForAssistantChannel(row: CsAssistChannelRow) {
if (!settlementMonth.value) {
ElMessage.warning('请选择结算月')
return
}
leafOrderDrill.value = {
mode: 'channel',
title: `业务订单 · ${row.appt_channel_name} · 医助 ${row.assistant_name}`,
assistant_id: row.assistant_id,
appt_channel_value: Number(row.appt_channel_value ?? 0),
}
leafOrdersBucket.value = 'all'
leafOrdersPage.value = 1
leafOrdersRows.value = []
leafOrdersTotal.value = 0
leafOrdersVisible.value = true
loadLeafOrders(1)
}
function openOrdersForDoctorChannel(row: CsDoctorChannelRow) {
if (!settlementMonth.value) {
ElMessage.warning('请选择结算月')
return
}
leafOrderDrill.value = {
mode: 'channel',
title: `业务订单 · ${row.appt_channel_name} · 医生 ${row.doctor_name}`,
doctor_id: row.doctor_id,
appt_channel_value: Number(row.appt_channel_value ?? 0),
}
leafOrdersBucket.value = 'all'
leafOrdersPage.value = 1
leafOrdersRows.value = []
@@ -728,18 +937,30 @@ function openOrderLinesForDept(deptId: number, deptName: string) {
}
async function loadLeafOrders(page: number) {
if (!settlementMonth.value || !leafOrdersDept.value) return
if (!settlementMonth.value || !leafOrderDrill.value) return
leafOrdersLoading.value = true
leafOrdersPage.value = page
try {
const d = leafOrderDrill.value
const p: Record<string, any> = {
settlement_month: settlementMonth.value,
dept_ids: String(leafOrdersDept.value.id),
page,
page_size: leafOrdersPageSize.value,
}
if (selectedChannel.value) {
p.channel_code = selectedChannel.value
if (d.mode === 'dept') {
p.dept_ids = String(d.dept_id)
if (selectedChannel.value) {
p.channel_code = selectedChannel.value
}
} else {
Object.assign(p, buildQueryParams())
p.appt_channel_value = d.appt_channel_value
if (d.assistant_id != null) {
p.assistant_id = d.assistant_id
}
if (d.doctor_id != null) {
p.doctor_id = d.doctor_id
}
}
if (leafOrdersBucket.value && leafOrdersBucket.value !== 'all') {
p.bucket = leafOrdersBucket.value
@@ -796,6 +1017,11 @@ async function loadOverview() {
ElMessage.warning('请选择结算月')
return
}
const ot = orderTimeRange.value
if (!ot || !ot[0] || !ot[1]) {
ElMessage.warning('请设置订单创建时间范围')
return
}
loading.value = true
try {
const res: any = await commissionSettlementOverview(buildQueryParams() as any)
@@ -807,9 +1033,21 @@ async function loadOverview() {
totalAgg.value = res?.total || {}
meta.settlement_month = res?.settlement_month || sm
meta.order_month = res?.order_month || ''
meta.order_start_time = res?.order_start_time ? String(res.order_start_time) : ''
meta.order_end_time = res?.order_end_time ? String(res.order_end_time) : ''
meta.fulfillment_status =
res?.fulfillment_status != null ? Number(res.fulfillment_status) : 3
meta.explicit_order_time_window =
typeof res?.explicit_order_time_window === 'boolean'
? res.explicit_order_time_window
: !!(meta.order_start_time && meta.order_end_time)
meta.cutoff_end = res?.cutoff_end || ''
meta.rule_note = res?.rule_note || ''
meta.channel_name = res?.channel_name || ''
meta.require_system_auto_prescription =
typeof res?.require_system_auto_prescription === 'boolean'
? res.require_system_auto_prescription
: false
confirmInfo.value = res?.confirm ?? null
reconcileNote.value =
res?.confirm?.reconcile_note != null ? String(res.confirm.reconcile_note) : ''
@@ -829,7 +1067,18 @@ async function loadOverview() {
function buildQueryParams(): Record<string, any> {
const sm = settlementMonth.value
const p: Record<string, any> = { settlement_month: sm }
const p: Record<string, any> = {
settlement_month: sm,
fulfillment_status: 3,
}
const ot = orderTimeRange.value
if (ot && ot[0] && ot[1]) {
p.start_time = ot[0]
p.end_time = ot[1]
}
if (requireSystemAutoOnly.value) {
p.require_system_auto_prescription = 1
}
if (selectedDeptIds.value.length > 0) {
p.dept_ids = selectedDeptIds.value.join(',')
}
@@ -882,8 +1131,8 @@ async function handleSaveReconcile() {
ElMessage.warning('请选择结算月')
return
}
if (confirmInfo.value?.status === 1) {
ElMessage.warning('已确定业绩不可修改备注')
if (settlementConfirmedLocked.value) {
ElMessage.warning('已确定业绩不可修改备注;请先撤回确定')
return
}
reconcileSaving.value = true
@@ -908,7 +1157,7 @@ async function handleConfirmFinalize() {
}
try {
await ElMessageBox.confirm(
'确定后将为「顺延下期」订单写入结转记录下一结算月在相同渠道与部门筛选下自动并入统计。确定后不可在线撤销,请与财务对后再操作。',
'确定后将为「顺延下期」订单写入结转记录下一结算月在相同渠道与部门筛选下自动并入统计。确定后仍可「撤回确定」以清除结转并重新核对(与财务对后再操作。',
'确定本期业绩',
{
confirmButtonText: '确定',
@@ -936,8 +1185,40 @@ async function handleConfirmFinalize() {
}
}
async function handleConfirmRevoke() {
if (!settlementMonth.value) {
ElMessage.warning('请选择结算月')
return
}
try {
await ElMessageBox.confirm(
'将撤回本次「确定业绩」状态,并删除已写入的顺延结转记录(下期将不再汇入这些单)。确定后继续?',
'撤回确定业绩',
{
confirmButtonText: '撤回',
cancelButtonText: '取消',
type: 'warning',
}
)
} catch {
return
}
confirmRevoking.value = true
try {
await commissionSettlementConfirmRevoke(buildQueryParams() as any)
ElMessage.success('已撤回确定;可重新保存备注并再次确定本期业绩')
await loadOverview()
} catch (e: any) {
ElMessage.error(e?.msg || e?.message || '撤回失败')
} finally {
confirmRevoking.value = false
}
}
function handleReset() {
settlementMonth.value = defaultSettlementMonth()
orderTimeRange.value = defaultOrderTimeRangeForSettlementMonth(settlementMonth.value)
requireSystemAutoOnly.value = false
selectedDeptIds.value = []
selectedChannel.value = ''
loadOverview()
@@ -1053,6 +1334,12 @@ function getSummaries(param: { columns: Array<{ property?: string }>; data: CsRo
return sums
}
watch(settlementMonth, (v) => {
if (v) {
orderTimeRange.value = defaultOrderTimeRangeForSettlementMonth(v)
}
})
onMounted(async () => {
await Promise.all([loadDeptOptions(), loadChannelOptions()])
await loadOverview()
@@ -1249,7 +1536,24 @@ onMounted(async () => {
flex: 1 1 280px;
min-width: 240px;
}
.filter-field--rangewrap {
flex: 1 1 440px;
min-width: 300px;
}
.filter-month { width: 168px; }
.filter-datetimerange {
width: 100%;
max-width: 520px;
}
.filter-sa-check {
margin-top: 6px;
font-weight: 500;
color: var(--cs-text);
}
.cs-tag-appt-muted {
margin-left: 6px;
vertical-align: middle;
}
.filter-select--channel { width: 220px; }
.filter-dept-tree-select { width: 100%; min-width: 240px; }
.filter-actions {
@@ -1581,6 +1885,12 @@ onMounted(async () => {
border-color: #cbd5e1 !important;
color: #64748b !important;
}
&--clickable {
cursor: pointer;
&:hover {
filter: brightness(0.96);
}
}
&--neutral {
background: #f0fdfa !important;
border-color: #99f6e4 !important;