刘光辉
8 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import type { DraftField } from './draft';
import type { FillFieldValue, StepFillStructure } from './types';
 
import { parseTag } from './tags';
 
export interface CheckboxGroupViolation {
  checkboxGroupCode: string;
  fields: DraftField[];
  kind: 'invalid' | 'maximum' | 'minimum';
  maxSelected: null | number;
  minSelected: number;
  selectedCount: number;
}
 
export interface CheckboxGroupTransition {
  accepted: boolean;
  changes: Array<{ tag: string; value: FillFieldValue }>;
  message?: string;
}
 
export function checkboxGroupRuntimeIdentity(
  field: DraftField,
  structure?: StepFillStructure,
): string | undefined {
  if (field.type !== 'checkbox' || !field.checkboxGroupCode || field.readOnly) return undefined;
  const parsed = parseTag(field.tag);
  if (!parsed || parsed.type === 'reference') return undefined;
  if (parsed.type === 'repeat') {
    return `repeat:${parsed.groupCode}:${parsed.rowId}:${field.checkboxGroupCode}`;
  }
  const stepId = structure ? structure.controlSteps[field.tag] : '';
  return stepId === undefined ? undefined : `step:${stepId}:${field.checkboxGroupCode}`;
}
 
export function checkboxGroupDeselections(
  fields: DraftField[],
  target: DraftField,
  value: FillFieldValue,
  structure?: StepFillStructure,
): Array<{ tag: string; value: false }> {
  return checkboxGroupTransition(fields, target, value, structure).changes
    .filter((change): change is { tag: string; value: false } => change.tag !== target.tag && change.value === false);
}
 
export function checkboxGroupTransition(
  fields: DraftField[],
  target: DraftField,
  value: FillFieldValue,
  structure?: StepFillStructure,
): CheckboxGroupTransition {
  const targetChange = { tag: target.tag, value };
  if (target.type !== 'checkbox' || typeof value !== 'boolean') {
    return { accepted: true, changes: [targetChange] };
  }
  const identity = checkboxGroupRuntimeIdentity(target, structure);
  if (!identity || !target.checkboxGroupCode || value === false) {
    return { accepted: true, changes: [targetChange] };
  }
  const members = fields.filter((field) => checkboxGroupRuntimeIdentity(field, structure) === identity);
  const rule = checkboxGroupRule(members);
  if (!rule.valid) {
    return {
      accepted: false,
      changes: [],
      message: `选择组“${target.checkboxGroupCode}”数量规则无效`,
    };
  }
  const selectedPeers = members.filter((field) => field.tag !== target.tag && field.draftValue === true);
  if (rule.maxSelected === 1) {
    return {
      accepted: true,
      changes: [targetChange, ...selectedPeers.map((field) => ({ tag: field.tag, value: false as const }))],
    };
  }
  if (rule.maxSelected !== null && selectedPeers.length + 1 > rule.maxSelected) {
    return {
      accepted: false,
      changes: [],
      message: `选择组“${target.checkboxGroupCode}”最多选择 ${rule.maxSelected} 项`,
    };
  }
  return { accepted: true, changes: [targetChange] };
}
 
export function checkboxGroupViolations(
  fields: DraftField[],
  structure?: StepFillStructure,
): CheckboxGroupViolation[] {
  const groups = new Map<string, DraftField[]>();
  for (const field of fields) {
    const identity = checkboxGroupRuntimeIdentity(field, structure);
    if (!identity || !field.checkboxGroupCode) continue;
    groups.set(identity, [...(groups.get(identity) ?? []), field]);
  }
  const violations: CheckboxGroupViolation[] = [];
  for (const members of groups.values()) {
    const rule = checkboxGroupRule(members);
    const selected = members.filter((field) => field.draftValue === true);
    const common = {
      checkboxGroupCode: members[0]!.checkboxGroupCode!,
      fields: selected,
      maxSelected: rule.maxSelected,
      minSelected: rule.minSelected,
      selectedCount: selected.length,
    };
    if (!rule.valid) violations.push({ ...common, kind: 'invalid' });
    else if (selected.length < rule.minSelected) violations.push({ ...common, kind: 'minimum' });
    else if (rule.maxSelected !== null && selected.length > rule.maxSelected) {
      violations.push({ ...common, kind: 'maximum' });
    }
  }
  return violations;
}
 
function checkboxGroupRule(fields: DraftField[]): {
  maxSelected: null | number;
  minSelected: number;
  valid: boolean;
} {
  const first = fields[0];
  const minSelected = first?.checkboxGroupMinSelected ?? 0;
  const maxSelected = first?.checkboxGroupMaxSelected === undefined
    ? 1
    : first.checkboxGroupMaxSelected;
  const validRange = Number.isInteger(minSelected) && minSelected >= 0
    && (maxSelected === null
      || (Number.isInteger(maxSelected) && maxSelected >= 1 && minSelected <= maxSelected));
  const consistent = fields.every((field) =>
    (field.checkboxGroupMinSelected ?? 0) === minSelected
    && (field.checkboxGroupMaxSelected === undefined ? 1 : field.checkboxGroupMaxSelected) === maxSelected);
  return { maxSelected, minSelected, valid: validRange && consistent };
}