修复配送合同候选范围与失效联动
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
"staff-relations:check": "node scripts/check-staff-relation-policy.mjs",
|
||||
"user-address-display:check": "node scripts/check-user-address-relation-display.mjs",
|
||||
"gasorder-address:check": "node scripts/check-gasorder-address-linkage.mjs",
|
||||
"gasorder-contract-candidates:check": "node scripts/check-gasorder-contract-candidates.mjs",
|
||||
"product-ownership:check": "node scripts/check-product-ownership-action.mjs",
|
||||
"product-ownership-display:check": "node scripts/check-product-ownership-display.mjs",
|
||||
"product-lifecycle-display:check": "node scripts/check-product-lifecycle-display.mjs",
|
||||
|
||||
@@ -10,6 +10,6 @@ const linkage = readFileSync(new URL('../src/views/resource/use-resource-relatio
|
||||
|
||||
assert.match(resources, /filterKey: 'contract_identity', filterOnly: true/, '气瓶字段必须按当前合同筛选');
|
||||
assert.match(resources, /请先选择合同,再选择该合同用户可绑定的气瓶/, '气瓶字段必须解释筛选范围');
|
||||
assert.match(linkage, /if \(linkage\.filterOnly\) return ''/, '纯筛选联动不得执行父标识校验');
|
||||
assert.match(linkage, /linkage\.filterOnly\) continue/, '纯筛选联动不得执行父标识校验');
|
||||
|
||||
console.log('合同气瓶候选过滤检查通过:气瓶下拉按当前合同加载可绑定项。');
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 功能:静态检查订单创建与合同气瓶绑定使用正确的合同候选范围。
|
||||
* 版本:v1.0.0
|
||||
*/
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const resources = readFileSync(new URL('../src/api/resources.ts', import.meta.url), 'utf8');
|
||||
const relations = readFileSync(new URL('../src/views/resource/use-resource-relations.ts', import.meta.url), 'utf8');
|
||||
const linkage = readFileSync(new URL('../src/views/resource/use-resource-relation-linkage.ts', import.meta.url), 'utf8');
|
||||
|
||||
assert.match(resources, /relationFilters: \{ candidate: 'order' \}/, '订单创建必须只加载可履约合同');
|
||||
assert.match(resources, /relationFilters: \{ candidate: 'binding' \}/, '合同气瓶绑定必须只加载草稿合同');
|
||||
assert.match(resources, /暂无可下单的生效合同,请先启用或续签配送合同/, '订单页缺少无可用合同时的引导');
|
||||
assert.match(resources, /暂无可绑定气瓶的草稿合同,请先新建配送合同/, '合同气瓶页缺少无草稿合同时的引导');
|
||||
assert.match(resources, /relationStrictFilter: true/, '合同候选必须启用严格筛选');
|
||||
assert.match(relations, /\.\.\.\(field\.relationFilters \?\? \{\}\)/, '关联请求未携带字段级筛选参数');
|
||||
assert.match(linkage, /clearInvalidStrictSelections/, '刷新后未清理失效合同选择');
|
||||
|
||||
console.log('配送合同候选检查通过:订单仅显示可履约合同,气瓶绑定仅显示草稿合同。');
|
||||
@@ -68,6 +68,12 @@ export type ResourceField = {
|
||||
relationAutoSelectKey?: string;
|
||||
/** 关联候选为空时展示的业务引导。 */
|
||||
relationEmptyText?: string;
|
||||
/** 该关联字段每次加载与搜索都必须携带的精确筛选参数。 */
|
||||
relationFilters?: Record<string, string>;
|
||||
/** 筛选结果中不存在当前值时立即清空,禁止通过详情补载绕过候选范围。 */
|
||||
relationStrictFilter?: boolean;
|
||||
/** 严格筛选清空失效值时展示的提示。 */
|
||||
relationInvalidMessage?: string;
|
||||
staffRelation?: import('./resource-staff-relation').StaffRelationPolicy;
|
||||
};
|
||||
|
||||
@@ -504,6 +510,9 @@ export const resources: ResourceUiDefinition[] = [
|
||||
], { canCreate: true, canEdit: true }),
|
||||
define('gasorder_contract_product', '合同气瓶', 'append_only', [relation('gasorder_contract_identity', '/gasorder_contract', true, {
|
||||
readonlyRelationText: true, displayRelationLabel: true,
|
||||
placeholder: '请选择草稿合同', relationFilters: { candidate: 'binding' }, relationStrictFilter: true,
|
||||
relationInvalidMessage: '所选合同已不是草稿状态,已清空,请重新选择',
|
||||
relationEmptyText: '暂无可绑定气瓶的草稿合同,请先新建配送合同',
|
||||
}), relation('product_info_identity', '/product_info', true, {
|
||||
placeholder: '请先选择合同,再选择该合同用户可绑定的气瓶',
|
||||
relationLinkage: {
|
||||
@@ -514,7 +523,7 @@ export const resources: ResourceUiDefinition[] = [
|
||||
{ name: '解绑气瓶', resource: '/gasorder_contract_product/:identity/unbind', danger: true, fields: reason },
|
||||
]),
|
||||
define('gasorder_contract_revision', '合同修订记录', 'readonly', []),
|
||||
define('gasorder_basic', '气体配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true, { label: '配送合同', placeholder: '请选择配送合同' }), f('creator_type', { required: true, type: 'select', options: resourceSearchEnumOptions('gasorder_basic', 'creator_type') }), f('creator_identity', { required: true }), relation('user_address_identity', '/user_address', true, {
|
||||
define('gasorder_basic', '气体配送订单', 'append_only', [f('request_no', { required: true }), relation('gasorder_contract_identity', '/gasorder_contract', true, { label: '配送合同', placeholder: '请选择当前可履约的生效合同', relationFilters: { candidate: 'order' }, relationStrictFilter: true, relationInvalidMessage: '所选配送合同已不可履约,已清空,请重新选择', relationEmptyText: '暂无可下单的生效合同,请先启用或续签配送合同' }), f('creator_type', { required: true, type: 'select', options: resourceSearchEnumOptions('gasorder_basic', 'creator_type') }), f('creator_identity', { required: true }), relation('user_address_identity', '/user_address', true, {
|
||||
label: '收货地址', placeholder: '请先选择配送合同,再选择该合同用户的收货地址',
|
||||
relationOptionLabelKey: 'address', relationOptionDefaultKey: 'is_default',
|
||||
relationAutoSelectKey: 'is_default', relationEmptyText: '该合同用户暂无收货地址,请先维护用户地址',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* 版本:v1.1.0
|
||||
*/
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { ref } from 'vue';
|
||||
import type { ResourceField } from '@/api/resources';
|
||||
import {
|
||||
linkedRelationFilters,
|
||||
@@ -25,6 +26,7 @@ export function useResourceRelationLinkage(
|
||||
relations: ResourceRelations,
|
||||
) {
|
||||
let active: ActiveLinkage[] = [];
|
||||
const configuredFields = ref<ResourceField[]>([]);
|
||||
let organizationStaffField: ResourceField | undefined;
|
||||
let initialized = false;
|
||||
|
||||
@@ -36,6 +38,7 @@ export function useResourceRelationLinkage(
|
||||
|
||||
/** 收集页面内全部显式联动,允许一个父字段同时驱动多个业务子项。 */
|
||||
function configure(fields: ResourceField[]) {
|
||||
configuredFields.value = fields;
|
||||
active = fields
|
||||
.filter((field) => field.relationLinkage)
|
||||
.map((childField) => ({
|
||||
@@ -147,15 +150,49 @@ export function useResourceRelationLinkage(
|
||||
field.key !== organizationStaffField?.key,
|
||||
),
|
||||
);
|
||||
await relations.ensureValues(fields, form);
|
||||
await relations.ensureValues(
|
||||
fields.filter((field) => !field.relationStrictFilter),
|
||||
form,
|
||||
);
|
||||
await Promise.all(active.map((item) => reloadChildOptions(item)));
|
||||
await reloadStaffOptions();
|
||||
clearInvalidStrictSelections();
|
||||
initialized = true;
|
||||
|
||||
const warning = validationMessage();
|
||||
if (warning) Message.warning(`当前组织归属需要确认:${warning}`);
|
||||
}
|
||||
|
||||
/** 严格候选中不存在当前值时清空,避免补载详情绕过业务筛选。 */
|
||||
function clearInvalidStrictSelections() {
|
||||
for (const field of configuredFields.value) {
|
||||
if (!field.relationStrictFilter || !field.relation) continue;
|
||||
const rawValue = form[field.key];
|
||||
const identities = (Array.isArray(rawValue) ? rawValue : [rawValue])
|
||||
.map(relationIdentity)
|
||||
.filter(Boolean);
|
||||
if (identities.length === 0) continue;
|
||||
const valid = new Set(
|
||||
(relations.options[field.relation] ?? []).map((option) =>
|
||||
relationIdentity(option.identity),
|
||||
),
|
||||
);
|
||||
if (identities.some((identity) => !valid.has(identity))) {
|
||||
form[field.key] = field.type === 'identity-list' ? [] : '';
|
||||
for (const linkageItem of active.filter(
|
||||
(item) => item.childField.relationLinkage?.parentKey === field.key,
|
||||
)) {
|
||||
const childField = linkageItem.childField;
|
||||
form[childField.key] = childField.type === 'identity-list' ? [] : '';
|
||||
relations.clear(childField.relation);
|
||||
}
|
||||
Message.info(
|
||||
field.relationInvalidMessage ?? '所选关联数据已失效,已清空',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleParentChange(
|
||||
linkageItem: ActiveLinkage,
|
||||
parentIdentity: string,
|
||||
@@ -292,19 +329,22 @@ export function useResourceRelationLinkage(
|
||||
|
||||
/** 必填联动候选为空时返回业务引导,用于阻止提交无效表单。 */
|
||||
function unavailableMessage() {
|
||||
for (const linkageItem of active) {
|
||||
const field = linkageItem.childField;
|
||||
for (const field of configuredFields.value) {
|
||||
const linkage = field.relationLinkage;
|
||||
if (
|
||||
!field.required ||
|
||||
!field.relation ||
|
||||
!field.relationEmptyText ||
|
||||
!linkage?.requiresParent ||
|
||||
!relationIdentity(form[linkage.parentKey]) ||
|
||||
relations.loading[field.relation]
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
linkage?.requiresParent &&
|
||||
!relationIdentity(form[linkage.parentKey])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if ((relations.options[field.relation] ?? []).length === 0) {
|
||||
return field.relationEmptyText;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ export function useResourceRelations(
|
||||
return load(field.relation, keyword, {
|
||||
...request,
|
||||
filters: {
|
||||
...(field.relationFilters ?? {}),
|
||||
...staffRelationFilters(field, context()),
|
||||
...(request.filters ?? {}),
|
||||
},
|
||||
@@ -170,6 +171,7 @@ export function useResourceRelations(
|
||||
search(field.relation, keyword, {
|
||||
...request,
|
||||
filters: {
|
||||
...(field.relationFilters ?? {}),
|
||||
...staffRelationFilters(field, context()),
|
||||
...(request.filters ?? {}),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user