口径(用户确认): - 个人分按个人成绩排名给,**不计入单位总分**(下沉到通道级开关,不再动全局系数) - 团体成绩 = 每队成员成绩的**平均值** → 排名(并列顺延)→ 团体模板给分 → 计入单位总分 - 平均值口径是**小项级单选项**:默认「只算有效成绩」;「全部报名人员计入」= 分母为报名条数、 没成绩按 0 加入分子;在**成绩录入页**就地切换并立即保存 服务端: - 迁移 0017:score_ledger 加 count_to_team(重建,无子表);event_score_channels 加 count_to_team; events 加 avg_mode(默认 valid) - 规则包 Channel 加 countToTeam(缺省 true);channelTemplates 支持 individual_team → 默认 athlete(result, 不计入单位) + team(aggregate 队内平均) - 计分引擎:SumByTeam 跳过 count_to_team=0 的流水;实现 basis=aggregate 的队内平均团体排名 (valid 分母=有有效成绩人数;all 分母=报名人数;某队 0 有效成绩不参与) 前端: - 计分通道页:每行通道「计入单位总分」勾选 + basis 下拉「按队内平均」 - 成绩录入页:仅有队内平均通道的小项显示「平均值口径」单选(默认只算有效成绩),改动即存 - 录入页「实时积分预览」按新口径对齐(原先按主体求和且双通道重复计入,与发布后不一致) - 大屏/管理端团体成绩只显示数值,不出现「平均」字样 自检:服务端 12 包全绿;前端 vue-tsc + build、node --test 134/134;console 16 文件通过。
34 lines
1.7 KiB
TypeScript
34 lines
1.7 KiB
TypeScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { parseEvent, parseScoreChannel } from '../guards'
|
|
|
|
// 大屏快照里的 avgMode / countToTeam 是后加的字段:旧服务端不下发时必须有默认值,
|
|
// 而 basis='aggregate'(新口径「按队内平均」)不能被当成未知取值回落到 result。
|
|
test('parseEvent 缺 avgMode 时按 valid 处理', () => {
|
|
assert.equal(parseEvent({ id: 1, code: 'TPULL', eventType: 'individual_team' }).avgMode, 'valid')
|
|
assert.equal(parseEvent({ id: 1, avgMode: 'all' }).avgMode, 'all')
|
|
assert.equal(parseEvent({ id: 1, avgMode: 'AVG' }).avgMode, 'valid')
|
|
})
|
|
|
|
test('parseScoreChannel 保留 aggregate 并按 true 补 countToTeam', () => {
|
|
const channel = parseScoreChannel({ id: 3, eventId: 1, scope: 'team', basis: 'aggregate', profileId: 2, sortOrder: 1 })
|
|
assert.equal(channel.basis, 'aggregate')
|
|
assert.equal(channel.countToTeam, true)
|
|
|
|
const personal = parseScoreChannel({ id: 4, eventId: 1, scope: 'athlete', basis: 'result', profileId: 1, countToTeam: false })
|
|
assert.equal(personal.countToTeam, false)
|
|
// 未知 basis 仍回落到 result,避免渲染出不存在的口径。
|
|
assert.equal(parseScoreChannel({ basis: 'sum' }).basis, 'result')
|
|
})
|
|
|
|
test('parseEvent 同时解析详情接口带出的通道', () => {
|
|
const event = parseEvent({
|
|
id: 1,
|
|
channels: [{ id: 1, scope: 'athlete', basis: 'result', profileId: 1, countToTeam: false }],
|
|
})
|
|
assert.equal(event.channels?.length, 1)
|
|
assert.equal(event.channels?.[0]?.countToTeam, false)
|
|
// 列表接口不下发 channels 时保持 undefined,不能凭空造出空数组。
|
|
assert.equal(parseEvent({ id: 1 }).channels, undefined)
|
|
})
|