fix(pl): 证书齐备待完善归档不再误出齐备按钮。
归档结转无用量行时保留待完善错误文案,前端按计划齐备状态只提醒完善证件。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
95ecb7236e
commit
02d576abfe
7 files changed
+146
-9
No files matched your search
@@ -3807,7 +3807,7 @@ def archive_plan(
|
||||
db.flush()
|
||||
result = get_plan(id, db, user)
|
||||
if not quota_settled:
|
||||
result.quota_settlement_error = read_quota_settlement_error(db, tenant_no, plan.id)
|
||||
result.quota_settlement_error = read_quota_settlement_error(db, tenant_no, plan.id, plan=plan)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -171,26 +171,40 @@ def settle_plan_quota(db, user, plan, payload, *, adjustment=False):
|
||||
except ArchiveQuotaBlocked as exc:
|
||||
raise HTTPException(409, str(exc)) from exc
|
||||
except (HTTPException, ValueError, AllocationIncomplete) as exc:
|
||||
message = str(getattr(exc, 'detail', exc))
|
||||
# 无用量行(如证书齐备仅放行安排)时仍保留原因,供同请求读回。
|
||||
setattr(plan, '_quota_settlement_error', message)
|
||||
rows = db.query(PlPlanCertUsage).filter_by(tenant_no=user.tenant_no, plan_id=plan.id, del_flag=0).all()
|
||||
for row in rows:
|
||||
if not adjustment:
|
||||
row.usage_status = 'exception'
|
||||
row.confirmation_snapshot = {**(row.confirmation_snapshot or {}), 'executed': True,
|
||||
'settlementError': str(getattr(exc, 'detail', exc))}
|
||||
'settlementError': message}
|
||||
row.update_by = user.id
|
||||
if not adjustment:
|
||||
plan.archive_status = 'exception'
|
||||
return False
|
||||
|
||||
|
||||
def read_quota_settlement_error(db, tenant_no, plan_id):
|
||||
def read_quota_settlement_error(db, tenant_no, plan_id, plan=None):
|
||||
"""结转失败时返回已经写在用量快照上的原因。"""
|
||||
from app.models.pl_models import PlPlan
|
||||
from app.models.pl_quota_models import PlPlanCertUsage
|
||||
|
||||
if plan is not None:
|
||||
cached = getattr(plan, '_quota_settlement_error', None)
|
||||
if isinstance(cached, str) and cached.strip():
|
||||
return cached.strip()
|
||||
rows = db.query(PlPlanCertUsage).filter_by(tenant_no=tenant_no, plan_id=plan_id, del_flag=0).all()
|
||||
for row in rows:
|
||||
message = (row.confirmation_snapshot or {}).get('settlementError')
|
||||
if isinstance(message, str) and message.strip():
|
||||
return message.strip()
|
||||
target = plan
|
||||
if target is None:
|
||||
target = db.query(PlPlan).filter_by(id=plan_id, tenant_no=tenant_no, del_flag=0).first()
|
||||
if target is not None and int(getattr(target, 'quota_cert_pending_fulfill', 0) or 0) == 1:
|
||||
return '该计划仍处于证书齐备待完善,请先在证书齐备中完善并关闭后再归档'
|
||||
return '实际数量已保存,证件没有扣减。请把实际数量改小,或先补充购买证和运输证,再重新归档。'
|
||||
|
||||
|
||||
|
||||
@@ -61,6 +61,47 @@ def test_apply_plan_quota_new_rule_waive_skips_reserve_not_settle(monkeypatch):
|
||||
assert '待完善' in str(exc.value.detail)
|
||||
|
||||
|
||||
def test_settle_pending_fulfill_without_usage_rows_keeps_pending_message(monkeypatch):
|
||||
"""证书齐备待完善且无用量行时,归档结转错误不能回落成通用扣减文案。"""
|
||||
from contextlib import nullcontext
|
||||
|
||||
from app.services import pl_quota_business as biz
|
||||
from fastapi import HTTPException
|
||||
|
||||
plan = SimpleNamespace(
|
||||
id='p1',
|
||||
quota_cert_waived=1,
|
||||
quota_cert_pending_fulfill=1,
|
||||
cert_quota_rule_version='customer_category',
|
||||
cert_configuration={},
|
||||
archive_status='none',
|
||||
)
|
||||
user = SimpleNamespace(id='u', tenant_no='t')
|
||||
pending = '该计划仍处于证书齐备待完善,请先在证书齐备中完善并关闭后再归档'
|
||||
|
||||
class _Query:
|
||||
def filter_by(self, **kwargs):
|
||||
return self
|
||||
|
||||
def all(self):
|
||||
return []
|
||||
|
||||
db = MagicMock()
|
||||
db.query.return_value = _Query()
|
||||
db.flush = MagicMock()
|
||||
db.begin_nested = MagicMock(return_value=nullcontext())
|
||||
|
||||
def boom(*_a, **_k):
|
||||
raise HTTPException(409, pending)
|
||||
|
||||
monkeypatch.setattr(biz, 'apply_plan_quota', boom)
|
||||
|
||||
assert biz.settle_plan_quota(db, user, plan, None) is False
|
||||
message = biz.read_quota_settlement_error(db, 't', 'p1', plan=plan)
|
||||
assert '待完善' in message
|
||||
assert '证件没有扣减' not in message
|
||||
|
||||
|
||||
def test_list_certs_default_excludes_closed_void(monkeypatch):
|
||||
from app.services import pl_quota_cert_service as svc
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { resolveArchiveQuotaBlockMode } from '../src/utils/pl-archive-quota-block'
|
||||
|
||||
test('pending fulfill plan only alerts, no certificate-ready confirm', () => {
|
||||
assert.equal(
|
||||
resolveArchiveQuotaBlockMode(
|
||||
'实际数量已保存,证件没有扣减。请把实际数量改小,或先补充购买证和运输证,再重新归档。',
|
||||
'用证没有完成',
|
||||
{ quotaCertPendingFulfill: 1, quotaCertWaived: 1 }
|
||||
),
|
||||
'alert-pending'
|
||||
)
|
||||
})
|
||||
|
||||
test('already waived plan does not offer archive-time certificate ready', () => {
|
||||
assert.equal(
|
||||
resolveArchiveQuotaBlockMode('余额不足', '用证没有完成', { quotaCertWaived: 1 }),
|
||||
'alert-pending'
|
||||
)
|
||||
})
|
||||
|
||||
test('pending keywords still force alert mode without plan flags', () => {
|
||||
assert.equal(
|
||||
resolveArchiveQuotaBlockMode(
|
||||
'该计划仍处于证书齐备待完善,请先在证书齐备中完善并关闭后再归档',
|
||||
'用证没有完成',
|
||||
{}
|
||||
),
|
||||
'alert-pending'
|
||||
)
|
||||
})
|
||||
|
||||
test('unmarked legacy quota block still offers certificate ready', () => {
|
||||
assert.equal(
|
||||
resolveArchiveQuotaBlockMode(
|
||||
'实际数量已保存,证件没有扣减。请把实际数量改小,或先补充购买证和运输证,再重新归档。',
|
||||
'用证没有完成',
|
||||
{ quotaCertPendingFulfill: 0, quotaCertWaived: 0 }
|
||||
),
|
||||
'confirm-ready'
|
||||
)
|
||||
})
|
||||
|
||||
test('non quota-title blocks stay plain alert', () => {
|
||||
assert.equal(resolveArchiveQuotaBlockMode('不能归档:缺证', '不能归档', {}), 'alert-plain')
|
||||
})
|
||||
@@ -624,6 +624,13 @@ export interface Plan {
|
||||
hazardCertReady?: number
|
||||
hazardCertReadyBy?: string
|
||||
hazardCertReadyTime?: string
|
||||
/** 证书齐备:新规则仅放行安排 */
|
||||
quotaCertWaived?: number
|
||||
quotaCertWaivedBy?: string
|
||||
quotaCertWaivedByName?: string
|
||||
quotaCertWaivedTime?: string
|
||||
/** 新规则证书齐备待完善 */
|
||||
quotaCertPendingFulfill?: number
|
||||
erpEntered?: number
|
||||
remark?: string
|
||||
createBy?: string
|
||||
|
||||
@@ -299,6 +299,7 @@
|
||||
extractArchiveLineOverageDetail,
|
||||
showArchiveLineOverageConfirm
|
||||
} from '@/utils/pl-contract-qty-reminder'
|
||||
import { resolveArchiveQuotaBlockMode } from '@/utils/pl-archive-quota-block'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
@@ -1602,12 +1603,12 @@
|
||||
|
||||
async function showArchiveQuotaBlockDialog(message: string, title = '不能归档') {
|
||||
const html = `<p class="pl-arrange-block-text">${escapeArchiveBlockText(message)}</p>`
|
||||
const pendingFulfill =
|
||||
message.includes('待完善') || message.includes('完善并关闭') || message.includes('证书齐备待完善')
|
||||
if (title !== '用证没有完成' || pendingFulfill) {
|
||||
const guide = pendingFulfill
|
||||
? `${html}<p class="pl-arrange-block-text">新规则下证书齐备只放行安排,不能绕过归档挂证。请到「证书齐备 → 已标记待完善证书」上传证件并执行完善并关闭后再归档。</p>`
|
||||
: html
|
||||
const mode = resolveArchiveQuotaBlockMode(message, title, plan.value)
|
||||
if (mode !== 'confirm-ready') {
|
||||
const guide =
|
||||
mode === 'alert-pending'
|
||||
? `${html}<p class="pl-arrange-block-text">该运输计划已走证书齐备,归档前须完善证件。请到「证书齐备 → 已标记待完善证书」上传购买证/运输证并执行完善并关闭后再归档。</p>`
|
||||
: html
|
||||
await ElMessageBox.alert(guide, title, {
|
||||
type: 'warning',
|
||||
confirmButtonText: '知道了',
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/** 归档用证拦截弹窗:已齐备/待完善只提醒,未齐备旧规则才可再点证书齐备。 */
|
||||
|
||||
export type ArchiveQuotaBlockMode = 'alert-pending' | 'alert-plain' | 'confirm-ready'
|
||||
|
||||
export type ArchiveQuotaBlockPlanFlags = {
|
||||
quotaCertPendingFulfill?: number | boolean | null
|
||||
quotaCertWaived?: number | boolean | null
|
||||
}
|
||||
|
||||
function flagOn(value: number | boolean | null | undefined) {
|
||||
return value === true || Number(value || 0) === 1
|
||||
}
|
||||
|
||||
export function resolveArchiveQuotaBlockMode(
|
||||
message: string,
|
||||
title: string,
|
||||
plan?: ArchiveQuotaBlockPlanFlags | null
|
||||
): ArchiveQuotaBlockMode {
|
||||
const pendingByMessage =
|
||||
message.includes('待完善') ||
|
||||
message.includes('完善并关闭') ||
|
||||
message.includes('证书齐备待完善')
|
||||
const pendingByPlan = flagOn(plan?.quotaCertPendingFulfill) || flagOn(plan?.quotaCertWaived)
|
||||
if (title !== '用证没有完成') return 'alert-plain'
|
||||
if (pendingByPlan || pendingByMessage) return 'alert-pending'
|
||||
return 'confirm-ready'
|
||||
}
|
||||
Reference in new issue
Block a user