volleyballsimtypes 0.0.426 → 0.0.428
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/api/index.d.ts +9 -0
- package/dist/cjs/src/data/init-models.js +6 -0
- package/dist/cjs/src/data/models/index.d.ts +1 -0
- package/dist/cjs/src/data/models/index.js +1 -0
- package/dist/cjs/src/data/models/lineup-preset.d.ts +29 -0
- package/dist/cjs/src/data/models/lineup-preset.js +71 -0
- package/dist/cjs/src/data/models/tactics.d.ts +2 -0
- package/dist/cjs/src/data/transformers/index.d.ts +1 -0
- package/dist/cjs/src/data/transformers/index.js +1 -0
- package/dist/cjs/src/data/transformers/lineup-preset.d.ts +4 -0
- package/dist/cjs/src/data/transformers/lineup-preset.js +13 -0
- package/dist/cjs/src/data/transformers/lineup-preset.test.d.ts +1 -0
- package/dist/cjs/src/data/transformers/lineup-preset.test.js +25 -0
- package/dist/cjs/src/data/transformers/tactics.d.ts +3 -1
- package/dist/cjs/src/data/transformers/tactics.js +42 -0
- package/dist/cjs/src/data/transformers/tactics.test.d.ts +1 -0
- package/dist/cjs/src/data/transformers/tactics.test.js +66 -0
- package/dist/cjs/src/service/team/designated-sub.d.ts +1 -0
- package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +3 -0
- package/dist/cjs/src/service/team/schemas/designated-sub.z.js +1 -0
- package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +19 -0
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +2 -0
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +2 -0
- package/dist/esm/src/api/index.d.ts +9 -0
- package/dist/esm/src/data/init-models.js +7 -1
- package/dist/esm/src/data/models/index.d.ts +1 -0
- package/dist/esm/src/data/models/index.js +1 -0
- package/dist/esm/src/data/models/lineup-preset.d.ts +29 -0
- package/dist/esm/src/data/models/lineup-preset.js +67 -0
- package/dist/esm/src/data/models/tactics.d.ts +2 -0
- package/dist/esm/src/data/transformers/index.d.ts +1 -0
- package/dist/esm/src/data/transformers/index.js +1 -0
- package/dist/esm/src/data/transformers/lineup-preset.d.ts +4 -0
- package/dist/esm/src/data/transformers/lineup-preset.js +11 -0
- package/dist/esm/src/data/transformers/lineup-preset.test.d.ts +1 -0
- package/dist/esm/src/data/transformers/lineup-preset.test.js +23 -0
- package/dist/esm/src/data/transformers/tactics.d.ts +3 -1
- package/dist/esm/src/data/transformers/tactics.js +42 -1
- package/dist/esm/src/data/transformers/tactics.test.d.ts +1 -0
- package/dist/esm/src/data/transformers/tactics.test.js +64 -0
- package/dist/esm/src/service/team/designated-sub.d.ts +1 -0
- package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +3 -0
- package/dist/esm/src/service/team/schemas/designated-sub.z.js +1 -0
- package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +19 -0
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +2 -0
- package/dist/esm/src/service/team/schemas/team.z.d.ts +2 -0
- package/package.json +1 -1
|
@@ -74,6 +74,7 @@ export interface ApiSetSubConfig {
|
|
|
74
74
|
mode: SubMode;
|
|
75
75
|
benchId?: string;
|
|
76
76
|
fatigueBand?: EnergyBand;
|
|
77
|
+
benchFatigueBand?: EnergyBand;
|
|
77
78
|
conditions?: PinchCondition[];
|
|
78
79
|
conditionLogic?: ConditionLogic;
|
|
79
80
|
}
|
|
@@ -82,6 +83,7 @@ export interface ApiDesignatedSub {
|
|
|
82
83
|
benchId?: string;
|
|
83
84
|
mode: SubMode;
|
|
84
85
|
fatigueBand?: EnergyBand;
|
|
86
|
+
benchFatigueBand?: EnergyBand;
|
|
85
87
|
conditions?: PinchCondition[];
|
|
86
88
|
conditionLogic?: ConditionLogic;
|
|
87
89
|
sets?: ApiSetSubConfig[];
|
|
@@ -112,6 +114,13 @@ export interface Tactics {
|
|
|
112
114
|
designatedSetters: string[];
|
|
113
115
|
offensivePreferences: ApiOffensivePreference[];
|
|
114
116
|
}
|
|
117
|
+
export interface ApiLineupPreset {
|
|
118
|
+
presetId: string;
|
|
119
|
+
name: string;
|
|
120
|
+
isActive: boolean;
|
|
121
|
+
orderIndex: number;
|
|
122
|
+
config: Tactics;
|
|
123
|
+
}
|
|
115
124
|
export type Team = Omit<DataProps<_Team>, 'roster' | '_rating' | 'rating' | 'tactics'> & {
|
|
116
125
|
roster: Player[];
|
|
117
126
|
rating: {
|
|
@@ -7,6 +7,7 @@ function initModels(sequelize) {
|
|
|
7
7
|
const Scout = models_1.ScoutModel.initModel(sequelize);
|
|
8
8
|
const CurrencyTransaction = models_1.CurrencyTransactionModel.initModel(sequelize);
|
|
9
9
|
const Tactics = models_1.TacticsModel.initModel(sequelize);
|
|
10
|
+
const LineupPreset = models_1.LineupPresetModel.initModel(sequelize);
|
|
10
11
|
const AuthUser = models_1.AuthUserModel.initModel(sequelize);
|
|
11
12
|
const UserCurrency = models_1.UserCurrencyModel.initModel(sequelize);
|
|
12
13
|
const UserPlayerProgress = models_1.UserPlayerProgressModel.initModel(sequelize);
|
|
@@ -199,6 +200,10 @@ function initModels(sequelize) {
|
|
|
199
200
|
Team.hasOne(Scout, { as: 'Scout', foreignKey: 'team_id' });
|
|
200
201
|
Scout.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
201
202
|
Team.hasOne(Tactics, { as: 'tactics', foreignKey: 'team_id' });
|
|
203
|
+
// Lineup presets are loaded only by the management API; deliberately NOT in Team.defaultScope so the
|
|
204
|
+
// simulator's team loads are unchanged (it reads the active config from the mirrored Tactics row).
|
|
205
|
+
Team.hasMany(LineupPreset, { as: 'lineupPresets', foreignKey: 'team_id' });
|
|
206
|
+
LineupPreset.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
202
207
|
Team.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
|
|
203
208
|
Team.belongsToMany(Competition, {
|
|
204
209
|
as: 'Competitions',
|
|
@@ -270,6 +275,7 @@ function initModels(sequelize) {
|
|
|
270
275
|
Team,
|
|
271
276
|
TeamReplacement,
|
|
272
277
|
Tactics,
|
|
278
|
+
LineupPreset,
|
|
273
279
|
UserTeam,
|
|
274
280
|
UserCurrency,
|
|
275
281
|
UserPlayerProgress,
|
|
@@ -33,6 +33,7 @@ export * from './scout';
|
|
|
33
33
|
export * from './team';
|
|
34
34
|
export * from './team-replacement';
|
|
35
35
|
export * from './tactics';
|
|
36
|
+
export * from './lineup-preset';
|
|
36
37
|
export * from './user-currency';
|
|
37
38
|
export * from './user-player-progress';
|
|
38
39
|
export * from './gacha-pity';
|
|
@@ -49,6 +49,7 @@ __exportStar(require("./scout"), exports);
|
|
|
49
49
|
__exportStar(require("./team"), exports);
|
|
50
50
|
__exportStar(require("./team-replacement"), exports);
|
|
51
51
|
__exportStar(require("./tactics"), exports);
|
|
52
|
+
__exportStar(require("./lineup-preset"), exports);
|
|
52
53
|
__exportStar(require("./user-currency"), exports);
|
|
53
54
|
__exportStar(require("./user-player-progress"), exports);
|
|
54
55
|
__exportStar(require("./gacha-pity"), exports);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model, Optional } from 'sequelize';
|
|
3
|
+
import { TeamId, TeamModel } from '.';
|
|
4
|
+
import type { Tactics as ApiTactics } from '../../api';
|
|
5
|
+
export interface LineupPresetAttributes {
|
|
6
|
+
preset_id: string;
|
|
7
|
+
team_id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
is_active: boolean;
|
|
10
|
+
order_index: number;
|
|
11
|
+
config: ApiTactics;
|
|
12
|
+
}
|
|
13
|
+
export type LineupPresetPk = 'preset_id';
|
|
14
|
+
export type LineupPresetId = LineupPresetModel[LineupPresetPk];
|
|
15
|
+
export type LineupPresetOptionalAttributes = 'preset_id' | 'is_active';
|
|
16
|
+
export type LineupPresetCreationAttributes = Optional<LineupPresetAttributes, LineupPresetOptionalAttributes>;
|
|
17
|
+
export declare class LineupPresetModel extends Model<LineupPresetAttributes, LineupPresetCreationAttributes> implements LineupPresetAttributes {
|
|
18
|
+
preset_id: string;
|
|
19
|
+
team_id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
is_active: boolean;
|
|
22
|
+
order_index: number;
|
|
23
|
+
config: ApiTactics;
|
|
24
|
+
team: TeamModel;
|
|
25
|
+
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
26
|
+
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
27
|
+
createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
|
|
28
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof LineupPresetModel;
|
|
29
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LineupPresetModel = void 0;
|
|
4
|
+
const sequelize_1 = require("sequelize");
|
|
5
|
+
class LineupPresetModel extends sequelize_1.Model {
|
|
6
|
+
static initModel(sequelize) {
|
|
7
|
+
return LineupPresetModel.init({
|
|
8
|
+
preset_id: {
|
|
9
|
+
type: sequelize_1.DataTypes.UUID,
|
|
10
|
+
allowNull: false,
|
|
11
|
+
primaryKey: true,
|
|
12
|
+
defaultValue: sequelize_1.DataTypes.UUIDV4
|
|
13
|
+
},
|
|
14
|
+
team_id: {
|
|
15
|
+
type: sequelize_1.DataTypes.UUID,
|
|
16
|
+
allowNull: false,
|
|
17
|
+
references: {
|
|
18
|
+
model: 'Team',
|
|
19
|
+
key: 'team_id'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
name: {
|
|
23
|
+
type: sequelize_1.DataTypes.STRING,
|
|
24
|
+
allowNull: false
|
|
25
|
+
},
|
|
26
|
+
is_active: {
|
|
27
|
+
type: sequelize_1.DataTypes.BOOLEAN,
|
|
28
|
+
allowNull: false,
|
|
29
|
+
defaultValue: false
|
|
30
|
+
},
|
|
31
|
+
order_index: {
|
|
32
|
+
type: sequelize_1.DataTypes.INTEGER,
|
|
33
|
+
allowNull: false
|
|
34
|
+
},
|
|
35
|
+
config: {
|
|
36
|
+
type: sequelize_1.DataTypes.JSONB,
|
|
37
|
+
allowNull: false
|
|
38
|
+
}
|
|
39
|
+
}, {
|
|
40
|
+
sequelize,
|
|
41
|
+
tableName: 'LineupPreset',
|
|
42
|
+
schema: 'public',
|
|
43
|
+
timestamps: false,
|
|
44
|
+
indexes: [
|
|
45
|
+
{
|
|
46
|
+
name: 'LineupPreset_pk',
|
|
47
|
+
unique: true,
|
|
48
|
+
fields: [
|
|
49
|
+
{ name: 'preset_id' }
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'LineupPreset_team_id_idx',
|
|
54
|
+
fields: [
|
|
55
|
+
{ name: 'team_id' }
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
// At most one active preset per team.
|
|
60
|
+
name: 'LineupPreset_one_active_per_team',
|
|
61
|
+
unique: true,
|
|
62
|
+
fields: [
|
|
63
|
+
{ name: 'team_id' }
|
|
64
|
+
],
|
|
65
|
+
where: { is_active: true }
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.LineupPresetModel = LineupPresetModel;
|
|
@@ -17,6 +17,7 @@ export interface SetSubConfigAttributes {
|
|
|
17
17
|
mode: SubMode;
|
|
18
18
|
benchId?: PlayerId;
|
|
19
19
|
fatigueBand?: EnergyBand;
|
|
20
|
+
benchFatigueBand?: EnergyBand;
|
|
20
21
|
conditions?: PinchCondition[];
|
|
21
22
|
conditionLogic?: ConditionLogic;
|
|
22
23
|
}
|
|
@@ -26,6 +27,7 @@ export interface DesignatedSubAttributes {
|
|
|
26
27
|
mode?: SubMode;
|
|
27
28
|
isPinchServer?: boolean;
|
|
28
29
|
fatigueBand?: SubBand;
|
|
30
|
+
benchFatigueBand?: EnergyBand;
|
|
29
31
|
conditions?: PinchCondition[];
|
|
30
32
|
conditionLogic?: ConditionLogic;
|
|
31
33
|
sets?: SetSubConfigAttributes[];
|
|
@@ -42,4 +42,5 @@ __exportStar(require("./national"), exports);
|
|
|
42
42
|
__exportStar(require("./national-match"), exports);
|
|
43
43
|
__exportStar(require("./team"), exports);
|
|
44
44
|
__exportStar(require("./tactics"), exports);
|
|
45
|
+
__exportStar(require("./lineup-preset"), exports);
|
|
45
46
|
__exportStar(require("./vper"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformToLineupPreset = transformToObject;
|
|
4
|
+
// `config` is already stored as the API Tactics DTO, so a preset row maps to its API shape with no hydration.
|
|
5
|
+
function transformToObject(model) {
|
|
6
|
+
return {
|
|
7
|
+
presetId: model.preset_id,
|
|
8
|
+
name: model.name,
|
|
9
|
+
isActive: model.is_active,
|
|
10
|
+
orderIndex: model.order_index,
|
|
11
|
+
config: model.config
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const lineup_preset_1 = require("./lineup-preset");
|
|
5
|
+
(0, globals_1.describe)('transformToLineupPreset()', () => {
|
|
6
|
+
(0, globals_1.it)('maps preset columns to the API shape and passes config through by reference', () => {
|
|
7
|
+
const config = { lineup: { bench: [] }, substitutionBand: 'TIRED' };
|
|
8
|
+
const preset = (0, lineup_preset_1.transformToLineupPreset)({
|
|
9
|
+
preset_id: 'p1',
|
|
10
|
+
team_id: 't1',
|
|
11
|
+
name: 'Starters',
|
|
12
|
+
is_active: true,
|
|
13
|
+
order_index: 2,
|
|
14
|
+
config
|
|
15
|
+
});
|
|
16
|
+
(0, globals_1.expect)(preset).toEqual({
|
|
17
|
+
presetId: 'p1',
|
|
18
|
+
name: 'Starters',
|
|
19
|
+
isActive: true,
|
|
20
|
+
orderIndex: 2,
|
|
21
|
+
config
|
|
22
|
+
});
|
|
23
|
+
(0, globals_1.expect)(preset.config).toBe(config);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { TacticsAttributes, TacticsModel } from '../models';
|
|
2
2
|
import { Player, Tactics } from '../../service';
|
|
3
|
+
import type { Tactics as ApiTactics } from '../../api';
|
|
3
4
|
declare function transformToAttributes(tactics: Tactics, teamId: string): TacticsAttributes;
|
|
4
5
|
declare function transformToObject(model: TacticsModel, roster: Player[]): Tactics;
|
|
5
|
-
|
|
6
|
+
declare function tacticsColumnsToApiDto(model: TacticsModel): ApiTactics;
|
|
7
|
+
export { transformToObject as transformToTactics, transformToAttributes as transformFromTactics, tacticsColumnsToApiDto };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.transformToTactics = transformToObject;
|
|
4
4
|
exports.transformFromTactics = transformToAttributes;
|
|
5
|
+
exports.tacticsColumnsToApiDto = tacticsColumnsToApiDto;
|
|
5
6
|
const service_1 = require("../../service");
|
|
6
7
|
// Back-compat: new rows persist `mode`; rows written before the mode change persist `isPinchServer` (+ a
|
|
7
8
|
// 'NEVER' fatigueBand for the old "never fatigue-sub" state). Derive the mode so existing data keeps working
|
|
@@ -62,12 +63,14 @@ function transformToAttributes(tactics, teamId) {
|
|
|
62
63
|
benchId: ds.bench?.id,
|
|
63
64
|
mode: ds.mode,
|
|
64
65
|
fatigueBand: ds.fatigueBand,
|
|
66
|
+
benchFatigueBand: ds.benchFatigueBand,
|
|
65
67
|
conditions: ds.conditions,
|
|
66
68
|
conditionLogic: ds.conditionLogic,
|
|
67
69
|
sets: ds.sets?.map(s => ({
|
|
68
70
|
mode: s.mode,
|
|
69
71
|
benchId: s.bench?.id,
|
|
70
72
|
fatigueBand: s.fatigueBand,
|
|
73
|
+
benchFatigueBand: s.benchFatigueBand,
|
|
71
74
|
conditions: s.conditions,
|
|
72
75
|
conditionLogic: s.conditionLogic
|
|
73
76
|
}))
|
|
@@ -115,12 +118,14 @@ function transformToObject(model, roster) {
|
|
|
115
118
|
bench: d.benchId != null ? findPlayer(d.benchId, roster) : undefined,
|
|
116
119
|
mode: subModeFromAttributes(d),
|
|
117
120
|
fatigueBand: d.fatigueBand === 'NEVER' ? undefined : d.fatigueBand,
|
|
121
|
+
benchFatigueBand: d.benchFatigueBand,
|
|
118
122
|
conditions: d.conditions,
|
|
119
123
|
conditionLogic: d.conditionLogic,
|
|
120
124
|
sets: d.sets?.map(s => ({
|
|
121
125
|
mode: s.mode,
|
|
122
126
|
bench: s.benchId != null ? findPlayer(s.benchId, roster) : undefined,
|
|
123
127
|
fatigueBand: s.fatigueBand,
|
|
128
|
+
benchFatigueBand: s.benchFatigueBand,
|
|
124
129
|
conditions: s.conditions,
|
|
125
130
|
conditionLogic: s.conditionLogic
|
|
126
131
|
}))
|
|
@@ -133,3 +138,40 @@ function transformToObject(model, roster) {
|
|
|
133
138
|
}))
|
|
134
139
|
});
|
|
135
140
|
}
|
|
141
|
+
// Serialize a Tactics ROW (db columns) straight to the API Tactics DTO (player references stay as ids; no
|
|
142
|
+
// roster needed). Used to seed a lineup preset's `config` from the existing single tactics row (backfill) and
|
|
143
|
+
// to keep the active preset and the mirrored Tactics row in the same shape. Applies the same legacy fixups as
|
|
144
|
+
// the service transformer: resolve the designated-sub `mode` and drop a legacy 'NEVER' fatigueBand.
|
|
145
|
+
function tacticsColumnsToApiDto(model) {
|
|
146
|
+
return {
|
|
147
|
+
lineup: model.lineup,
|
|
148
|
+
substitutionTolerance: model.substitution_tolerance,
|
|
149
|
+
liberoReplacements: model.libero_replacements,
|
|
150
|
+
pinchServerSubs: model.pinch_server_subs,
|
|
151
|
+
receiveRotationOffset: model.receive_rotation_offset,
|
|
152
|
+
substitutionBand: model.substitution_band,
|
|
153
|
+
secondLibero: model.second_libero,
|
|
154
|
+
liberoSub: model.libero_sub,
|
|
155
|
+
liberoReplacementSets: model.libero_replacement_sets,
|
|
156
|
+
designatedSubs: (model.designated_subs ?? []).map(d => ({
|
|
157
|
+
starterId: d.starterId,
|
|
158
|
+
benchId: d.benchId,
|
|
159
|
+
mode: subModeFromAttributes(d),
|
|
160
|
+
fatigueBand: d.fatigueBand === 'NEVER' ? undefined : d.fatigueBand,
|
|
161
|
+
benchFatigueBand: d.benchFatigueBand,
|
|
162
|
+
conditions: d.conditions,
|
|
163
|
+
conditionLogic: d.conditionLogic,
|
|
164
|
+
sets: d.sets?.map(s => ({
|
|
165
|
+
mode: s.mode,
|
|
166
|
+
benchId: s.benchId,
|
|
167
|
+
fatigueBand: s.fatigueBand,
|
|
168
|
+
benchFatigueBand: s.benchFatigueBand,
|
|
169
|
+
conditions: s.conditions,
|
|
170
|
+
conditionLogic: s.conditionLogic
|
|
171
|
+
}))
|
|
172
|
+
})),
|
|
173
|
+
rotationSystem: model.rotation_system,
|
|
174
|
+
designatedSetters: model.designated_setters,
|
|
175
|
+
offensivePreferences: model.offensive_preferences
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globals_1 = require("@jest/globals");
|
|
4
|
+
const tactics_1 = require("./tactics");
|
|
5
|
+
const service_1 = require("../../service");
|
|
6
|
+
// Minimal TacticsModel-shaped object; cast through unknown since the transformer only reads columns.
|
|
7
|
+
function makeTactics(designatedSubs) {
|
|
8
|
+
return {
|
|
9
|
+
team_id: 'team-1',
|
|
10
|
+
substitution_tolerance: 10,
|
|
11
|
+
lineup: {
|
|
12
|
+
[service_1.CourtPosition.LEFT_FRONT]: 'lf',
|
|
13
|
+
[service_1.CourtPosition.MIDDLE_FRONT]: 'mf',
|
|
14
|
+
[service_1.CourtPosition.RIGHT_FRONT]: 'rf',
|
|
15
|
+
[service_1.CourtPosition.LEFT_BACK]: 'lb',
|
|
16
|
+
[service_1.CourtPosition.MIDDLE_BACK]: 'mb',
|
|
17
|
+
[service_1.CourtPosition.RIGHT_BACK]: 'rb',
|
|
18
|
+
bench: ['b1', 'b2']
|
|
19
|
+
},
|
|
20
|
+
libero_replacements: ['lf'],
|
|
21
|
+
pinch_server_subs: { starter1: 'server1' },
|
|
22
|
+
receive_rotation_offset: true,
|
|
23
|
+
designated_subs: designatedSubs,
|
|
24
|
+
substitution_band: service_1.EnergyBand.TIRED,
|
|
25
|
+
second_libero: undefined,
|
|
26
|
+
libero_sub: undefined,
|
|
27
|
+
libero_replacement_sets: undefined,
|
|
28
|
+
rotation_system: '5-1',
|
|
29
|
+
designated_setters: ['lf'],
|
|
30
|
+
offensive_preferences: [{ rotation: 1, order: ['lf', 'rf'] }]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
(0, globals_1.describe)('tacticsColumnsToApiDto()', () => {
|
|
34
|
+
(0, globals_1.it)('passes scalar/array columns through to the API DTO shape', () => {
|
|
35
|
+
const dto = (0, tactics_1.tacticsColumnsToApiDto)(makeTactics([]));
|
|
36
|
+
(0, globals_1.expect)(dto.substitutionTolerance).toBe(10);
|
|
37
|
+
(0, globals_1.expect)(dto.receiveRotationOffset).toBe(true);
|
|
38
|
+
(0, globals_1.expect)(dto.substitutionBand).toBe(service_1.EnergyBand.TIRED);
|
|
39
|
+
(0, globals_1.expect)(dto.rotationSystem).toBe('5-1');
|
|
40
|
+
(0, globals_1.expect)(dto.liberoReplacements).toEqual(['lf']);
|
|
41
|
+
(0, globals_1.expect)(dto.pinchServerSubs).toEqual({ starter1: 'server1' });
|
|
42
|
+
(0, globals_1.expect)(dto.lineup.bench).toEqual(['b1', 'b2']);
|
|
43
|
+
(0, globals_1.expect)(dto.offensivePreferences).toEqual([{ rotation: 1, order: ['lf', 'rf'] }]);
|
|
44
|
+
});
|
|
45
|
+
(0, globals_1.it)("resolves a legacy isPinchServer entry to mode 'PINCH'", () => {
|
|
46
|
+
const dto = (0, tactics_1.tacticsColumnsToApiDto)(makeTactics([
|
|
47
|
+
{ starterId: 's1', benchId: 'b1', isPinchServer: true, conditions: [{ type: 'ASAP' }] }
|
|
48
|
+
]));
|
|
49
|
+
(0, globals_1.expect)(dto.designatedSubs[0].mode).toBe('PINCH');
|
|
50
|
+
});
|
|
51
|
+
(0, globals_1.it)("resolves a legacy 'NEVER' fatigueBand to mode NEVER and drops the band", () => {
|
|
52
|
+
const dto = (0, tactics_1.tacticsColumnsToApiDto)(makeTactics([
|
|
53
|
+
{ starterId: 's1', fatigueBand: 'NEVER' }
|
|
54
|
+
]));
|
|
55
|
+
(0, globals_1.expect)(dto.designatedSubs[0].mode).toBe('NEVER');
|
|
56
|
+
(0, globals_1.expect)(dto.designatedSubs[0].fatigueBand).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
(0, globals_1.it)('passes a fatigue entry with its own bands through unchanged', () => {
|
|
59
|
+
const dto = (0, tactics_1.tacticsColumnsToApiDto)(makeTactics([
|
|
60
|
+
{ starterId: 's1', benchId: 'b1', mode: 'FATIGUE', fatigueBand: service_1.EnergyBand.WORN, benchFatigueBand: service_1.EnergyBand.TIRED }
|
|
61
|
+
]));
|
|
62
|
+
(0, globals_1.expect)(dto.designatedSubs[0].mode).toBe('FATIGUE');
|
|
63
|
+
(0, globals_1.expect)(dto.designatedSubs[0].fatigueBand).toBe(service_1.EnergyBand.WORN);
|
|
64
|
+
(0, globals_1.expect)(dto.designatedSubs[0].benchFatigueBand).toBe(service_1.EnergyBand.TIRED);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -43,6 +43,7 @@ export declare const SetSubConfigSchema: z.ZodObject<{
|
|
|
43
43
|
}>;
|
|
44
44
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
45
45
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
46
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
46
47
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
47
48
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
48
49
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -80,6 +81,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
80
81
|
}>;
|
|
81
82
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
82
83
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
84
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
83
85
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
84
86
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
85
87
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -115,6 +117,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
115
117
|
}>;
|
|
116
118
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
117
119
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
120
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
118
121
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
119
122
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
120
123
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -26,6 +26,7 @@ const setSubConfigShape = {
|
|
|
26
26
|
mode: exports.SubModeSchema,
|
|
27
27
|
bench: playerInstanceSchema.optional(),
|
|
28
28
|
fatigueBand: zod_1.z.nativeEnum(energy_band_1.EnergyBand).optional(),
|
|
29
|
+
benchFatigueBand: zod_1.z.nativeEnum(energy_band_1.EnergyBand).optional(),
|
|
29
30
|
conditions: zod_1.z.array(exports.PinchConditionSchema).optional(),
|
|
30
31
|
conditionLogic: exports.ConditionLogicSchema.optional()
|
|
31
32
|
};
|
|
@@ -73,6 +73,25 @@ const test_helpers_1 = require("../../test-helpers");
|
|
|
73
73
|
});
|
|
74
74
|
(0, globals_1.expect)(res.success).toBe(true);
|
|
75
75
|
});
|
|
76
|
+
(0, globals_1.it)("accepts a fatigue-mode entry with the substitute's own benchFatigueBand", () => {
|
|
77
|
+
const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
|
|
78
|
+
starter,
|
|
79
|
+
bench,
|
|
80
|
+
mode: 'FATIGUE',
|
|
81
|
+
fatigueBand: energy_band_1.EnergyBand.WORN,
|
|
82
|
+
benchFatigueBand: energy_band_1.EnergyBand.TIRED
|
|
83
|
+
});
|
|
84
|
+
(0, globals_1.expect)(res.success).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
(0, globals_1.it)('rejects benchFatigueBand = NEVER (only the team default band may be NEVER)', () => {
|
|
87
|
+
const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
|
|
88
|
+
starter,
|
|
89
|
+
bench,
|
|
90
|
+
mode: 'FATIGUE',
|
|
91
|
+
benchFatigueBand: 'NEVER'
|
|
92
|
+
});
|
|
93
|
+
(0, globals_1.expect)(res.success).toBe(false);
|
|
94
|
+
});
|
|
76
95
|
(0, globals_1.it)('accepts a fatigue-mode entry with no bench (falls back to closest sub)', () => {
|
|
77
96
|
const res = designated_sub_z_1.DesignatedSubSchema.safeParse({
|
|
78
97
|
starter,
|
|
@@ -26,6 +26,7 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
26
26
|
}>;
|
|
27
27
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
28
28
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
29
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
29
30
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
31
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
31
32
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -61,6 +62,7 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
61
62
|
}>;
|
|
62
63
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
63
64
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
65
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
64
66
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
65
67
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
66
68
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -34,6 +34,7 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
34
34
|
}>;
|
|
35
35
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
36
36
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
37
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
37
38
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
38
39
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
39
40
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -69,6 +70,7 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
69
70
|
}>;
|
|
70
71
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
71
72
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
73
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
72
74
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
73
75
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
74
76
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -74,6 +74,7 @@ export interface ApiSetSubConfig {
|
|
|
74
74
|
mode: SubMode;
|
|
75
75
|
benchId?: string;
|
|
76
76
|
fatigueBand?: EnergyBand;
|
|
77
|
+
benchFatigueBand?: EnergyBand;
|
|
77
78
|
conditions?: PinchCondition[];
|
|
78
79
|
conditionLogic?: ConditionLogic;
|
|
79
80
|
}
|
|
@@ -82,6 +83,7 @@ export interface ApiDesignatedSub {
|
|
|
82
83
|
benchId?: string;
|
|
83
84
|
mode: SubMode;
|
|
84
85
|
fatigueBand?: EnergyBand;
|
|
86
|
+
benchFatigueBand?: EnergyBand;
|
|
85
87
|
conditions?: PinchCondition[];
|
|
86
88
|
conditionLogic?: ConditionLogic;
|
|
87
89
|
sets?: ApiSetSubConfig[];
|
|
@@ -112,6 +114,13 @@ export interface Tactics {
|
|
|
112
114
|
designatedSetters: string[];
|
|
113
115
|
offensivePreferences: ApiOffensivePreference[];
|
|
114
116
|
}
|
|
117
|
+
export interface ApiLineupPreset {
|
|
118
|
+
presetId: string;
|
|
119
|
+
name: string;
|
|
120
|
+
isActive: boolean;
|
|
121
|
+
orderIndex: number;
|
|
122
|
+
config: Tactics;
|
|
123
|
+
}
|
|
115
124
|
export type Team = Omit<DataProps<_Team>, 'roster' | '_rating' | 'rating' | 'tactics'> & {
|
|
116
125
|
roster: Player[];
|
|
117
126
|
rating: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
|
|
1
|
+
import { AuthIdentityModel, AuthSessionModel, AuthUserModel, BoxScoreModel, CoachModel, ScoutModel, BoxScoreTotalsModel, CompetitionChampionModel, CurrencyTransactionModel, GachaPityModel, GachaPullHistoryModel, WishlistModel, UserSettingsModel, CompetitionMatchModel, CompetitionMVPModel, CompetitionModel, CompetitionStandingsMatchModel, CompetitionStandingsModel, CompetitionTeamsModel, CountryModel, DivisionModel, DivisionSeasonModel, IterationModel, LeagueModel, MatchModel, MatchRatingModel, MatchResultModel, MatchSetModel, PerformanceStatsModel, PlayerModel, PlayerImprovementLogModel, PlayerTeamModel, RetiredPlayerModel, PromotionMatchModel, RegionQualifierModel, NationalCountryModel, RallyModel, TacticsModel, LineupPresetModel, TeamModel, TeamReplacementModel, UserCurrencyModel, UserPlayerProgressModel, UserPullGrantModel, UserTeamModel, VPERModel, NotificationModel, AppConfigModel, LegacyTeamFlagModel } from './models';
|
|
2
2
|
export function initModels(sequelize) {
|
|
3
3
|
const Coach = CoachModel.initModel(sequelize);
|
|
4
4
|
const Scout = ScoutModel.initModel(sequelize);
|
|
5
5
|
const CurrencyTransaction = CurrencyTransactionModel.initModel(sequelize);
|
|
6
6
|
const Tactics = TacticsModel.initModel(sequelize);
|
|
7
|
+
const LineupPreset = LineupPresetModel.initModel(sequelize);
|
|
7
8
|
const AuthUser = AuthUserModel.initModel(sequelize);
|
|
8
9
|
const UserCurrency = UserCurrencyModel.initModel(sequelize);
|
|
9
10
|
const UserPlayerProgress = UserPlayerProgressModel.initModel(sequelize);
|
|
@@ -196,6 +197,10 @@ export function initModels(sequelize) {
|
|
|
196
197
|
Team.hasOne(Scout, { as: 'Scout', foreignKey: 'team_id' });
|
|
197
198
|
Scout.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
198
199
|
Team.hasOne(Tactics, { as: 'tactics', foreignKey: 'team_id' });
|
|
200
|
+
// Lineup presets are loaded only by the management API; deliberately NOT in Team.defaultScope so the
|
|
201
|
+
// simulator's team loads are unchanged (it reads the active config from the mirrored Tactics row).
|
|
202
|
+
Team.hasMany(LineupPreset, { as: 'lineupPresets', foreignKey: 'team_id' });
|
|
203
|
+
LineupPreset.belongsTo(Team, { as: 'team', foreignKey: 'team_id' });
|
|
199
204
|
Team.belongsTo(Country, { as: 'country', foreignKey: 'country_id' });
|
|
200
205
|
Team.belongsToMany(Competition, {
|
|
201
206
|
as: 'Competitions',
|
|
@@ -267,6 +272,7 @@ export function initModels(sequelize) {
|
|
|
267
272
|
Team,
|
|
268
273
|
TeamReplacement,
|
|
269
274
|
Tactics,
|
|
275
|
+
LineupPreset,
|
|
270
276
|
UserTeam,
|
|
271
277
|
UserCurrency,
|
|
272
278
|
UserPlayerProgress,
|
|
@@ -33,6 +33,7 @@ export * from './scout';
|
|
|
33
33
|
export * from './team';
|
|
34
34
|
export * from './team-replacement';
|
|
35
35
|
export * from './tactics';
|
|
36
|
+
export * from './lineup-preset';
|
|
36
37
|
export * from './user-currency';
|
|
37
38
|
export * from './user-player-progress';
|
|
38
39
|
export * from './gacha-pity';
|
|
@@ -33,6 +33,7 @@ export * from './scout';
|
|
|
33
33
|
export * from './team';
|
|
34
34
|
export * from './team-replacement';
|
|
35
35
|
export * from './tactics';
|
|
36
|
+
export * from './lineup-preset';
|
|
36
37
|
export * from './user-currency';
|
|
37
38
|
export * from './user-player-progress';
|
|
38
39
|
export * from './gacha-pity';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as Sequelize from 'sequelize';
|
|
2
|
+
import { Model, Optional } from 'sequelize';
|
|
3
|
+
import { TeamId, TeamModel } from '.';
|
|
4
|
+
import type { Tactics as ApiTactics } from '../../api';
|
|
5
|
+
export interface LineupPresetAttributes {
|
|
6
|
+
preset_id: string;
|
|
7
|
+
team_id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
is_active: boolean;
|
|
10
|
+
order_index: number;
|
|
11
|
+
config: ApiTactics;
|
|
12
|
+
}
|
|
13
|
+
export type LineupPresetPk = 'preset_id';
|
|
14
|
+
export type LineupPresetId = LineupPresetModel[LineupPresetPk];
|
|
15
|
+
export type LineupPresetOptionalAttributes = 'preset_id' | 'is_active';
|
|
16
|
+
export type LineupPresetCreationAttributes = Optional<LineupPresetAttributes, LineupPresetOptionalAttributes>;
|
|
17
|
+
export declare class LineupPresetModel extends Model<LineupPresetAttributes, LineupPresetCreationAttributes> implements LineupPresetAttributes {
|
|
18
|
+
preset_id: string;
|
|
19
|
+
team_id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
is_active: boolean;
|
|
22
|
+
order_index: number;
|
|
23
|
+
config: ApiTactics;
|
|
24
|
+
team: TeamModel;
|
|
25
|
+
getTeam: Sequelize.BelongsToGetAssociationMixin<TeamModel>;
|
|
26
|
+
setTeam: Sequelize.BelongsToSetAssociationMixin<TeamModel, TeamId>;
|
|
27
|
+
createTeam: Sequelize.BelongsToCreateAssociationMixin<TeamModel>;
|
|
28
|
+
static initModel(sequelize: Sequelize.Sequelize): typeof LineupPresetModel;
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DataTypes, Model } from 'sequelize';
|
|
2
|
+
export class LineupPresetModel extends Model {
|
|
3
|
+
static initModel(sequelize) {
|
|
4
|
+
return LineupPresetModel.init({
|
|
5
|
+
preset_id: {
|
|
6
|
+
type: DataTypes.UUID,
|
|
7
|
+
allowNull: false,
|
|
8
|
+
primaryKey: true,
|
|
9
|
+
defaultValue: DataTypes.UUIDV4
|
|
10
|
+
},
|
|
11
|
+
team_id: {
|
|
12
|
+
type: DataTypes.UUID,
|
|
13
|
+
allowNull: false,
|
|
14
|
+
references: {
|
|
15
|
+
model: 'Team',
|
|
16
|
+
key: 'team_id'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
name: {
|
|
20
|
+
type: DataTypes.STRING,
|
|
21
|
+
allowNull: false
|
|
22
|
+
},
|
|
23
|
+
is_active: {
|
|
24
|
+
type: DataTypes.BOOLEAN,
|
|
25
|
+
allowNull: false,
|
|
26
|
+
defaultValue: false
|
|
27
|
+
},
|
|
28
|
+
order_index: {
|
|
29
|
+
type: DataTypes.INTEGER,
|
|
30
|
+
allowNull: false
|
|
31
|
+
},
|
|
32
|
+
config: {
|
|
33
|
+
type: DataTypes.JSONB,
|
|
34
|
+
allowNull: false
|
|
35
|
+
}
|
|
36
|
+
}, {
|
|
37
|
+
sequelize,
|
|
38
|
+
tableName: 'LineupPreset',
|
|
39
|
+
schema: 'public',
|
|
40
|
+
timestamps: false,
|
|
41
|
+
indexes: [
|
|
42
|
+
{
|
|
43
|
+
name: 'LineupPreset_pk',
|
|
44
|
+
unique: true,
|
|
45
|
+
fields: [
|
|
46
|
+
{ name: 'preset_id' }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'LineupPreset_team_id_idx',
|
|
51
|
+
fields: [
|
|
52
|
+
{ name: 'team_id' }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
// At most one active preset per team.
|
|
57
|
+
name: 'LineupPreset_one_active_per_team',
|
|
58
|
+
unique: true,
|
|
59
|
+
fields: [
|
|
60
|
+
{ name: 'team_id' }
|
|
61
|
+
],
|
|
62
|
+
where: { is_active: true }
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -17,6 +17,7 @@ export interface SetSubConfigAttributes {
|
|
|
17
17
|
mode: SubMode;
|
|
18
18
|
benchId?: PlayerId;
|
|
19
19
|
fatigueBand?: EnergyBand;
|
|
20
|
+
benchFatigueBand?: EnergyBand;
|
|
20
21
|
conditions?: PinchCondition[];
|
|
21
22
|
conditionLogic?: ConditionLogic;
|
|
22
23
|
}
|
|
@@ -26,6 +27,7 @@ export interface DesignatedSubAttributes {
|
|
|
26
27
|
mode?: SubMode;
|
|
27
28
|
isPinchServer?: boolean;
|
|
28
29
|
fatigueBand?: SubBand;
|
|
30
|
+
benchFatigueBand?: EnergyBand;
|
|
29
31
|
conditions?: PinchCondition[];
|
|
30
32
|
conditionLogic?: ConditionLogic;
|
|
31
33
|
sets?: SetSubConfigAttributes[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `config` is already stored as the API Tactics DTO, so a preset row maps to its API shape with no hydration.
|
|
2
|
+
function transformToObject(model) {
|
|
3
|
+
return {
|
|
4
|
+
presetId: model.preset_id,
|
|
5
|
+
name: model.name,
|
|
6
|
+
isActive: model.is_active,
|
|
7
|
+
orderIndex: model.order_index,
|
|
8
|
+
config: model.config
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export { transformToObject as transformToLineupPreset };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { transformToLineupPreset } from './lineup-preset';
|
|
3
|
+
describe('transformToLineupPreset()', () => {
|
|
4
|
+
it('maps preset columns to the API shape and passes config through by reference', () => {
|
|
5
|
+
const config = { lineup: { bench: [] }, substitutionBand: 'TIRED' };
|
|
6
|
+
const preset = transformToLineupPreset({
|
|
7
|
+
preset_id: 'p1',
|
|
8
|
+
team_id: 't1',
|
|
9
|
+
name: 'Starters',
|
|
10
|
+
is_active: true,
|
|
11
|
+
order_index: 2,
|
|
12
|
+
config
|
|
13
|
+
});
|
|
14
|
+
expect(preset).toEqual({
|
|
15
|
+
presetId: 'p1',
|
|
16
|
+
name: 'Starters',
|
|
17
|
+
isActive: true,
|
|
18
|
+
orderIndex: 2,
|
|
19
|
+
config
|
|
20
|
+
});
|
|
21
|
+
expect(preset.config).toBe(config);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { TacticsAttributes, TacticsModel } from '../models';
|
|
2
2
|
import { Player, Tactics } from '../../service';
|
|
3
|
+
import type { Tactics as ApiTactics } from '../../api';
|
|
3
4
|
declare function transformToAttributes(tactics: Tactics, teamId: string): TacticsAttributes;
|
|
4
5
|
declare function transformToObject(model: TacticsModel, roster: Player[]): Tactics;
|
|
5
|
-
|
|
6
|
+
declare function tacticsColumnsToApiDto(model: TacticsModel): ApiTactics;
|
|
7
|
+
export { transformToObject as transformToTactics, transformToAttributes as transformFromTactics, tacticsColumnsToApiDto };
|
|
@@ -58,12 +58,14 @@ function transformToAttributes(tactics, teamId) {
|
|
|
58
58
|
benchId: ds.bench?.id,
|
|
59
59
|
mode: ds.mode,
|
|
60
60
|
fatigueBand: ds.fatigueBand,
|
|
61
|
+
benchFatigueBand: ds.benchFatigueBand,
|
|
61
62
|
conditions: ds.conditions,
|
|
62
63
|
conditionLogic: ds.conditionLogic,
|
|
63
64
|
sets: ds.sets?.map(s => ({
|
|
64
65
|
mode: s.mode,
|
|
65
66
|
benchId: s.bench?.id,
|
|
66
67
|
fatigueBand: s.fatigueBand,
|
|
68
|
+
benchFatigueBand: s.benchFatigueBand,
|
|
67
69
|
conditions: s.conditions,
|
|
68
70
|
conditionLogic: s.conditionLogic
|
|
69
71
|
}))
|
|
@@ -111,12 +113,14 @@ function transformToObject(model, roster) {
|
|
|
111
113
|
bench: d.benchId != null ? findPlayer(d.benchId, roster) : undefined,
|
|
112
114
|
mode: subModeFromAttributes(d),
|
|
113
115
|
fatigueBand: d.fatigueBand === 'NEVER' ? undefined : d.fatigueBand,
|
|
116
|
+
benchFatigueBand: d.benchFatigueBand,
|
|
114
117
|
conditions: d.conditions,
|
|
115
118
|
conditionLogic: d.conditionLogic,
|
|
116
119
|
sets: d.sets?.map(s => ({
|
|
117
120
|
mode: s.mode,
|
|
118
121
|
bench: s.benchId != null ? findPlayer(s.benchId, roster) : undefined,
|
|
119
122
|
fatigueBand: s.fatigueBand,
|
|
123
|
+
benchFatigueBand: s.benchFatigueBand,
|
|
120
124
|
conditions: s.conditions,
|
|
121
125
|
conditionLogic: s.conditionLogic
|
|
122
126
|
}))
|
|
@@ -129,4 +133,41 @@ function transformToObject(model, roster) {
|
|
|
129
133
|
}))
|
|
130
134
|
});
|
|
131
135
|
}
|
|
132
|
-
|
|
136
|
+
// Serialize a Tactics ROW (db columns) straight to the API Tactics DTO (player references stay as ids; no
|
|
137
|
+
// roster needed). Used to seed a lineup preset's `config` from the existing single tactics row (backfill) and
|
|
138
|
+
// to keep the active preset and the mirrored Tactics row in the same shape. Applies the same legacy fixups as
|
|
139
|
+
// the service transformer: resolve the designated-sub `mode` and drop a legacy 'NEVER' fatigueBand.
|
|
140
|
+
function tacticsColumnsToApiDto(model) {
|
|
141
|
+
return {
|
|
142
|
+
lineup: model.lineup,
|
|
143
|
+
substitutionTolerance: model.substitution_tolerance,
|
|
144
|
+
liberoReplacements: model.libero_replacements,
|
|
145
|
+
pinchServerSubs: model.pinch_server_subs,
|
|
146
|
+
receiveRotationOffset: model.receive_rotation_offset,
|
|
147
|
+
substitutionBand: model.substitution_band,
|
|
148
|
+
secondLibero: model.second_libero,
|
|
149
|
+
liberoSub: model.libero_sub,
|
|
150
|
+
liberoReplacementSets: model.libero_replacement_sets,
|
|
151
|
+
designatedSubs: (model.designated_subs ?? []).map(d => ({
|
|
152
|
+
starterId: d.starterId,
|
|
153
|
+
benchId: d.benchId,
|
|
154
|
+
mode: subModeFromAttributes(d),
|
|
155
|
+
fatigueBand: d.fatigueBand === 'NEVER' ? undefined : d.fatigueBand,
|
|
156
|
+
benchFatigueBand: d.benchFatigueBand,
|
|
157
|
+
conditions: d.conditions,
|
|
158
|
+
conditionLogic: d.conditionLogic,
|
|
159
|
+
sets: d.sets?.map(s => ({
|
|
160
|
+
mode: s.mode,
|
|
161
|
+
benchId: s.benchId,
|
|
162
|
+
fatigueBand: s.fatigueBand,
|
|
163
|
+
benchFatigueBand: s.benchFatigueBand,
|
|
164
|
+
conditions: s.conditions,
|
|
165
|
+
conditionLogic: s.conditionLogic
|
|
166
|
+
}))
|
|
167
|
+
})),
|
|
168
|
+
rotationSystem: model.rotation_system,
|
|
169
|
+
designatedSetters: model.designated_setters,
|
|
170
|
+
offensivePreferences: model.offensive_preferences
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export { transformToObject as transformToTactics, transformToAttributes as transformFromTactics, tacticsColumnsToApiDto };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { tacticsColumnsToApiDto } from './tactics';
|
|
3
|
+
import { CourtPosition, EnergyBand } from '../../service';
|
|
4
|
+
// Minimal TacticsModel-shaped object; cast through unknown since the transformer only reads columns.
|
|
5
|
+
function makeTactics(designatedSubs) {
|
|
6
|
+
return {
|
|
7
|
+
team_id: 'team-1',
|
|
8
|
+
substitution_tolerance: 10,
|
|
9
|
+
lineup: {
|
|
10
|
+
[CourtPosition.LEFT_FRONT]: 'lf',
|
|
11
|
+
[CourtPosition.MIDDLE_FRONT]: 'mf',
|
|
12
|
+
[CourtPosition.RIGHT_FRONT]: 'rf',
|
|
13
|
+
[CourtPosition.LEFT_BACK]: 'lb',
|
|
14
|
+
[CourtPosition.MIDDLE_BACK]: 'mb',
|
|
15
|
+
[CourtPosition.RIGHT_BACK]: 'rb',
|
|
16
|
+
bench: ['b1', 'b2']
|
|
17
|
+
},
|
|
18
|
+
libero_replacements: ['lf'],
|
|
19
|
+
pinch_server_subs: { starter1: 'server1' },
|
|
20
|
+
receive_rotation_offset: true,
|
|
21
|
+
designated_subs: designatedSubs,
|
|
22
|
+
substitution_band: EnergyBand.TIRED,
|
|
23
|
+
second_libero: undefined,
|
|
24
|
+
libero_sub: undefined,
|
|
25
|
+
libero_replacement_sets: undefined,
|
|
26
|
+
rotation_system: '5-1',
|
|
27
|
+
designated_setters: ['lf'],
|
|
28
|
+
offensive_preferences: [{ rotation: 1, order: ['lf', 'rf'] }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
describe('tacticsColumnsToApiDto()', () => {
|
|
32
|
+
it('passes scalar/array columns through to the API DTO shape', () => {
|
|
33
|
+
const dto = tacticsColumnsToApiDto(makeTactics([]));
|
|
34
|
+
expect(dto.substitutionTolerance).toBe(10);
|
|
35
|
+
expect(dto.receiveRotationOffset).toBe(true);
|
|
36
|
+
expect(dto.substitutionBand).toBe(EnergyBand.TIRED);
|
|
37
|
+
expect(dto.rotationSystem).toBe('5-1');
|
|
38
|
+
expect(dto.liberoReplacements).toEqual(['lf']);
|
|
39
|
+
expect(dto.pinchServerSubs).toEqual({ starter1: 'server1' });
|
|
40
|
+
expect(dto.lineup.bench).toEqual(['b1', 'b2']);
|
|
41
|
+
expect(dto.offensivePreferences).toEqual([{ rotation: 1, order: ['lf', 'rf'] }]);
|
|
42
|
+
});
|
|
43
|
+
it("resolves a legacy isPinchServer entry to mode 'PINCH'", () => {
|
|
44
|
+
const dto = tacticsColumnsToApiDto(makeTactics([
|
|
45
|
+
{ starterId: 's1', benchId: 'b1', isPinchServer: true, conditions: [{ type: 'ASAP' }] }
|
|
46
|
+
]));
|
|
47
|
+
expect(dto.designatedSubs[0].mode).toBe('PINCH');
|
|
48
|
+
});
|
|
49
|
+
it("resolves a legacy 'NEVER' fatigueBand to mode NEVER and drops the band", () => {
|
|
50
|
+
const dto = tacticsColumnsToApiDto(makeTactics([
|
|
51
|
+
{ starterId: 's1', fatigueBand: 'NEVER' }
|
|
52
|
+
]));
|
|
53
|
+
expect(dto.designatedSubs[0].mode).toBe('NEVER');
|
|
54
|
+
expect(dto.designatedSubs[0].fatigueBand).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
it('passes a fatigue entry with its own bands through unchanged', () => {
|
|
57
|
+
const dto = tacticsColumnsToApiDto(makeTactics([
|
|
58
|
+
{ starterId: 's1', benchId: 'b1', mode: 'FATIGUE', fatigueBand: EnergyBand.WORN, benchFatigueBand: EnergyBand.TIRED }
|
|
59
|
+
]));
|
|
60
|
+
expect(dto.designatedSubs[0].mode).toBe('FATIGUE');
|
|
61
|
+
expect(dto.designatedSubs[0].fatigueBand).toBe(EnergyBand.WORN);
|
|
62
|
+
expect(dto.designatedSubs[0].benchFatigueBand).toBe(EnergyBand.TIRED);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -43,6 +43,7 @@ export declare const SetSubConfigSchema: z.ZodObject<{
|
|
|
43
43
|
}>;
|
|
44
44
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
45
45
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
46
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
46
47
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
47
48
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
48
49
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -80,6 +81,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
80
81
|
}>;
|
|
81
82
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
82
83
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
84
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
83
85
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
84
86
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
85
87
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -115,6 +117,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
|
|
|
115
117
|
}>;
|
|
116
118
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
117
119
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
120
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
118
121
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
119
122
|
type: z.ZodLiteral<PinchConditionType.ASAP>;
|
|
120
123
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -23,6 +23,7 @@ const setSubConfigShape = {
|
|
|
23
23
|
mode: SubModeSchema,
|
|
24
24
|
bench: playerInstanceSchema.optional(),
|
|
25
25
|
fatigueBand: z.nativeEnum(EnergyBand).optional(),
|
|
26
|
+
benchFatigueBand: z.nativeEnum(EnergyBand).optional(),
|
|
26
27
|
conditions: z.array(PinchConditionSchema).optional(),
|
|
27
28
|
conditionLogic: ConditionLogicSchema.optional()
|
|
28
29
|
};
|
|
@@ -71,6 +71,25 @@ describe('DesignatedSubSchema', () => {
|
|
|
71
71
|
});
|
|
72
72
|
expect(res.success).toBe(true);
|
|
73
73
|
});
|
|
74
|
+
it("accepts a fatigue-mode entry with the substitute's own benchFatigueBand", () => {
|
|
75
|
+
const res = DesignatedSubSchema.safeParse({
|
|
76
|
+
starter,
|
|
77
|
+
bench,
|
|
78
|
+
mode: 'FATIGUE',
|
|
79
|
+
fatigueBand: EnergyBand.WORN,
|
|
80
|
+
benchFatigueBand: EnergyBand.TIRED
|
|
81
|
+
});
|
|
82
|
+
expect(res.success).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it('rejects benchFatigueBand = NEVER (only the team default band may be NEVER)', () => {
|
|
85
|
+
const res = DesignatedSubSchema.safeParse({
|
|
86
|
+
starter,
|
|
87
|
+
bench,
|
|
88
|
+
mode: 'FATIGUE',
|
|
89
|
+
benchFatigueBand: 'NEVER'
|
|
90
|
+
});
|
|
91
|
+
expect(res.success).toBe(false);
|
|
92
|
+
});
|
|
74
93
|
it('accepts a fatigue-mode entry with no bench (falls back to closest sub)', () => {
|
|
75
94
|
const res = DesignatedSubSchema.safeParse({
|
|
76
95
|
starter,
|
|
@@ -26,6 +26,7 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
26
26
|
}>;
|
|
27
27
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
28
28
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
29
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
29
30
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
31
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
31
32
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -61,6 +62,7 @@ export declare const TacticsInputSchema: z.ZodObject<{
|
|
|
61
62
|
}>;
|
|
62
63
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
63
64
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
65
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
|
|
64
66
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
65
67
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
66
68
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -34,6 +34,7 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
34
34
|
}>;
|
|
35
35
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
36
36
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
37
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
37
38
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
38
39
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
39
40
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -69,6 +70,7 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
69
70
|
}>;
|
|
70
71
|
bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
|
|
71
72
|
fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
73
|
+
benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
|
|
72
74
|
conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
73
75
|
type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
|
|
74
76
|
}, z.core.$strip>, z.ZodObject<{
|