完善合同气瓶绑定引导
This commit is contained in:
@@ -49,7 +49,7 @@ func TestContractActivationErrorMessages(t *testing.T) {
|
||||
errContractAttachment: "合同附件无效,请重新上传有效的 PDF 文件后再启用",
|
||||
errContractNotEffective: "合同尚未到生效时间,暂时不能启用",
|
||||
errContractExpired: "合同已到期,请调整到期时间或续签后再启用",
|
||||
errContractNoProduct: "合同尚未绑定有效气瓶,请先绑定气瓶后再启用",
|
||||
errContractNoProduct: "合同尚未绑定有效智能气阀。请先点击“绑定气阀”;若无可选设备,请到“智能气阀管理”将设备归属变更为合同用户,并确认设备已启用、未报废",
|
||||
}
|
||||
for businessErr, message := range want {
|
||||
if businessErr.Error() != message {
|
||||
|
||||
@@ -28,7 +28,7 @@ var (
|
||||
errContractAttachment = errors.New("合同附件无效,请重新上传有效的 PDF 文件后再启用")
|
||||
errContractNotEffective = errors.New("合同尚未到生效时间,暂时不能启用")
|
||||
errContractExpired = errors.New("合同已到期,请调整到期时间或续签后再启用")
|
||||
errContractNoProduct = errors.New("合同尚未绑定有效气瓶,请先绑定气瓶后再启用")
|
||||
errContractNoProduct = errors.New("合同尚未绑定有效智能气阀。请先点击“绑定气阀”;若无可选设备,请到“智能气阀管理”将设备归属变更为合同用户,并确认设备已启用、未报废")
|
||||
errBindingContractStatus = errors.New("仅草稿合同可以绑定气瓶")
|
||||
errBindingProductDisabled = errors.New("所选气瓶已停用,不能绑定到合同")
|
||||
errBindingProductScrapped = errors.New("所选气瓶已报废,不能绑定到合同")
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
|
||||
合同气瓶必须属于合同用户、处于可用状态且未报废;订单只能使用有效合同中仍绑定的气瓶。
|
||||
|
||||
合同气瓶绑定页面只允许选择草稿合同;已生效、已过期或已终止合同不进入绑定候选。合同管理列表仍展示全部生命周期状态。
|
||||
合同气瓶绑定页面只允许选择草稿合同;已生效、已过期或已终止合同不进入绑定候选。绑定字段使用“智能气阀”业务名称。合同用户没有符合条件的智能气阀时,页面必须说明设备需先归属合同用户、处于启用且未报废状态,并提供前往“智能气阀管理”的站内入口。合同管理列表仍展示全部生命周期状态。
|
||||
|
||||
### 6.6 气体配送订单
|
||||
|
||||
|
||||
35
docs/操作日志_合同气瓶空状态引导_20260814.md
Normal file
35
docs/操作日志_合同气瓶空状态引导_20260814.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 操作日志:合同气瓶空状态引导
|
||||
|
||||
操作时间:2026-08-14
|
||||
|
||||
操作类型:修改、扩展
|
||||
|
||||
影响模块:平台总后台、配送合同、合同气瓶
|
||||
|
||||
## 操作前状态
|
||||
|
||||
草稿合同没有可绑定设备时,智能气阀下拉仅显示“暂无数据”。管理员无法得知设备必须先归属合同用户并处于启用、未报废状态;启用合同失败提示也没有完整说明处理顺序。
|
||||
|
||||
## 具体操作
|
||||
|
||||
- 将合同气瓶绑定表单字段标题从“智能气阀唯一标识”改为“智能气阀”。
|
||||
- 空候选明确提示设备归属、启用状态和报废状态要求。
|
||||
- 增加“前往智能气阀管理”站内入口。
|
||||
- 启用合同失败时提示先绑定智能气阀;无候选时引导先变更设备归属。
|
||||
- 保留服务端“设备必须属于合同用户”的最终校验,不允许跨用户绑定。
|
||||
|
||||
## 操作后状态
|
||||
|
||||
管理员可以从空状态直接进入智能气阀管理,完成“变更智能气阀归属”,返回合同列表绑定设备后再启用合同。
|
||||
|
||||
## 代码变更
|
||||
|
||||
- `backend/api/internal/logic/platform/gasorder/gasorder.go`:完善合同无有效智能气阀时的业务提示。
|
||||
- `backend/api/internal/logic/platform/gasorder/contract_change_error_test.go`:固定中文错误信息。
|
||||
- `frontend/platform_admin/src/api/resources.ts`:配置业务字段名、空状态说明和跳转入口。
|
||||
- `frontend/platform_admin/src/views/resource/ResourceFieldForm.vue`:支持关系空状态站内操作按钮。
|
||||
- `frontend/platform_admin/scripts/check-contract-product-entry.mjs`:增加字段名、说明和入口回归检查。
|
||||
|
||||
## 验证与风险
|
||||
|
||||
本次不修改数据库结构和数据,不放宽候选筛选及绑定校验。新增空状态操作为通用可选配置,未配置的其他关系字段保持原有行为。
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 功能:检查配送合同详情页保留可发现的气瓶绑定入口与合同预填参数。
|
||||
* 版本:v1.0.0
|
||||
* 版本:v1.1.0
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
@@ -17,6 +17,10 @@ const resourcesSource = readFileSync(
|
||||
new URL('../src/api/resources.ts', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const formSource = readFileSync(
|
||||
new URL('../src/views/resource/ResourceFieldForm.vue', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
assert.match(listSource, /definition\.name === 'gasorder_contract' && Number\(record\.contract_status\) === 0/, '绑定入口必须仅展示在草稿合同列表行');
|
||||
assert.match(listSource, />\s*绑定气瓶\s*</, '草稿合同操作列必须展示“绑定气瓶”按钮');
|
||||
@@ -27,5 +31,9 @@ assert.match(listSource, /return_to: route\.fullPath/, '绑定完成后必须支
|
||||
assert.doesNotMatch(detailSource, />\s*绑定气瓶\s*</, '合同详情页不应继续展示绑定按钮');
|
||||
assert.match(detailSource, /definition\.value\.name === 'gasorder_contract_product'[\s\S]*field\.key === 'gasorder_contract_identity'[\s\S]*route\.query\.relation_key === 'gasorder_contract_identity'[\s\S]*Boolean\(route\.query\.owner_identity\)/, '从合同列表进入时必须锁定预填合同');
|
||||
assert.match(resourcesSource, /readonlyRelationText: true, displayRelationLabel: true/, '锁定合同必须以可读名称展示');
|
||||
assert.match(resourcesSource, /label: '智能气阀'/, '绑定表单不得继续显示“智能气阀唯一标识”');
|
||||
assert.match(resourcesSource, /该合同用户暂无可绑定的智能气阀/, '空候选必须说明合同用户归属前置条件');
|
||||
assert.match(resourcesSource, /routeName: 'product-info'/, '空候选必须提供智能气阀管理入口');
|
||||
assert.match(formSource, /navigateRelationEmptyAction/, '关系空状态必须渲染可执行的站内引导');
|
||||
|
||||
console.log('合同气瓶绑定入口检查通过:仅草稿合同列表行可见,进入后锁定所选合同。');
|
||||
console.log('合同气瓶绑定入口检查通过:草稿合同可绑定气阀,空候选会解释归属条件并跳转智能气阀管理。');
|
||||
|
||||
@@ -95,6 +95,8 @@ export type ResourceField = {
|
||||
relationAutoSelectKey?: string;
|
||||
/** 关联候选为空时展示的业务引导。 */
|
||||
relationEmptyText?: string;
|
||||
/** 关联候选为空时提供的站内处理入口。 */
|
||||
relationEmptyAction?: { label: string; routeName: string };
|
||||
/** 该关联字段每次加载与搜索都必须携带的精确筛选参数。 */
|
||||
relationFilters?: Record<string, string>;
|
||||
/** 筛选结果中不存在当前值时立即清空,禁止通过详情补载绕过候选范围。 */
|
||||
@@ -541,7 +543,10 @@ export const resources: ResourceUiDefinition[] = [
|
||||
relationInvalidMessage: '所选合同已不是草稿状态,已清空,请重新选择',
|
||||
relationEmptyText: '暂无可绑定气瓶的草稿合同,请先新建配送合同',
|
||||
}), relation('product_info_identity', '/product_info', true, {
|
||||
label: '智能气阀',
|
||||
placeholder: '请先选择合同,再选择该合同用户可绑定的气瓶',
|
||||
relationEmptyText: '该合同用户暂无可绑定的智能气阀。请先到“智能气阀管理”将设备归属变更为该用户,并确认设备已启用、未报废。',
|
||||
relationEmptyAction: { label: '前往智能气阀管理', routeName: 'product-info' },
|
||||
relationLinkage: {
|
||||
parentKey: 'gasorder_contract_identity', optionParentKey: '',
|
||||
filterKey: 'contract_identity', filterOnly: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--
|
||||
功能:渲染标准资源的新建、编辑和业务动作字段控件。
|
||||
版本:v1.3.0
|
||||
版本:v1.4.0
|
||||
-->
|
||||
<template>
|
||||
<div class="field-grid">
|
||||
@@ -114,7 +114,17 @@
|
||||
}}
|
||||
</a-option>
|
||||
<template #empty>
|
||||
<div class="relation-empty">{{ relationEmptyText(field) }}</div>
|
||||
<div class="relation-empty">
|
||||
<span>{{ relationEmptyText(field) }}</span>
|
||||
<a-button
|
||||
v-if="field.relationEmptyAction && !relationRequiresParent(field)"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="navigateRelationEmptyAction(field)"
|
||||
>
|
||||
{{ field.relationEmptyAction.label }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-select>
|
||||
<div
|
||||
@@ -137,6 +147,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { relationOptionLabel } from '@/api/resource-display';
|
||||
import { dynamicRelationResource, type ResourceField } from '@/api/resources';
|
||||
import type { ResourceRow } from '@/api/resource-page-rules';
|
||||
@@ -181,6 +192,7 @@ const emit = defineEmits<{
|
||||
'preview-attachment': [];
|
||||
}>();
|
||||
const model = defineModel<Record<string, any>>({ required: true });
|
||||
const router = useRouter();
|
||||
const disabledSet = computed(() => new Set(props.disabledKeys));
|
||||
|
||||
function isWide(field: ResourceField) {
|
||||
@@ -267,6 +279,12 @@ function relationEmptyText(field: ResourceField) {
|
||||
return field.relationEmptyText ?? '暂无数据';
|
||||
}
|
||||
|
||||
/** 从空候选引导直接进入对应管理页面,避免管理员只能猜测前置操作。 */
|
||||
function navigateRelationEmptyAction(field: ResourceField) {
|
||||
if (!field.relationEmptyAction) return;
|
||||
void router.push({ name: field.relationEmptyAction.routeName });
|
||||
}
|
||||
|
||||
/** 为历史未知编码补充只读选项,避免下拉框退回显示原始值。 */
|
||||
function selectOptions(field: ResourceField) {
|
||||
const options = [...(field.options ?? [])];
|
||||
@@ -309,6 +327,15 @@ function selectOptions(field: ResourceField) {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
.relation-empty {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
color: var(--color-text-3);
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
}
|
||||
.relation-identity {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
Reference in New Issue
Block a user