Commit 0ce83421 by luoqi

fix(plan): 隐藏回收倒计时 + 返池放宽到 active(被认领即可回收)

- 头部「回收倒计时」隐藏:暂无自动回收机制(返池改手动),RecycleCountdown 组件保留待恢复。
- recycle 后端只拒终态(completed/abandoned/superseded);assigned + active 都可返池 —— 无自动
  回收后 plan 可能停在 active 却仍挂客服名下,原「只 assigned 可回收」会误拒(图2 报错场景)。
- 前端「返池」按钮 gate 由 status==='assigned' 改为「有 assignee 且非终态」,与「认领」(无 assignee)
  互斥。本地实测:active+挂名下 → 点返池成功、DB active/assignee=null、toast 正常。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent 45096f85
Pipeline #3383 failed in 0 seconds
...@@ -475,8 +475,10 @@ export class PlanService { ...@@ -475,8 +475,10 @@ export class PlanService {
if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) { if (!plan || plan.hostId !== scope.hostId || plan.tenantId !== scope.tenantId) {
throw new NotFoundException(`Plan ${planId} not found`); throw new NotFoundException(`Plan ${planId} not found`);
} }
if (plan.status !== 'assigned') { // 只拒终态(已完成/已放弃/已被取代);assigned 与 active 都可返池(无自动回收机制后,
throw new BadRequestException(`Plan 当前状态 ${plan.status},不能 recycle(只 assigned 可回收)`); // plan 可能停在 active 但仍挂在某客服名下 → 也要能退回池)。终态返池无意义,拒绝。
if (plan.status === 'completed' || plan.status === 'abandoned' || plan.status === 'superseded') {
throw new BadRequestException(`Plan 已终态(${plan.status}),不能返池`);
} }
await this.prisma.followupPlan.update({ await this.prisma.followupPlan.update({
where: { id: planId }, where: { id: planId },
......
...@@ -864,8 +864,7 @@ function TopBar({ ...@@ -864,8 +864,7 @@ function TopBar({
<span className="hidden sm:inline">{refreshing ? '同步中…' : '刷新'}</span> <span className="hidden sm:inline">{refreshing ? '同步中…' : '刷新'}</span>
</button> </button>
)} )}
{/* 回收倒计时 — 核心信息,始终显示 */} {/* 回收倒计时已隐藏 —— 暂无自动回收机制(返池改为手动)。RecycleCountdown 组件保留,需要时再挂回。 */}
<RecycleCountdown recycleAt={plan.recycleAt} />
{/* 用户名 + 头像 + 退出 —— 嵌入宿主时整体隐藏(见 IdentityCluster) */} {/* 用户名 + 头像 + 退出 —— 嵌入宿主时整体隐藏(见 IdentityCluster) */}
<IdentityCluster /> <IdentityCluster />
</div> </div>
...@@ -936,7 +935,9 @@ function AIDisclaimerFooter({ ...@@ -936,7 +935,9 @@ function AIDisclaimerFooter({
// ────────────────────────────────────────── // ──────────────────────────────────────────
// RecycleCountdown — 实时倒计时 HH:MM:SS,基于 plan.recycleAt // RecycleCountdown — 实时倒计时 HH:MM:SS,基于 plan.recycleAt
// 暂无自动回收机制,头部已不挂载(见上方);组件保留以便日后恢复。
// ────────────────────────────────────────── // ──────────────────────────────────────────
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function RecycleCountdown({ recycleAt }: { recycleAt: Date | null }) { function RecycleCountdown({ recycleAt }: { recycleAt: Date | null }) {
const [tick, setTick] = useState(0); const [tick, setTick] = useState(0);
useEffect(() => { useEffect(() => {
......
...@@ -285,7 +285,13 @@ export function PatientPickerRail({ ...@@ -285,7 +285,13 @@ export function PatientPickerRail({
: undefined : undefined
} }
onRecycle={ onRecycle={
canRecycle && p.status === 'assigned' // 被认领(有 assignee)且非终态 → 可返池。不再只认 status='assigned':
// 无自动回收后 plan 可能停在 active 但仍挂人名下,也要能退回池。
canRecycle &&
!!p.assigneeUserId &&
p.status !== 'completed' &&
p.status !== 'abandoned' &&
p.status !== 'superseded'
? () => void recycle(p.id, p.patient.name ?? '') ? () => void recycle(p.id, p.patient.name ?? '')
: undefined : undefined
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment