@outbuild-company/schedule-core 1.0.0
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/README.md +59 -0
- package/dist/cdn/schedule-core.global.js +2 -0
- package/dist/cdn/schedule-core.global.js.map +1 -0
- package/dist/index.cjs +18665 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2122 -0
- package/dist/index.d.ts +2122 -0
- package/dist/index.js +18633 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,2122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the custom AutoScheduler and its satellite packages
|
|
3
|
+
* (recording, replay, shadow, discovery).
|
|
4
|
+
*
|
|
5
|
+
* This file is the single seam between all components. All scheduler code
|
|
6
|
+
* depends ONLY on these types — never on the DHTMLX gantt object directly.
|
|
7
|
+
*/
|
|
8
|
+
type ActivityId$2 = string;
|
|
9
|
+
type LinkId$1 = string;
|
|
10
|
+
type CalendarId$2 = string;
|
|
11
|
+
|
|
12
|
+
/** Value constants for link types. Matches `gantt.config.links`. */
|
|
13
|
+
declare const LINK_TYPE: {
|
|
14
|
+
readonly FINISH_TO_START: "0";
|
|
15
|
+
readonly START_TO_START: "1";
|
|
16
|
+
readonly FINISH_TO_FINISH: "2";
|
|
17
|
+
readonly START_TO_FINISH: "3";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* RAW DHTMLX row as the scheduler engine and `ScheduleState` see it:
|
|
21
|
+
* snake_case fields, `parent` with the numeric-0 root sentinel,
|
|
22
|
+
* `$source`/`$target` link id arrays, `duration` in HOURS, ids
|
|
23
|
+
* `string | number`. Dates are real Date objects (not ISO strings);
|
|
24
|
+
* serialization for JSON fixtures happens in the recording layer.
|
|
25
|
+
* Sibling shapes: persisted `Activity` (`src/types.ts`) and the
|
|
26
|
+
* pipelines' camelCase `ActivitySnapshot` (`src/columns/types.ts`) —
|
|
27
|
+
* see the `Activity` docblock for the full map.
|
|
28
|
+
*/
|
|
29
|
+
interface EngineActivitySnapshot {
|
|
30
|
+
id: ActivityId$2;
|
|
31
|
+
start_date: Date;
|
|
32
|
+
end_date: Date;
|
|
33
|
+
duration: number;
|
|
34
|
+
type: ActivityType;
|
|
35
|
+
parent: ActivityId$2 | 0;
|
|
36
|
+
constraint_type: ConstraintType | null;
|
|
37
|
+
constraint_date: Date | null;
|
|
38
|
+
progress: number;
|
|
39
|
+
auto_scheduling: boolean;
|
|
40
|
+
calendar_id: CalendarId$2 | null;
|
|
41
|
+
/** Outgoing link ids (this activity is the link source) */
|
|
42
|
+
$source: LinkId$1[];
|
|
43
|
+
/** Incoming link ids (this activity is the link target) */
|
|
44
|
+
$target: LinkId$1[];
|
|
45
|
+
/** Mirrors `Activity.is_critical` (`CriticalFlag`): boolean fresh from the
|
|
46
|
+
* backend, `'Si'`/`'No'` after the critical-path pass. */
|
|
47
|
+
is_critical?: boolean | 'Si' | 'No';
|
|
48
|
+
dragged?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Display/domain fields carried at runtime via the activityToSnapshot `...a`
|
|
51
|
+
* spread; declared so state-ports reads them typed instead of casting.
|
|
52
|
+
*/
|
|
53
|
+
text?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
cost?: number;
|
|
56
|
+
used_cost?: number;
|
|
57
|
+
real_cost?: number;
|
|
58
|
+
hhWorkTime?: number;
|
|
59
|
+
real_work?: number;
|
|
60
|
+
ponderator?: number;
|
|
61
|
+
tasks?: unknown[];
|
|
62
|
+
real_constraint_type?: ConstraintType | null;
|
|
63
|
+
last_constraint?: ConstraintType | null;
|
|
64
|
+
last_constraint_date?: Date | null;
|
|
65
|
+
last_start_date?: Date | null;
|
|
66
|
+
date_origin?: Date | null;
|
|
67
|
+
/**
|
|
68
|
+
* Backend DB row id (distinct from `id` which is `unique_id`). Optional
|
|
69
|
+
* because activities created in-session have no backend row yet.
|
|
70
|
+
*/
|
|
71
|
+
proplannerId?: number;
|
|
72
|
+
/** Sequential display index used as a short human-readable activity label.
|
|
73
|
+
* `| undefined` is explicit: rollback before-images assign the prior value,
|
|
74
|
+
* which is `undefined` for an activity that had no correlative_id yet. */
|
|
75
|
+
correlative_id?: number | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Project-scoped unique identifier string, user-visible (e.g. "A-0001").
|
|
78
|
+
* Null for activities that have not been assigned one yet.
|
|
79
|
+
*/
|
|
80
|
+
custom_id?: string | null;
|
|
81
|
+
/**
|
|
82
|
+
* UUID-like correlative assigned by Outbuild. May be string or number
|
|
83
|
+
* depending on the backend version; absent on in-session-created activities.
|
|
84
|
+
*/
|
|
85
|
+
unique_correlative_id?: string | number | null;
|
|
86
|
+
/**
|
|
87
|
+
* Working-hours mirror of `duration` used by the Duration column renderer to
|
|
88
|
+
* avoid re-computing hours from days on every render. Stamped by
|
|
89
|
+
* `parseFromBackend`, `activityCreationPipeline`, and parent-bounds.
|
|
90
|
+
*/
|
|
91
|
+
for_disable_milestone_duration?: number;
|
|
92
|
+
/**
|
|
93
|
+
* View-model visibility bit (UI filters); ex `should_be_showed` of the
|
|
94
|
+
* DHTMLX task. `visible !== false` = visible; default true.
|
|
95
|
+
*/
|
|
96
|
+
visible?: boolean;
|
|
97
|
+
/** Checkbox-selection bit owned by the selection pipeline. */
|
|
98
|
+
checked?: boolean;
|
|
99
|
+
/** Visible (UI-layer) checkbox state, kept in sync with `checked`. */
|
|
100
|
+
visibleChecked?: boolean;
|
|
101
|
+
/** Signal to the bridge that `visibleChecked` was set this dispatch. */
|
|
102
|
+
mustApplyVisibleChecked?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Pending Schedule Impact Requests freezing this activity (id-only
|
|
105
|
+
* reduction; non-empty ⇒ frozen). Carried verbatim from the backend payload.
|
|
106
|
+
* Only for dynamic setActivityField writes + arbitrary backend passthrough;
|
|
107
|
+
* all known fields are declared above.
|
|
108
|
+
*/
|
|
109
|
+
pendingRequests?: PendingRequest[];
|
|
110
|
+
earlyStart?: Date | null;
|
|
111
|
+
earlyFinish?: Date | null;
|
|
112
|
+
lateStart?: Date | null;
|
|
113
|
+
lateFinish?: Date | null;
|
|
114
|
+
totalSlack?: number | null;
|
|
115
|
+
freeSlack?: number | null;
|
|
116
|
+
/** Expected-progress baseline output, written by the expected-progress pass. */
|
|
117
|
+
expected_progress?: number;
|
|
118
|
+
/** Lookahead membership; the core derives it in promote/demote (R4). */
|
|
119
|
+
is_lookahead?: boolean;
|
|
120
|
+
/** Client-side "new, unsaved" marker (not persisted as such). */
|
|
121
|
+
isNewActivity?: true | null;
|
|
122
|
+
/** Descendant new-activity id bookkeeping (R15, temporal). */
|
|
123
|
+
newActivitiesArray?: ActivityId$2[];
|
|
124
|
+
/** `newActivitiesArray.length > 0` mirror (R15, temporal). */
|
|
125
|
+
hasNewActivities?: boolean;
|
|
126
|
+
/** Activity modification log carried from the backend. */
|
|
127
|
+
activityModifications?: unknown[];
|
|
128
|
+
/** Load-derived: the activity fell back to the default calendar. */
|
|
129
|
+
assignedDefaultCalendar?: boolean;
|
|
130
|
+
/** Backend flag: has persisted children. */
|
|
131
|
+
has_childs?: boolean;
|
|
132
|
+
/** Backend flag: ponderator set manually (not auto-distributed). */
|
|
133
|
+
hasCustomPonderator?: boolean;
|
|
134
|
+
/** Backend aggregate: recursive duration sum. */
|
|
135
|
+
sumOfDurationRecursively?: number;
|
|
136
|
+
/** Backend row ids (distinct from the domain `id`). */
|
|
137
|
+
ganttId?: number;
|
|
138
|
+
sectorId?: number;
|
|
139
|
+
companyId?: number;
|
|
140
|
+
/** Subcontract column id + its raw backend object. */
|
|
141
|
+
subcontractId?: number | null;
|
|
142
|
+
subcontract?: unknown;
|
|
143
|
+
/** Responsibility / tag columns (arrays of backend rows). */
|
|
144
|
+
responsables?: unknown[];
|
|
145
|
+
tags?: unknown[];
|
|
146
|
+
/** Active baseline points overlaid at load. */
|
|
147
|
+
baseline_points?: BaselinePoint[];
|
|
148
|
+
/** Backend timestamps. */
|
|
149
|
+
createdAt?: string;
|
|
150
|
+
updatedAt?: string;
|
|
151
|
+
old_progress_registered?: number;
|
|
152
|
+
old_duration_registered?: number;
|
|
153
|
+
old_cost_registered?: number;
|
|
154
|
+
old_used_cost_registered?: number;
|
|
155
|
+
old_real_cost_registered?: number;
|
|
156
|
+
old_hhWorkTime_registered?: number;
|
|
157
|
+
/** Mirror of the constraint before the last change (constraint cluster). */
|
|
158
|
+
constraint_type_old?: ConstraintType | null;
|
|
159
|
+
/** `type === 'milestone'` cache flipped by the duration pipeline. */
|
|
160
|
+
is_milestone?: boolean;
|
|
161
|
+
/** DHTMLX markers consumed by the bridge / render layer. */
|
|
162
|
+
$keep_constraints?: boolean;
|
|
163
|
+
$rendered_type?: ActivityType;
|
|
164
|
+
$no_start?: boolean;
|
|
165
|
+
$no_end?: boolean;
|
|
166
|
+
status?: 'Waiting' | 'Doing' | 'Done';
|
|
167
|
+
progressSolidColor?: string;
|
|
168
|
+
color?: string;
|
|
169
|
+
/** Flags a recursive complete/uncomplete from the action-button flow. */
|
|
170
|
+
progressChangedByActionButton?: boolean;
|
|
171
|
+
custom_predecessors?: string;
|
|
172
|
+
/** Backend typo preserved (single 'c'). */
|
|
173
|
+
custom_sucessors?: string;
|
|
174
|
+
start_base?: Date;
|
|
175
|
+
end_base?: Date;
|
|
176
|
+
duration_base?: number;
|
|
177
|
+
cost_base?: number;
|
|
178
|
+
work_base?: number;
|
|
179
|
+
expected_progress_base?: number;
|
|
180
|
+
}
|
|
181
|
+
interface LinkSnapshot {
|
|
182
|
+
id: LinkId$1;
|
|
183
|
+
source: ActivityId$2;
|
|
184
|
+
target: ActivityId$2;
|
|
185
|
+
type: LinkType;
|
|
186
|
+
lag: number;
|
|
187
|
+
/**
|
|
188
|
+
* Precomputed lag components for a VIRTUAL summary-expanded link. When
|
|
189
|
+
* present, the link calculator uses them verbatim instead of deriving the
|
|
190
|
+
* components from leaf durations (the normal `normalize` switch).
|
|
191
|
+
*
|
|
192
|
+
* These exist so an FF/SF link INTO a summary can carry the SUMMARY's
|
|
193
|
+
* duration (`_targetLag = -summary.duration`) and the per-leaf offset from
|
|
194
|
+
* the summary's start (`_trueLag = userLag + off_tgt(leaf)`), matching legacy
|
|
195
|
+
* `_getImplicitLinks`/`_convertToFinishToStartLink` under
|
|
196
|
+
* `auto_scheduling_move_projects`. Computed ONCE at expansion time
|
|
197
|
+
* (`parent-link-expansion.ts`), never per scheduling tick.
|
|
198
|
+
*
|
|
199
|
+
* All three must be set together (or none): a half-populated link would mix
|
|
200
|
+
* precomputed and derived components. Only the FF/SF-into-summary path emits
|
|
201
|
+
* them; every other link leaves them undefined and hits the fast path.
|
|
202
|
+
*/
|
|
203
|
+
_sourceLag?: number;
|
|
204
|
+
_targetLag?: number;
|
|
205
|
+
_trueLag?: number;
|
|
206
|
+
ganttId?: number;
|
|
207
|
+
sectorId?: number;
|
|
208
|
+
/** BE row id (distinct from the schedule-level `id`). */
|
|
209
|
+
proplannerId?: number;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Constraint Rules — single source of truth.
|
|
214
|
+
*
|
|
215
|
+
* This module owns the definition of constraint types and the rules that
|
|
216
|
+
* govern them. Both the autoscheduler and the manual scheduling pipelines
|
|
217
|
+
* (column edits, drag, bulk) consume the same primitives from here.
|
|
218
|
+
*
|
|
219
|
+
* Two surfaces:
|
|
220
|
+
*
|
|
221
|
+
* (a) Manual scheduling — pipelines call `checkConstraintViolation()` after
|
|
222
|
+
* a user types a new value, to decide whether to emit a warning.
|
|
223
|
+
* Surface: scalar Date inputs, no adapter needed.
|
|
224
|
+
*
|
|
225
|
+
* (b) Autoscheduler — `computeConstraintBounds()` derives the four date
|
|
226
|
+
* boundaries (earliest/latest × start/end) an activity's constraint
|
|
227
|
+
* imposes. `isStartWithinBounds()` and `isEndWithinBounds()` are used
|
|
228
|
+
* by `limitPlanDates()` to decide whether a candidate date (from a
|
|
229
|
+
* link) respects the constraint.
|
|
230
|
+
*
|
|
231
|
+
* Domain invariant the autoscheduler must respect:
|
|
232
|
+
* The autoscheduler never moves an activity to a date that violates that
|
|
233
|
+
* activity's own constraint. If a cascade via a link would push the
|
|
234
|
+
* activity outside its constraint bounds, the activity stays put.
|
|
235
|
+
*
|
|
236
|
+
* Manual scheduling does NOT enforce this — a user is allowed to type a
|
|
237
|
+
* value that violates their own constraint. The pipeline applies the edit
|
|
238
|
+
* and emits a warning; the UI decides what to do with the warning (typically
|
|
239
|
+
* shows a "constraint violation detected" modal and offers to revert).
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
type ConstraintType = 'asap' | 'alap' | 'snet' | 'snlt' | 'fnet' | 'fnlt' | 'mso' | 'mfo';
|
|
243
|
+
declare const CONSTRAINT_TYPE: {
|
|
244
|
+
readonly ASAP: "asap";
|
|
245
|
+
readonly ALAP: "alap";
|
|
246
|
+
readonly SNET: "snet";
|
|
247
|
+
readonly SNLT: "snlt";
|
|
248
|
+
readonly FNET: "fnet";
|
|
249
|
+
readonly FNLT: "fnlt";
|
|
250
|
+
readonly MSO: "mso";
|
|
251
|
+
readonly MFO: "mfo";
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Public internal model types — the normalized form the core works with
|
|
256
|
+
* after parsing the backend payload.
|
|
257
|
+
*
|
|
258
|
+
* Field naming follows snake_case for entity fields (matches backend +
|
|
259
|
+
* DHTMLX convention) and camelCase for everything else, per Q14 in
|
|
260
|
+
* SCHEDULE_CORE_API.md.
|
|
261
|
+
*/
|
|
262
|
+
type ActivityId$1 = string;
|
|
263
|
+
type LinkId = string;
|
|
264
|
+
type CalendarId$1 = string;
|
|
265
|
+
type ActivityType = 'project' | 'task' | 'milestone';
|
|
266
|
+
|
|
267
|
+
/** DHTMLX link kinds: 0 = FS, 1 = SS, 2 = FF, 3 = SF. */
|
|
268
|
+
type LinkType = '0' | '1' | '2' | '3';
|
|
269
|
+
/**
|
|
270
|
+
* Critical-path output flag. Carries two historical shapes: `parseFromBackend`
|
|
271
|
+
* stores a boolean, and the critical-path pass (`initial-passes.ts`) overwrites
|
|
272
|
+
* it with the DHTMLX string `'Si'`/`'No'`. Both are observed at runtime (the
|
|
273
|
+
* CP / paste tests assert against `'Si'`/`'No'`; `parseFromBackend` against
|
|
274
|
+
* boolean), so the type unions all three rather than lying with a bare boolean.
|
|
275
|
+
*/
|
|
276
|
+
type CriticalFlag = boolean | 'Si' | 'No';
|
|
277
|
+
/** Direction for closest-work-time snapping. */
|
|
278
|
+
type WorkTimeDirection = 'future' | 'past';
|
|
279
|
+
declare const WORK_TIME_DIRECTION: {
|
|
280
|
+
readonly FUTURE: "future";
|
|
281
|
+
readonly PAST: "past";
|
|
282
|
+
};
|
|
283
|
+
/** Calendar query granularity (the engine boundary adds 'minute'/'any'). */
|
|
284
|
+
type CalendarUnit = 'hour' | 'day';
|
|
285
|
+
declare const CALENDAR_UNIT: {
|
|
286
|
+
readonly HOUR: "hour";
|
|
287
|
+
readonly DAY: "day";
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Project-level criterion used to redistribute `ponderator` (weight) among
|
|
291
|
+
* sibling activities. Backend stores it lowercase on `projects.activity_creter`
|
|
292
|
+
* (`'duration' | 'cost' | 'hh'`, default `'duration'`); the core normalizes
|
|
293
|
+
* to uppercase. See `getActiveBaseline` / `recomputePonderatorsForParent`.
|
|
294
|
+
*/
|
|
295
|
+
type ActivityCreter = 'DURATION' | 'COST' | 'HH';
|
|
296
|
+
/** Criterion for the `status` column: compare progress against baseline or live expected. */
|
|
297
|
+
type StatusCriteria = 'Baseline' | 'Actual';
|
|
298
|
+
/**
|
|
299
|
+
* A versioned container of baseline points for a sector
|
|
300
|
+
* (backend `sectorbaselineversion` model). Exactly one version per sector
|
|
301
|
+
* has `active: true` — that is the one the ponderator computation and the
|
|
302
|
+
* baseline columns read.
|
|
303
|
+
*/
|
|
304
|
+
interface BaselineVersion {
|
|
305
|
+
id?: number;
|
|
306
|
+
name?: string | null;
|
|
307
|
+
/** Marks the live baseline. Exactly one active version per sector. */
|
|
308
|
+
active?: boolean;
|
|
309
|
+
visible?: boolean;
|
|
310
|
+
/** Backend extras passed through opaquely. */
|
|
311
|
+
[key: string]: unknown;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* A single baseline data point for an activity, as stored by the backend
|
|
315
|
+
* (`sectorbaselinepoint` model) and carried verbatim through the core.
|
|
316
|
+
* Snapshots the activity's planned start/end/duration/cost/work at the
|
|
317
|
+
* moment a baseline version was saved. The schedule reads the ACTIVE point
|
|
318
|
+
* (see `getActiveBaseline`) to derive `ponderator` (baseline-only, no
|
|
319
|
+
* fallback — see memory `project_ponderator_baseline_fix`).
|
|
320
|
+
*
|
|
321
|
+
* Casing note: the frontend baseline columns (`startBaseCol` …
|
|
322
|
+
* `durationBaseCol`) read these fields directly off the activity, so the
|
|
323
|
+
* core preserves the backend snake_case exactly (passthrough, not parsed).
|
|
324
|
+
*
|
|
325
|
+
* Unit trap: `duration` is in DAYS (backend unit), unlike the activity's
|
|
326
|
+
* `duration` which the core models in working hours — convert with the
|
|
327
|
+
* sector's hours-per-day before arithmetic (legacy `transformDaysToHours`).
|
|
328
|
+
*/
|
|
329
|
+
interface BaselinePoint {
|
|
330
|
+
/** Planned start, backend date string `"YYYY/MM/DD H:MM"` or ISO. */
|
|
331
|
+
start_date: string;
|
|
332
|
+
/** Planned end (inclusive). */
|
|
333
|
+
end_date: string;
|
|
334
|
+
/** Planned duration in DAYS (backend unit). */
|
|
335
|
+
duration: number;
|
|
336
|
+
/** Planned cost. May arrive as a numeric string. */
|
|
337
|
+
cost: number | string;
|
|
338
|
+
/** Planned work hours (HH). May arrive as a numeric string. */
|
|
339
|
+
hh_work: number | string;
|
|
340
|
+
/** The version this point belongs to; `.active` selects the live one. */
|
|
341
|
+
sectorbaselineversion?: BaselineVersion;
|
|
342
|
+
sectorbaselineversionId?: number;
|
|
343
|
+
baseCalendarId?: number;
|
|
344
|
+
hoursPerDay?: number;
|
|
345
|
+
hoursPerWeek?: number;
|
|
346
|
+
/** Backend extras passed through opaquely. */
|
|
347
|
+
[key: string]: unknown;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Pure-domain activity model produced by `parseFromBackend`. Snake_case
|
|
351
|
+
* matches the backend convention. No index signature — every field is
|
|
352
|
+
* explicit. New backend fields must be added here (or to
|
|
353
|
+
* `ACTIVITY_PASSTHROUGH_FIELDS` in `parseFromBackend.ts` and here).
|
|
354
|
+
*
|
|
355
|
+
* Companion type for runtime mutations: see `ActivityRuntime` below,
|
|
356
|
+
* which extends this with `DhtmlxHeritageFields`.
|
|
357
|
+
*/
|
|
358
|
+
/**
|
|
359
|
+
* A pending SIR freezing an activity, reduced to the only field the core
|
|
360
|
+
* needs: its id (equal to the backend SIR id and the front's Redux item id).
|
|
361
|
+
* The schedule load already filters to pending, so presence ⇒ pending.
|
|
362
|
+
*/
|
|
363
|
+
interface PendingRequest {
|
|
364
|
+
id: string;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* PUBLIC persisted domain model — what `core.getActivityView()` returns and
|
|
368
|
+
* what the backend payload parses into. Fields are snake_case (backend +
|
|
369
|
+
* DHTMLX convention); `duration` is in DAYS at this boundary.
|
|
370
|
+
*
|
|
371
|
+
* The package has three activity shapes — one per layer:
|
|
372
|
+
* - `Activity` (this): public, persisted, snake_case.
|
|
373
|
+
* - `ActivitySnapshot` (`src/columns/types.ts`): camelCase + derived
|
|
374
|
+
* fields (hasChildren, isMilestone…); what column pipelines read.
|
|
375
|
+
* Built per-read by `state-ports.ts`; never persisted.
|
|
376
|
+
* - `EngineActivitySnapshot` (`src/autoscheduler/types.ts`): raw DHTMLX
|
|
377
|
+
* row (snake_case + `parent`/`$source`/`$target`, hours-based
|
|
378
|
+
* duration); what `ScheduleState` stores and the scheduler engine
|
|
379
|
+
* consumes. Translation Activity ↔ engine: `src/dispatch/conversion.ts`.
|
|
380
|
+
*/
|
|
381
|
+
interface Activity {
|
|
382
|
+
id: ActivityId$1;
|
|
383
|
+
/** Parent's `id`, or `'0'` when the activity is a root. */
|
|
384
|
+
parent: ActivityId$1 | '0';
|
|
385
|
+
text: string;
|
|
386
|
+
description: string;
|
|
387
|
+
type: ActivityType;
|
|
388
|
+
start_date: Date;
|
|
389
|
+
end_date: Date;
|
|
390
|
+
duration: number;
|
|
391
|
+
constraint_type: ConstraintType | null;
|
|
392
|
+
constraint_date: Date | null;
|
|
393
|
+
auto_scheduling: boolean;
|
|
394
|
+
calendar_id: CalendarId$1 | null;
|
|
395
|
+
/**
|
|
396
|
+
* True when the activity had no valid calendar of its own and inherited the
|
|
397
|
+
* project default at load (its backend `calendarId` was null/undefined or a
|
|
398
|
+
* dangling reference). Set by `parseFromBackend` when `resolveCalendarId`
|
|
399
|
+
* falls back. The legacy DHTMLX worker computed this; the core owns calendar
|
|
400
|
+
* assignment now, so it owns the flag. The UI uses it to surface
|
|
401
|
+
* default-assigned activities. See `CALENDARS.md`.
|
|
402
|
+
*/
|
|
403
|
+
assignedDefaultCalendar?: boolean;
|
|
404
|
+
progress: number;
|
|
405
|
+
cost: number;
|
|
406
|
+
used_cost: number;
|
|
407
|
+
real_cost: number;
|
|
408
|
+
hhWorkTime: number;
|
|
409
|
+
real_work: number;
|
|
410
|
+
ponderator: number;
|
|
411
|
+
correlative_id: number;
|
|
412
|
+
is_lookahead: boolean;
|
|
413
|
+
/**
|
|
414
|
+
* Bit de visibilidad del view-model (filtros de UI); ex `should_be_showed`
|
|
415
|
+
* del task DHTMLX. `visible !== false` = visible; default true. Escrito por
|
|
416
|
+
* el intent `visibility-set` (prop-only) y por `parseFromBackend` al load.
|
|
417
|
+
*/
|
|
418
|
+
visible?: boolean;
|
|
419
|
+
newActivitiesArray: ActivityId$1[];
|
|
420
|
+
activityModifications: unknown[];
|
|
421
|
+
/**
|
|
422
|
+
* Backend DB row id (distinct from the core `id`, which is `unique_id`).
|
|
423
|
+
* Captured from `a.id` in `parseActivity`; `undefined` for activities
|
|
424
|
+
* created in-session (no backend id yet). Used by save dirty-detection to
|
|
425
|
+
* tell persisted activities from new ones. Mirrors `Link.proplannerId`.
|
|
426
|
+
*/
|
|
427
|
+
proplannerId?: number;
|
|
428
|
+
unique_correlative_id: string;
|
|
429
|
+
ganttId: number;
|
|
430
|
+
sectorId: number;
|
|
431
|
+
companyId: number;
|
|
432
|
+
subcontractId: number | null;
|
|
433
|
+
subcontract: unknown;
|
|
434
|
+
custom_id: string | null;
|
|
435
|
+
hasCustomPonderator: boolean;
|
|
436
|
+
has_childs: boolean;
|
|
437
|
+
sumOfDurationRecursively: number;
|
|
438
|
+
/**
|
|
439
|
+
* Critical-path outputs (poblados cuando corre el CP: en background tras la
|
|
440
|
+
* carga vía `criticalPathReady`, o tras cada dispatch). `undefined` si el CP
|
|
441
|
+
* aún no corrió. `is_critical` es string DHTMLX (`'Si'`/`'No'`) post-CP,
|
|
442
|
+
* boolean recién parseado del backend — ver `CriticalFlag`.
|
|
443
|
+
*/
|
|
444
|
+
earlyStart?: Date | null;
|
|
445
|
+
earlyFinish?: Date | null;
|
|
446
|
+
lateStart?: Date | null;
|
|
447
|
+
lateFinish?: Date | null;
|
|
448
|
+
totalSlack?: number | null;
|
|
449
|
+
freeSlack: number | null;
|
|
450
|
+
is_critical: CriticalFlag;
|
|
451
|
+
hasNewActivities: boolean;
|
|
452
|
+
isNewActivity: true | null;
|
|
453
|
+
/**
|
|
454
|
+
* Active-baseline snapshots for this activity (backend
|
|
455
|
+
* `sectorbaselinepoint[]`). Passthrough — carried verbatim so the
|
|
456
|
+
* frontend baseline columns and the ponderator computation can read the
|
|
457
|
+
* ACTIVE point via `getActiveBaseline`. `undefined` when the activity has
|
|
458
|
+
* no baseline (e.g. created in-session after the snapshot).
|
|
459
|
+
*/
|
|
460
|
+
baseline_points?: BaselinePoint[];
|
|
461
|
+
tasks: unknown[];
|
|
462
|
+
responsables: unknown[];
|
|
463
|
+
tags: unknown[];
|
|
464
|
+
/**
|
|
465
|
+
* Pending Schedule Impact Requests (SIRs) freezing this activity. The
|
|
466
|
+
* schedule load path (`sector.controller.js`) already filters to
|
|
467
|
+
* `state==='pending'` and sends `{ id }` only, so the core stores just the
|
|
468
|
+
* id — enough to name the SIR when emitting a `sir-auto-reject` effect.
|
|
469
|
+
* Non-empty ⇒ the activity is frozen (`isFrozen`). The full SIR record lives
|
|
470
|
+
* in the front's Redux, not here. See `SIR_MAP.md`.
|
|
471
|
+
*/
|
|
472
|
+
pendingRequests: PendingRequest[];
|
|
473
|
+
createdAt: string;
|
|
474
|
+
updatedAt: string;
|
|
475
|
+
}
|
|
476
|
+
interface Link {
|
|
477
|
+
id: LinkId;
|
|
478
|
+
source: ActivityId$1;
|
|
479
|
+
target: ActivityId$1;
|
|
480
|
+
type: LinkType;
|
|
481
|
+
lag: number;
|
|
482
|
+
ganttId?: number;
|
|
483
|
+
sectorId?: number;
|
|
484
|
+
/** BE row id (distinct from the schedule-level `id`/`unique_id`). */
|
|
485
|
+
proplannerId?: number;
|
|
486
|
+
}
|
|
487
|
+
interface CalendarWorktime {
|
|
488
|
+
/**
|
|
489
|
+
* Per-weekday working hours, length 7 — index 0 = Sunday.
|
|
490
|
+
*
|
|
491
|
+
* - `false` → non-working day
|
|
492
|
+
* - `string[]` → working day with these `"HH:MM-HH:MM"` windows
|
|
493
|
+
* (supports per-day variation, e.g. a short Friday,
|
|
494
|
+
* and multi-shift split days like `["8-12","13-17"]`)
|
|
495
|
+
*
|
|
496
|
+
* Single source of truth: this is exactly what `parseFromBackend` emits —
|
|
497
|
+
* a working day ALWAYS carries its resolved windows (the parser resolves
|
|
498
|
+
* the default-hours fallback at parse time). The vendored work-calendar
|
|
499
|
+
* engine natively also accepts a boolean `true` per day (DHTMLX
|
|
500
|
+
* contract), but that form never appears in the domain `Calendar`; it is
|
|
501
|
+
* reserved for the engine-facing internals (`SnapshotWorktime`,
|
|
502
|
+
* synthetic replay fixtures).
|
|
503
|
+
*
|
|
504
|
+
* The work-calendar engine consumes each weekday independently, so
|
|
505
|
+
* different days can carry different windows.
|
|
506
|
+
*/
|
|
507
|
+
days: Array<false | string[]>;
|
|
508
|
+
/**
|
|
509
|
+
* Default working-hour windows (`"HH:MM-HH:MM"`). Used as the engine's
|
|
510
|
+
* global fallback. `parseFromBackend` sets it to the first working day's
|
|
511
|
+
* windows.
|
|
512
|
+
*/
|
|
513
|
+
hours: string[];
|
|
514
|
+
/**
|
|
515
|
+
* Per-date exceptions: holidays (non-working) and date-level shift
|
|
516
|
+
* overrides. Keys are `Date.UTC(year, month, day)` (UTC midnight epoch
|
|
517
|
+
* ms) stringified — this matches the lookup the work-calendar engine
|
|
518
|
+
* performs internally at `calendar.dates[dateValue]`.
|
|
519
|
+
*
|
|
520
|
+
* Value semantics:
|
|
521
|
+
* - `false` → the day is non-working
|
|
522
|
+
* - `string[]` → use these `"HH:MM-HH:MM"` windows instead
|
|
523
|
+
* of the default `hours`
|
|
524
|
+
*
|
|
525
|
+
* Source: `BackendCalendar.exceptiondays[]` after the adapter expands
|
|
526
|
+
* `from_date`/`to_date` ranges into individual UTC days.
|
|
527
|
+
*/
|
|
528
|
+
dates?: Record<string, false | string[]>;
|
|
529
|
+
/** Optional per-week overrides for advanced calendars. */
|
|
530
|
+
customWeeks?: Record<string, unknown>;
|
|
531
|
+
}
|
|
532
|
+
interface Calendar {
|
|
533
|
+
id: CalendarId$1;
|
|
534
|
+
/** Display name. Used by the DHTMLX calendar-selector column. */
|
|
535
|
+
name: string;
|
|
536
|
+
worktime: CalendarWorktime;
|
|
537
|
+
is_default: boolean;
|
|
538
|
+
/**
|
|
539
|
+
* True for "base" calendars (baseline comparison). The backend ships
|
|
540
|
+
* these with an `id` suffixed `-base` and `baseDefault` instead of
|
|
541
|
+
* `is_default`. The bridge uses this to set `gantt.defaultBaseCalendar`
|
|
542
|
+
* and to keep base calendars out of the selector dropdown.
|
|
543
|
+
*/
|
|
544
|
+
baseDefault?: boolean;
|
|
545
|
+
/**
|
|
546
|
+
* Date-keyed map of exceptions (holidays, working overrides).
|
|
547
|
+
* Keys are `YYYY-MM-DD` UTC.
|
|
548
|
+
*/
|
|
549
|
+
exceptions?: Record<string, {
|
|
550
|
+
working: boolean;
|
|
551
|
+
hours?: string[];
|
|
552
|
+
}>;
|
|
553
|
+
}
|
|
554
|
+
/** Sector metadata the core respects from the backend. */
|
|
555
|
+
interface SectorMetadata {
|
|
556
|
+
id: string;
|
|
557
|
+
name: string;
|
|
558
|
+
hoursPerDay: number;
|
|
559
|
+
hoursPerWeek: number;
|
|
560
|
+
dateFormat: string;
|
|
561
|
+
projectId: number;
|
|
562
|
+
companyId: number;
|
|
563
|
+
/**
|
|
564
|
+
* Project's `custom_id_prefix` (Outbuild project setting). Used by the
|
|
565
|
+
* CustomIdTracker to generate ids for new activities. Falls back to
|
|
566
|
+
* `'A'` if not provided.
|
|
567
|
+
*/
|
|
568
|
+
customIdPrefix?: string;
|
|
569
|
+
/**
|
|
570
|
+
* Project's `custom_id_increment` (Outbuild project setting), as a
|
|
571
|
+
* string so leading zeros are preserved. Used by the CustomIdTracker
|
|
572
|
+
* to choose the next suffix step. Falls back to `'10'` if not provided.
|
|
573
|
+
*/
|
|
574
|
+
customIdIncrement?: string;
|
|
575
|
+
/**
|
|
576
|
+
* Legacy Primavera-import flag. When true, the core recomputes each
|
|
577
|
+
* activity's `duration` from its start/end against its calendar at load
|
|
578
|
+
* (Primavera stores end-driven durations that may not match the
|
|
579
|
+
* calendar). Mirrors the legacy `fixPrimaveraDurations`. See CALENDARS.md.
|
|
580
|
+
*/
|
|
581
|
+
updateDurationForPrimaveraEndDate?: boolean;
|
|
582
|
+
/**
|
|
583
|
+
* Project's `activity_creter` — criterion for ponderator redistribution
|
|
584
|
+
* (`'DURATION' | 'COST' | 'HH'`). Lives at the project level; arrives via
|
|
585
|
+
* the sector payload's pass-through bucket. `parseSector` always sets it,
|
|
586
|
+
* defaulting to `'DURATION'` (the backend default) when absent/unknown.
|
|
587
|
+
*/
|
|
588
|
+
activityCreter?: ActivityCreter;
|
|
589
|
+
/**
|
|
590
|
+
* Project's status criterion for the `status` column — `'Baseline'` compares
|
|
591
|
+
* `progress` against `expected_progress_base`, `'Actual'` against
|
|
592
|
+
* `expected_progress`. Mutable at runtime via `ScheduleCore.setStatusCriteria`
|
|
593
|
+
* (the Baseline↔Actual toggle). Not a backend field (client-only session
|
|
594
|
+
* setting); a complete sector always carries it — `parseSector` sets the
|
|
595
|
+
* default and `resolveStatusCriteria` normalizes the client override at init.
|
|
596
|
+
* See `docs/domain/columns/COLUMN_status.md`.
|
|
597
|
+
*/
|
|
598
|
+
statusCriteria: StatusCriteria;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* A single entity mutation record emitted by a dispatch ChangeSet.
|
|
602
|
+
* Layer-neutral: contains no dispatch-specific dependencies and is used
|
|
603
|
+
* both by `dispatch/` and by `internal/tracking/`.
|
|
604
|
+
*/
|
|
605
|
+
interface EntityChange<T> {
|
|
606
|
+
id: string;
|
|
607
|
+
kind: 'created' | 'updated' | 'deleted';
|
|
608
|
+
fields?: Record<string, {
|
|
609
|
+
before: unknown;
|
|
610
|
+
after: unknown;
|
|
611
|
+
}> | undefined;
|
|
612
|
+
after: Readonly<T> | null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Public dispatch types — see Q8/Q9/Q10 in SCHEDULE_CORE_API.md.
|
|
617
|
+
*
|
|
618
|
+
* Supported kinds:
|
|
619
|
+
* - `inline-edit` — edit a single column on an activity.
|
|
620
|
+
* - `link-create` — add a link between two activities.
|
|
621
|
+
* - `link-update` — change the lag and/or type of an existing link.
|
|
622
|
+
* - `link-delete` — remove a link by id.
|
|
623
|
+
* - `activity-create` — create a new activity (root, child, or sibling).
|
|
624
|
+
* - `activity-delete` — cascade-delete activities + incident links.
|
|
625
|
+
* - `activity-move` — reorder among siblings or move to a different parent.
|
|
626
|
+
* - `activity-indent` — multi-select indent (children of previous non-selected sibling).
|
|
627
|
+
* - `activity-outdent` — multi-select outdent (move to grandparent level).
|
|
628
|
+
* - `activity-set-progress` — complete/uncomplete (0/100) with cascade.
|
|
629
|
+
* - `selection-toggle` / `selection-replace` — checkbox selection (prop-only).
|
|
630
|
+
* - `activity-paste` — bulk paste (N activities + M links, one atomic ChangeSet).
|
|
631
|
+
* - `dates-batch` — K date edits as one unit of work (multi-drag).
|
|
632
|
+
* - `bulk-edit` — K edits on any pipeline column as one unit of work (bulk editors).
|
|
633
|
+
* - `links-batch` — K link create/update/delete as one atomic unit (mass link/unlink buttons).
|
|
634
|
+
*/
|
|
635
|
+
type LinksBatchOperation = {
|
|
636
|
+
kind: 'create';
|
|
637
|
+
source: ActivityId$1;
|
|
638
|
+
target: ActivityId$1;
|
|
639
|
+
type: LinkType;
|
|
640
|
+
lag?: number;
|
|
641
|
+
linkId?: LinkId;
|
|
642
|
+
} | {
|
|
643
|
+
kind: 'update';
|
|
644
|
+
linkId: LinkId;
|
|
645
|
+
type?: LinkType;
|
|
646
|
+
lag?: number;
|
|
647
|
+
} | {
|
|
648
|
+
kind: 'delete';
|
|
649
|
+
linkId: LinkId;
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
type DispatchAction = {
|
|
653
|
+
kind: 'inline-edit';
|
|
654
|
+
activityId: ActivityId$1;
|
|
655
|
+
column: string;
|
|
656
|
+
newValue: unknown;
|
|
657
|
+
} | {
|
|
658
|
+
/**
|
|
659
|
+
* K ediciones de fecha como UNA unidad de trabajo (multi-drag): cada
|
|
660
|
+
* edit corre su pipeline individual (verdict por barra), pero el
|
|
661
|
+
* post-mutation (autoscheduler → bounds → CP) corre UNA sola vez y
|
|
662
|
+
* sale UN ChangeSet. Patrón hermano de `activity-paste` /
|
|
663
|
+
* `dispatchInlineEditLinks`. Track onAfterTaskDrag F2.2.
|
|
664
|
+
*/
|
|
665
|
+
kind: 'dates-batch';
|
|
666
|
+
edits: ReadonlyArray<{
|
|
667
|
+
activityId: ActivityId$1;
|
|
668
|
+
column: 'start_date' | 'end_date';
|
|
669
|
+
newValue: unknown;
|
|
670
|
+
}>;
|
|
671
|
+
} | {
|
|
672
|
+
/**
|
|
673
|
+
* K edits on ANY pipeline column as ONE unit of work — the
|
|
674
|
+
* generalization of `dates-batch` to the 16 registry columns
|
|
675
|
+
* (schedule bulk editors / modals; PLAN_convergencia_wiring §4.1).
|
|
676
|
+
* Each edit resolves its pipeline via `findPipelineForColumn` and
|
|
677
|
+
* runs phases 1-4 with an individual verdict (a rejected edit does
|
|
678
|
+
* NOT abort the others — paste semantics). A column without a
|
|
679
|
+
* pipeline (e.g. the link columns `custom_predecessors` /
|
|
680
|
+
* `custom_sucessors`, which mutate the link graph and dispatch as
|
|
681
|
+
* link intents) rejects ONLY that edit. The post-mutation pass
|
|
682
|
+
* (autoscheduler → parent bounds → CP) runs ONCE at the end and a
|
|
683
|
+
* single ChangeSet comes out = ONE undo step (never coalesces).
|
|
684
|
+
* Supports both shapes: K activities × same column, and 1 activity
|
|
685
|
+
* × several columns (atomic multi-field edit).
|
|
686
|
+
*/
|
|
687
|
+
kind: 'bulk-edit';
|
|
688
|
+
edits: ReadonlyArray<{
|
|
689
|
+
activityId: ActivityId$1;
|
|
690
|
+
column: string;
|
|
691
|
+
newValue: unknown;
|
|
692
|
+
}>;
|
|
693
|
+
eventSource?: string;
|
|
694
|
+
} | {
|
|
695
|
+
kind: 'link-create';
|
|
696
|
+
source: ActivityId$1;
|
|
697
|
+
target: ActivityId$1;
|
|
698
|
+
type: LinkType;
|
|
699
|
+
/** Lag in DAYS (public boundary unit; converted to working hours inside). */
|
|
700
|
+
lag: number;
|
|
701
|
+
/** Optional explicit id (tests pass deterministic ids). */
|
|
702
|
+
linkId?: LinkId;
|
|
703
|
+
} | {
|
|
704
|
+
kind: 'link-update';
|
|
705
|
+
linkId: LinkId;
|
|
706
|
+
/** New type. Omit to preserve. */
|
|
707
|
+
type?: LinkType;
|
|
708
|
+
/** New lag in DAYS (public boundary unit). Omit to preserve. */
|
|
709
|
+
lag?: number;
|
|
710
|
+
} | {
|
|
711
|
+
kind: 'link-delete';
|
|
712
|
+
linkId: LinkId;
|
|
713
|
+
} | {
|
|
714
|
+
kind: 'links-batch';
|
|
715
|
+
operations: ReadonlyArray<LinksBatchOperation>;
|
|
716
|
+
} | {
|
|
717
|
+
kind: 'activity-create';
|
|
718
|
+
/** Where to put the new activity. Use `'0'` for root. */
|
|
719
|
+
parentId: ActivityId$1 | '0';
|
|
720
|
+
/**
|
|
721
|
+
* Optional — insert the new activity immediately AFTER this sibling.
|
|
722
|
+
* If omitted (and `beforeSiblingId` also omitted), the new activity
|
|
723
|
+
* is appended as the last child of `parentId`. The sibling must
|
|
724
|
+
* currently be a child of `parentId`, otherwise the dispatch is
|
|
725
|
+
* rejected. Mutually exclusive with `beforeSiblingId`.
|
|
726
|
+
*/
|
|
727
|
+
afterSiblingId?: ActivityId$1 | undefined;
|
|
728
|
+
/**
|
|
729
|
+
* Optional — insert the new activity immediately BEFORE this sibling.
|
|
730
|
+
* Use case: "insert as first child" (`beforeSiblingId = currentFirstChild`)
|
|
731
|
+
* or "insert at position N" (`beforeSiblingId = children[N]`).
|
|
732
|
+
* The sibling must currently be a child of `parentId`, otherwise the
|
|
733
|
+
* dispatch is rejected. Mutually exclusive with `afterSiblingId`.
|
|
734
|
+
*/
|
|
735
|
+
beforeSiblingId?: ActivityId$1 | undefined;
|
|
736
|
+
/**
|
|
737
|
+
* Optional field overrides applied on top of the defaults built by
|
|
738
|
+
* the creation pipeline. Common cases: `text`, `duration`,
|
|
739
|
+
* `start_date`, `constraint_type`, `constraint_date`, `calendar_id`.
|
|
740
|
+
*
|
|
741
|
+
* Only keys that name a real model field are honored — the pipeline
|
|
742
|
+
* drops any incidental non-model key (dhtmlx runtime junk, lightbox UI
|
|
743
|
+
* state) so it can never phantom onto the created activity. `name` is
|
|
744
|
+
* accepted as an alias for `text` (mapped, then dropped as a raw key).
|
|
745
|
+
* See `isDroppableOverrideKey` in `creation/defaults.ts`.
|
|
746
|
+
*/
|
|
747
|
+
overrides?: Partial<Activity>;
|
|
748
|
+
/**
|
|
749
|
+
* Deterministic activity id — used by tests and by replay flows
|
|
750
|
+
* (paste, undo) that need to assign a specific id. If omitted, the
|
|
751
|
+
* core's internal id generator allocates one.
|
|
752
|
+
*/
|
|
753
|
+
activityId?: ActivityId$1;
|
|
754
|
+
/**
|
|
755
|
+
* Where this create originated from — passed through to the
|
|
756
|
+
* `schedule_activity_creation` Amplitude event. Free-form string
|
|
757
|
+
* (matches legacy `INSERT_EVENT_SOURCES`).
|
|
758
|
+
*/
|
|
759
|
+
eventSource?: string | undefined;
|
|
760
|
+
} | {
|
|
761
|
+
kind: 'activity-delete';
|
|
762
|
+
/**
|
|
763
|
+
* IDs to delete. Each id triggers a cascade delete of its entire
|
|
764
|
+
* subtree. Links incident to any deleted activity are removed too.
|
|
765
|
+
* Operation is atomic — if any id is missing, the whole dispatch is
|
|
766
|
+
* rejected before mutating anything.
|
|
767
|
+
*/
|
|
768
|
+
activityIds: ReadonlyArray<ActivityId$1>;
|
|
769
|
+
eventSource?: string;
|
|
770
|
+
} | {
|
|
771
|
+
kind: 'activity-move';
|
|
772
|
+
/**
|
|
773
|
+
* The activity to move. Single-id only — for batch reorder use
|
|
774
|
+
* multiple dispatches (each one observes the post-previous state).
|
|
775
|
+
*/
|
|
776
|
+
activityId: ActivityId$1;
|
|
777
|
+
/** Destination parent. Same as current parent = reorder within. */
|
|
778
|
+
parentId: ActivityId$1 | '0';
|
|
779
|
+
/**
|
|
780
|
+
* Optional — position the moved activity immediately below this
|
|
781
|
+
* sibling. If omitted, appended as the last child of `parentId`.
|
|
782
|
+
* The sibling must currently be a child of `parentId`; otherwise
|
|
783
|
+
* the dispatch is rejected.
|
|
784
|
+
*/
|
|
785
|
+
afterSiblingId?: ActivityId$1;
|
|
786
|
+
eventSource?: string;
|
|
787
|
+
} | {
|
|
788
|
+
kind: 'activity-indent';
|
|
789
|
+
/**
|
|
790
|
+
* IDs to indent. Multi-select honors chain semantics: each id is
|
|
791
|
+
* moved under its closest previous sibling that is NOT in the
|
|
792
|
+
* selected set, so consecutive selected siblings all land under the
|
|
793
|
+
* same anchor (matches legacy `findPreviousNonSelectedSibling`).
|
|
794
|
+
*/
|
|
795
|
+
activityIds: ReadonlyArray<ActivityId$1>;
|
|
796
|
+
eventSource?: string;
|
|
797
|
+
} | {
|
|
798
|
+
kind: 'activity-outdent';
|
|
799
|
+
/**
|
|
800
|
+
* IDs to outdent. Multi-select preserves relative order among the
|
|
801
|
+
* selected siblings via initial-index capture (matches legacy
|
|
802
|
+
* `initialFirstSiblings` + `initialTaskIndexes` pre-loop setup).
|
|
803
|
+
*/
|
|
804
|
+
activityIds: ReadonlyArray<ActivityId$1>;
|
|
805
|
+
eventSource?: string;
|
|
806
|
+
} | {
|
|
807
|
+
/**
|
|
808
|
+
* Set progress to 0 or 100 with recursive cascade semantics.
|
|
809
|
+
*
|
|
810
|
+
* Triggered by the schedule's "complete" / "uncomplete" buttons.
|
|
811
|
+
* **Distinct intent from `inline-edit`** — bypasses the
|
|
812
|
+
* `isSummaryActivity` gate of `progressPipeline.canEdit` because
|
|
813
|
+
* the user is explicitly invoking the recursive operation
|
|
814
|
+
* ("complete this parent and all its descendants"), not editing
|
|
815
|
+
* a single cell.
|
|
816
|
+
*
|
|
817
|
+
* Inline edits on the progress column still use `inline-edit` with
|
|
818
|
+
* full `canEdit`/`validate` gating (which blocks summary activities
|
|
819
|
+
* by design). See [[complete activity business rules]] R8 and the
|
|
820
|
+
* bifurcation rationale.
|
|
821
|
+
*
|
|
822
|
+
* Internally reuses the `progressPipeline.transform` so cascade,
|
|
823
|
+
* rollup, tracking and visualization are identical to inline-edit
|
|
824
|
+
* with newValue=0 or newValue=100.
|
|
825
|
+
*/
|
|
826
|
+
kind: 'activity-set-progress';
|
|
827
|
+
activityId: ActivityId$1;
|
|
828
|
+
/** Strictly 0 (uncomplete) or 100 (complete). */
|
|
829
|
+
newValue: 0 | 100;
|
|
830
|
+
eventSource?: string;
|
|
831
|
+
} | {
|
|
832
|
+
/**
|
|
833
|
+
* Checkbox selection toggle (2026-06-06). The core is the
|
|
834
|
+
* authority of the check state (`checked` / `visibleChecked` /
|
|
835
|
+
* `mustApplyVisibleChecked` written as a single bit): it runs the
|
|
836
|
+
* full propagation (descendants cascade, frozen partition with
|
|
837
|
+
* parent blocking, unified upward pass — see
|
|
838
|
+
* `internal/selection/compute-selection-update.ts`) and emits a
|
|
839
|
+
* prop-only ChangeSet the bridge applies in bulk to DHTMLX
|
|
840
|
+
* (silent store write + viewport repaint). No autoscheduler, no
|
|
841
|
+
* critical path.
|
|
842
|
+
*/
|
|
843
|
+
kind: 'selection-toggle';
|
|
844
|
+
activityId: ActivityId$1;
|
|
845
|
+
/** Checkbox state AFTER the user click. */
|
|
846
|
+
isChecked: boolean;
|
|
847
|
+
/**
|
|
848
|
+
* Ids frozen by pending SIRs, computed by the caller from the
|
|
849
|
+
* runtime (DHTMLX task props — the core's own `pendingRequests`
|
|
850
|
+
* is load-time-stale until TODO #10). When omitted, the core
|
|
851
|
+
* falls back to its own snapshots.
|
|
852
|
+
*/
|
|
853
|
+
frozenIds?: ReadonlyArray<ActivityId$1>;
|
|
854
|
+
/**
|
|
855
|
+
* Enable the upward auto-check of ancestors when all their
|
|
856
|
+
* checkable children become selected. Mirrors the (inverted)
|
|
857
|
+
* CHECKBOX_NOT_AUTOMATIC_PARENT_SELECTION flag. Default: false.
|
|
858
|
+
*/
|
|
859
|
+
autoCheckParents?: boolean;
|
|
860
|
+
eventSource?: string;
|
|
861
|
+
} | {
|
|
862
|
+
/**
|
|
863
|
+
* Replace the whole selection with an exact id set — no
|
|
864
|
+
* propagation. Used by clear / block-selection / external sync.
|
|
865
|
+
* Unknown ids are ignored. Same prop-only ChangeSet contract as
|
|
866
|
+
* `selection-toggle`.
|
|
867
|
+
*/
|
|
868
|
+
kind: 'selection-replace';
|
|
869
|
+
activityIds: ReadonlyArray<ActivityId$1>;
|
|
870
|
+
eventSource?: string;
|
|
871
|
+
} | {
|
|
872
|
+
/**
|
|
873
|
+
* View-model visibility set (Fase 1, 2026-07-04). ABSOLUTE set: the
|
|
874
|
+
* UI sends the COMPLETE list of ids visible after its filters run;
|
|
875
|
+
* the core diffs against the current `visible` bits (an id changes
|
|
876
|
+
* when `(visible !== false) !== willBeVisible`) and emits a
|
|
877
|
+
* prop-only ChangeSet carrying ONLY the `visible` field for the
|
|
878
|
+
* activities that changed. Unknown ids are ignored. Same prop-only
|
|
879
|
+
* contract as `selection-replace`: no autoscheduler, no critical
|
|
880
|
+
* path, no undo step. Idempotent.
|
|
881
|
+
*/
|
|
882
|
+
kind: 'visibility-set';
|
|
883
|
+
visibleIds: ReadonlyArray<ActivityId$1>;
|
|
884
|
+
eventSource?: string;
|
|
885
|
+
} | {
|
|
886
|
+
/**
|
|
887
|
+
* Sync the pending-SIR set for one activity into the core
|
|
888
|
+
* (2026-06-08). ABSOLUTE set: the bridge sends the activity's current
|
|
889
|
+
* pending requests whenever a SIR is created/resolved in the front,
|
|
890
|
+
* so the core's freeze bit stays fresh (closes TODO #10) and is the
|
|
891
|
+
* standalone authority for selection gating + auto-reject. Prop-only
|
|
892
|
+
* ChangeSet; no autoscheduler, no CP. Idempotent.
|
|
893
|
+
*/
|
|
894
|
+
kind: 'sir-sync';
|
|
895
|
+
activityId: ActivityId$1;
|
|
896
|
+
pendingRequests: ReadonlyArray<PendingRequest>;
|
|
897
|
+
eventSource?: string;
|
|
898
|
+
} | {
|
|
899
|
+
/**
|
|
900
|
+
* Bulk paste (2026-06-08). Creates N activities (preserving the
|
|
901
|
+
* copied subtree hierarchy) plus M internal links in a SINGLE
|
|
902
|
+
* atomic ChangeSet — the future unit of undo. Replaces the
|
|
903
|
+
* DHTMLX-side paste that bypassed the core via `gantt.isPasting`.
|
|
904
|
+
*
|
|
905
|
+
* The caller (bridge) supplies, per activity, the field
|
|
906
|
+
* `overrides` already shaped by the paste prepare step (resets +
|
|
907
|
+
* validated catalogs). The core allocates ids/uids, re-hangs the
|
|
908
|
+
* hierarchy via `originalId → newId`, promotes parents, runs the
|
|
909
|
+
* hh/cost cascade + correlative sweep + autoscheduler ONCE at the
|
|
910
|
+
* end of the batch (not per activity).
|
|
911
|
+
*
|
|
912
|
+
* See [[COPY_PASTE_CORE_MIGRATION_DESIGN]].
|
|
913
|
+
*/
|
|
914
|
+
kind: 'activity-paste';
|
|
915
|
+
/**
|
|
916
|
+
* Where the pasted roots land. `{ parentId, index }` pastes INSIDE
|
|
917
|
+
* the reference (index 0 = first child). `{ afterSiblingId }`
|
|
918
|
+
* pastes as a sibling immediately below the reference; the parent
|
|
919
|
+
* is derived from the sibling.
|
|
920
|
+
*/
|
|
921
|
+
destination: {
|
|
922
|
+
parentId: ActivityId$1 | '0';
|
|
923
|
+
index: number;
|
|
924
|
+
} | {
|
|
925
|
+
afterSiblingId: ActivityId$1;
|
|
926
|
+
};
|
|
927
|
+
/**
|
|
928
|
+
* The activity the paste is anchored to (the single selected row).
|
|
929
|
+
* Used as the custom_id reference for pasted children. Replaces
|
|
930
|
+
* the legacy `gantt.pasteReferenceActivity` instance flag.
|
|
931
|
+
*/
|
|
932
|
+
referenceActivityId: ActivityId$1;
|
|
933
|
+
/**
|
|
934
|
+
* Activities to create, in tree order (parents before children).
|
|
935
|
+
* `originalId` / `originalParentId` are the ids from the copied
|
|
936
|
+
* payload; the core remaps them to freshly allocated ids and uses
|
|
937
|
+
* the remap to re-hang the hierarchy and the links.
|
|
938
|
+
*/
|
|
939
|
+
activities: ReadonlyArray<PastedActivityInput>;
|
|
940
|
+
/**
|
|
941
|
+
* Internal links to recreate. `source`/`target` reference the
|
|
942
|
+
* ORIGINAL (copied) ids; links with an endpoint outside the
|
|
943
|
+
* pasted set are dropped (dangling).
|
|
944
|
+
*/
|
|
945
|
+
links: ReadonlyArray<PastedLinkInput>;
|
|
946
|
+
eventSource?: string;
|
|
947
|
+
};
|
|
948
|
+
interface PastedActivityInput {
|
|
949
|
+
readonly originalId: ActivityId$1;
|
|
950
|
+
readonly originalParentId: ActivityId$1 | '0';
|
|
951
|
+
readonly overrides: Partial<Activity>;
|
|
952
|
+
}
|
|
953
|
+
interface PastedLinkInput {
|
|
954
|
+
readonly source: ActivityId$1;
|
|
955
|
+
readonly target: ActivityId$1;
|
|
956
|
+
readonly type: LinkType;
|
|
957
|
+
/** Lag in DAYS (public boundary unit; converted to working hours inside). */
|
|
958
|
+
readonly lag: number;
|
|
959
|
+
}
|
|
960
|
+
/** Unit of duration/lag at the public boundary. Default 'days'. */
|
|
961
|
+
type OutputUnit = 'days' | 'hours';
|
|
962
|
+
interface DispatchOptions {
|
|
963
|
+
/** Skip the autoscheduler pass after the mutation. Default: false. */
|
|
964
|
+
skipAutoSchedule?: boolean;
|
|
965
|
+
/** Skip the CP recompute after the mutation. Default: false. */
|
|
966
|
+
skipCriticalPath?: boolean;
|
|
967
|
+
/** 'days' default; the DHTMLX bridge passes 'hours' to skip conversion. */
|
|
968
|
+
outputUnit?: OutputUnit;
|
|
969
|
+
/**
|
|
970
|
+
* Unit of the INPUT `newValue` for unit-bearing inline-edit columns (today:
|
|
971
|
+
* duration). Default 'days' (public contract; card + tests send days). The
|
|
972
|
+
* DHTMLX grid editor produces the store unit (HOURS via formatter.parse), so
|
|
973
|
+
* the bridge passes 'hours' for those edits → the pipeline skips the days→hours
|
|
974
|
+
* conversion instead of double-converting.
|
|
975
|
+
*/
|
|
976
|
+
inputUnit?: OutputUnit;
|
|
977
|
+
}
|
|
978
|
+
type DispatchResult = {
|
|
979
|
+
ok: true;
|
|
980
|
+
changes: ChangeSet;
|
|
981
|
+
/**
|
|
982
|
+
* Solo los intents batch (`dates-batch` / `bulk-edit`): resultado
|
|
983
|
+
* POR EDIT, en el orden de `edits`. Un edit rechazado no aborta a
|
|
984
|
+
* los demás (semántica paste); el bridge re-proyecta solo los
|
|
985
|
+
* rechazados.
|
|
986
|
+
*/
|
|
987
|
+
verdicts?: ReadonlyArray<DatesBatchVerdict>;
|
|
988
|
+
/**
|
|
989
|
+
* INTERNAL (undo / B4). Full pre-mutation snapshot of every
|
|
990
|
+
* deleted/structurally-removed activity row, keyed by STRING id. The
|
|
991
|
+
* ChangeSet emits deleted rows as `{ kind:'deleted', after:null }` with no
|
|
992
|
+
* `before`, so resurrection on undo needs this. Populated by structural
|
|
993
|
+
* handlers (delete/move/paste); stripped before the result reaches the
|
|
994
|
+
* bridge in the Wave-3 integration.
|
|
995
|
+
*/
|
|
996
|
+
__beforeSnap?: ReadonlyMap<string, EngineActivitySnapshot>;
|
|
997
|
+
/**
|
|
998
|
+
* INTERNAL (undo / B4). Incident links of deleted activities, keyed by
|
|
999
|
+
* STRING id (EntityChange link ids are String()-normalized). Carries the
|
|
1000
|
+
* engine `LinkSnapshot` (lag in working hours) for unit-correct re-add.
|
|
1001
|
+
*/
|
|
1002
|
+
__beforeLinks?: ReadonlyMap<string, LinkSnapshot>;
|
|
1003
|
+
} | {
|
|
1004
|
+
ok: false;
|
|
1005
|
+
reason: string;
|
|
1006
|
+
alertKey?: string | undefined;
|
|
1007
|
+
};
|
|
1008
|
+
type DatesBatchVerdict = {
|
|
1009
|
+
activityId: string;
|
|
1010
|
+
ok: true;
|
|
1011
|
+
} | {
|
|
1012
|
+
activityId: string;
|
|
1013
|
+
ok: false;
|
|
1014
|
+
reason: string;
|
|
1015
|
+
alertKey?: string | undefined;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
interface TrackingEvent {
|
|
1019
|
+
name: string;
|
|
1020
|
+
properties: Record<string, unknown>;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Side effects a dispatch emits that are NOT entity-field changes and NOT
|
|
1024
|
+
* Amplitude tracking — commands the bridge must act on. Today: SIR
|
|
1025
|
+
* auto-reject (a frozen activity's dates changed or it was deleted, so its
|
|
1026
|
+
* pending SIR must be rejected). The core owns the rule; the bridge consumes
|
|
1027
|
+
* the effect (Redux REJECTED; durable persistence rides the activity save).
|
|
1028
|
+
* See `SIR_MAP.md` §10.
|
|
1029
|
+
*/
|
|
1030
|
+
type ScheduleEffect = {
|
|
1031
|
+
kind: 'sir-auto-reject';
|
|
1032
|
+
activityId: ActivityId$1;
|
|
1033
|
+
/** Backend SIR id == the front Redux item id (see parseFromBackend A1). */
|
|
1034
|
+
sirId: string;
|
|
1035
|
+
reason: 'date_changed' | 'activity_deleted';
|
|
1036
|
+
};
|
|
1037
|
+
/**
|
|
1038
|
+
* A direct user edit violated the activity's own (pre-edit) constraint. Per
|
|
1039
|
+
* the canonical model (PRD constraint violations, R2/R3): the edit APPLIES
|
|
1040
|
+
* anyway — the warning is data for the UI (constraint validation modal),
|
|
1041
|
+
* never a rejection, and it is only emitted from direct edits (the
|
|
1042
|
+
* autoscheduler never violates, R1). Decision D-A 2026-07-04.
|
|
1043
|
+
*/
|
|
1044
|
+
interface ConstraintWarning {
|
|
1045
|
+
kind: 'constraint_violation';
|
|
1046
|
+
activityId: ActivityId$1;
|
|
1047
|
+
constraintType: ConstraintType;
|
|
1048
|
+
constraintDate: Date;
|
|
1049
|
+
/** The projected date that violated (start or end, per constraint type). */
|
|
1050
|
+
projectedDate: Date;
|
|
1051
|
+
messageKey: string;
|
|
1052
|
+
}
|
|
1053
|
+
interface ChangeSet {
|
|
1054
|
+
source: DispatchAction | {
|
|
1055
|
+
kind: 'init';
|
|
1056
|
+
} | {
|
|
1057
|
+
kind: 'undo';
|
|
1058
|
+
} | {
|
|
1059
|
+
kind: 'redo';
|
|
1060
|
+
};
|
|
1061
|
+
activities: ReadonlyArray<EntityChange<Activity>>;
|
|
1062
|
+
links: ReadonlyArray<EntityChange<Link>>;
|
|
1063
|
+
calendars: ReadonlyArray<EntityChange<Calendar>>;
|
|
1064
|
+
trackingEvents: ReadonlyArray<TrackingEvent>;
|
|
1065
|
+
/**
|
|
1066
|
+
* Optional — present only when the dispatch produced side effects. Builders
|
|
1067
|
+
* that emit none omit it; consumers read `changeSet.effects ?? []`. Kept
|
|
1068
|
+
* optional to avoid churning every ChangeSet construction site. SIR (A4).
|
|
1069
|
+
*/
|
|
1070
|
+
effects?: ReadonlyArray<ScheduleEffect>;
|
|
1071
|
+
/**
|
|
1072
|
+
* Optional — present only when a direct edit violated a constraint (see
|
|
1073
|
+
* `ConstraintWarning`). Consumers read `changeSet.warnings ?? []`.
|
|
1074
|
+
*/
|
|
1075
|
+
warnings?: ReadonlyArray<ConstraintWarning>;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
interface ProjectWorkHours {
|
|
1079
|
+
/** Earliest start of the working day across the week, `"HH:MM"`. */
|
|
1080
|
+
startHour: string;
|
|
1081
|
+
/** Latest end of the working day across the week, `"HH:MM"`. */
|
|
1082
|
+
endHour: string;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Backend payload shapes — exactly as the Outbuild server returns.
|
|
1087
|
+
*
|
|
1088
|
+
* The core accepts these directly in the constructor and parses them into
|
|
1089
|
+
* normalized internal types. Keeping these as the public input type means
|
|
1090
|
+
* any consumer of the schedule API (frontend, node_server, future) speaks
|
|
1091
|
+
* the same contract.
|
|
1092
|
+
*
|
|
1093
|
+
* Validated against the real mocks in
|
|
1094
|
+
* `packages/schedule-core/src/testing/projects/startDate/`.
|
|
1095
|
+
*/
|
|
1096
|
+
/** Calendar shift definition — one shift per day, CSV-encoded strings. */
|
|
1097
|
+
interface BackendShift {
|
|
1098
|
+
id: number;
|
|
1099
|
+
/**
|
|
1100
|
+
* Encoded shift string for the week:
|
|
1101
|
+
* `false,8:00,8:00,8:00,8:00,8:00,false-false,16:00,...,false`
|
|
1102
|
+
* left side = starts, right side = ends, each CSV is Sun..Sat.
|
|
1103
|
+
* `false` means non-working day.
|
|
1104
|
+
*/
|
|
1105
|
+
shift_string: string;
|
|
1106
|
+
correlative_id: number;
|
|
1107
|
+
createdAt: string;
|
|
1108
|
+
updatedAt: string;
|
|
1109
|
+
calendarId: number;
|
|
1110
|
+
calendarexceptiondayId: number | null;
|
|
1111
|
+
}
|
|
1112
|
+
/** Backend calendar exception (per-date override). */
|
|
1113
|
+
interface BackendCalendarException {
|
|
1114
|
+
id: number;
|
|
1115
|
+
date: string;
|
|
1116
|
+
/** Optional shifts for this specific exception. */
|
|
1117
|
+
shifts?: BackendShift[];
|
|
1118
|
+
[key: string]: unknown;
|
|
1119
|
+
}
|
|
1120
|
+
/** Calendar as returned by the server. */
|
|
1121
|
+
interface BackendCalendar {
|
|
1122
|
+
id: number;
|
|
1123
|
+
name: string;
|
|
1124
|
+
unique_id: number;
|
|
1125
|
+
is_default: boolean;
|
|
1126
|
+
/** CSV of Sun..Sat: `"0,1,1,1,1,1,0"` (0 = non-working, 1 = working). */
|
|
1127
|
+
working_days: string;
|
|
1128
|
+
/** CSV of shift starts per day: `"false,8:00,8:00,8:00,8:00,8:00,false"`. */
|
|
1129
|
+
shift_start: string;
|
|
1130
|
+
/** CSV of shift ends per day: `"false,16:00,...,false"`. */
|
|
1131
|
+
shift_end: string;
|
|
1132
|
+
exceptiondays: BackendCalendarException[];
|
|
1133
|
+
shifts: BackendShift[];
|
|
1134
|
+
status: boolean;
|
|
1135
|
+
companyId: number;
|
|
1136
|
+
ganttId: number | null;
|
|
1137
|
+
userId: number;
|
|
1138
|
+
sectorId: number;
|
|
1139
|
+
createdAt: string;
|
|
1140
|
+
updatedAt: string;
|
|
1141
|
+
[key: string]: unknown;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Activity exactly as the Outbuild server returns it — the raw, untrusted
|
|
1145
|
+
* input boundary. Dates are strings, `duration` is in backend DAYS, and field
|
|
1146
|
+
* names are the backend's (snake_case, `unique_id`, `parent_id`, `name`).
|
|
1147
|
+
*
|
|
1148
|
+
* `parseFromBackend` is the only place that consumes it: it validates and
|
|
1149
|
+
* normalizes each row into the internal model. Nothing else in the core should
|
|
1150
|
+
* read a `BackendActivityInput` after parse.
|
|
1151
|
+
*/
|
|
1152
|
+
interface BackendActivityInput {
|
|
1153
|
+
/** Database row id. Distinct from `unique_id`. */
|
|
1154
|
+
id: number;
|
|
1155
|
+
/** Canonical schedule id (used by links and DHTMLX). String-encoded number. */
|
|
1156
|
+
unique_id: string;
|
|
1157
|
+
/** Parent's `unique_id`, or `"0"` for root. */
|
|
1158
|
+
parent_id: string;
|
|
1159
|
+
unique_correlative_id: string;
|
|
1160
|
+
correlative_id: number;
|
|
1161
|
+
name: string;
|
|
1162
|
+
description: string;
|
|
1163
|
+
type: 'project' | 'task' | 'milestone';
|
|
1164
|
+
/** Format: `"YYYY/MM/DD H:MM"` (server local, see dateFormat in sector). */
|
|
1165
|
+
start_date: string;
|
|
1166
|
+
end_date: string;
|
|
1167
|
+
duration: number;
|
|
1168
|
+
/**
|
|
1169
|
+
* Constraint label in plain English: `"As soon As Possible"`,
|
|
1170
|
+
* `"Start no earlier than"`, etc. Mapped to internal `ConstraintType`.
|
|
1171
|
+
*/
|
|
1172
|
+
constraint: string;
|
|
1173
|
+
/** Optional constraint date — server uses ISO 8601 or null. */
|
|
1174
|
+
constraint_date?: string | null;
|
|
1175
|
+
progress: number;
|
|
1176
|
+
cost: number;
|
|
1177
|
+
used_cost: number;
|
|
1178
|
+
real_cost: number;
|
|
1179
|
+
hhWorkTime: number;
|
|
1180
|
+
real_work: number;
|
|
1181
|
+
ponderator: number;
|
|
1182
|
+
hasCustomPonderator: boolean;
|
|
1183
|
+
has_childs: boolean;
|
|
1184
|
+
isOnLookahead: boolean;
|
|
1185
|
+
sumOfDurationRecursively: number;
|
|
1186
|
+
freeSlack: number | null;
|
|
1187
|
+
is_critical: boolean;
|
|
1188
|
+
newActivitiesArray: string;
|
|
1189
|
+
hasNewActivities: boolean;
|
|
1190
|
+
isNewActivity: true | null;
|
|
1191
|
+
custom_id: string | null;
|
|
1192
|
+
ganttId: number;
|
|
1193
|
+
sectorId: number;
|
|
1194
|
+
companyId: number;
|
|
1195
|
+
calendarId: number;
|
|
1196
|
+
subcontractId: number | null;
|
|
1197
|
+
/**
|
|
1198
|
+
* Visibility bit of the legacy view-model. Historically NEVER sent by the
|
|
1199
|
+
* backend (0 of ~105k activities across the captured seeds carry it) — the
|
|
1200
|
+
* legacy client hardcoded `should_be_showed: true` at parse. Declared so
|
|
1201
|
+
* `parseActivity` can map it to `visible` if it ever arrives; absent/null
|
|
1202
|
+
* defaults to visible.
|
|
1203
|
+
*/
|
|
1204
|
+
should_be_showed?: boolean;
|
|
1205
|
+
tasks: unknown[];
|
|
1206
|
+
responsables: unknown[];
|
|
1207
|
+
tags: unknown[];
|
|
1208
|
+
activitymodifications: unknown[];
|
|
1209
|
+
/** Pending SIRs for this activity — schedule load sends `{ id }` only. */
|
|
1210
|
+
pendingRequests: ReadonlyArray<{
|
|
1211
|
+
id: number | string;
|
|
1212
|
+
}>;
|
|
1213
|
+
subcontract: unknown;
|
|
1214
|
+
createdAt: string;
|
|
1215
|
+
updatedAt: string;
|
|
1216
|
+
/** Custom fields the backend may include — passed through opaquely. */
|
|
1217
|
+
[key: string]: unknown;
|
|
1218
|
+
}
|
|
1219
|
+
/** Link as returned by the server. */
|
|
1220
|
+
interface BackendLink {
|
|
1221
|
+
id: number | string;
|
|
1222
|
+
source: number | string;
|
|
1223
|
+
target: number | string;
|
|
1224
|
+
/** DHTMLX kind: `"0"` FS, `"1"` SS, `"2"` FF, `"3"` SF. */
|
|
1225
|
+
type: '0' | '1' | '2' | '3';
|
|
1226
|
+
lag: number;
|
|
1227
|
+
[key: string]: unknown;
|
|
1228
|
+
}
|
|
1229
|
+
/** Sector metadata — project-level config from the server. */
|
|
1230
|
+
interface BackendSector {
|
|
1231
|
+
id: number;
|
|
1232
|
+
name: string;
|
|
1233
|
+
description: string | null;
|
|
1234
|
+
status: boolean;
|
|
1235
|
+
set_current: boolean;
|
|
1236
|
+
visible: boolean;
|
|
1237
|
+
order: number;
|
|
1238
|
+
productive: boolean;
|
|
1239
|
+
hoursPerDay: number;
|
|
1240
|
+
hoursPerWeek: number;
|
|
1241
|
+
accumulatedDuration: number;
|
|
1242
|
+
/** Date format string used by the backend, e.g. `"DD/MM/YY hh:mm"`. */
|
|
1243
|
+
dateFormat: string;
|
|
1244
|
+
didCloseWeek: string | null;
|
|
1245
|
+
currentClosedWeek: string | null;
|
|
1246
|
+
expectedProgress: number;
|
|
1247
|
+
/**
|
|
1248
|
+
* Legacy Primavera-import flag. When true, the stored `duration` of each
|
|
1249
|
+
* activity may not match the calendar (Primavera stores end-driven
|
|
1250
|
+
* durations); the core recomputes duration from start/end at load. ~218
|
|
1251
|
+
* production sectors carry it (active). See `Obsidian:newPlanSchedule/CALENDARS.md`.
|
|
1252
|
+
*/
|
|
1253
|
+
update_duration_for_primavera_for_end_date?: boolean | null;
|
|
1254
|
+
companyId: number;
|
|
1255
|
+
projectId: number;
|
|
1256
|
+
version: string;
|
|
1257
|
+
createdAt: string;
|
|
1258
|
+
updatedAt: string;
|
|
1259
|
+
/** Calendars nested inside sector when the sector-fetch endpoint is used. */
|
|
1260
|
+
calendars?: BackendCalendar[];
|
|
1261
|
+
[key: string]: unknown;
|
|
1262
|
+
}
|
|
1263
|
+
/** Optional sibling payloads the API often returns alongside the sector. */
|
|
1264
|
+
interface BackendScheduleImpactRequests {
|
|
1265
|
+
pending: unknown[];
|
|
1266
|
+
approved: unknown[];
|
|
1267
|
+
rejected: unknown[];
|
|
1268
|
+
}
|
|
1269
|
+
/** Complete server payload accepted by `ScheduleCore`. */
|
|
1270
|
+
interface BackendInput {
|
|
1271
|
+
sector: BackendSector;
|
|
1272
|
+
activities: BackendActivityInput[];
|
|
1273
|
+
links: BackendLink[];
|
|
1274
|
+
/**
|
|
1275
|
+
* Calendars come either inside `sector.calendars` or as a sibling array.
|
|
1276
|
+
* If both are present, the sibling array wins.
|
|
1277
|
+
*/
|
|
1278
|
+
calendars?: BackendCalendar[] | undefined;
|
|
1279
|
+
/**
|
|
1280
|
+
* Baseline calendars, RAW from the backend's `showBaseCalendar` endpoint
|
|
1281
|
+
* (exceptions under `baseexceptiondays`, `is_default`, no `-base` suffix).
|
|
1282
|
+
* Parsed into a separate `ParsedInput.baseCalendars` collection — never
|
|
1283
|
+
* mixed into the scheduling calendars. The core owns their reshape /
|
|
1284
|
+
* differentiation (previously done by the frontend `refreshCalendarsRewrite`).
|
|
1285
|
+
*/
|
|
1286
|
+
baseCalendars?: BackendCalendar[] | undefined;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
declare const COLUMN: {
|
|
1290
|
+
readonly REAL_WORK: "real_work";
|
|
1291
|
+
readonly REAL_COST: "real_cost";
|
|
1292
|
+
readonly EXPECTED_PROGRESS: "expected_progress";
|
|
1293
|
+
readonly EXPECTED_PROGRESS_BASE: "expected_progress_base";
|
|
1294
|
+
readonly START_BASE: "start_base";
|
|
1295
|
+
readonly END_BASE: "end_base";
|
|
1296
|
+
readonly DURATION_BASE: "duration_base";
|
|
1297
|
+
readonly COST_BASE: "cost_base";
|
|
1298
|
+
readonly WORK_BASE: "work_base";
|
|
1299
|
+
readonly CALENDAR_DURATION: "calendarDuration";
|
|
1300
|
+
readonly IS_CRITICAL: "is_critical";
|
|
1301
|
+
readonly EARLY_START: "earlyStart";
|
|
1302
|
+
readonly EARLY_FINISH: "earlyFinish";
|
|
1303
|
+
readonly LATE_START: "lateStart";
|
|
1304
|
+
readonly LATE_FINISH: "lateFinish";
|
|
1305
|
+
readonly FREE_SLACK: "freeSlack";
|
|
1306
|
+
readonly TOTAL_SLACK: "totalSlack";
|
|
1307
|
+
readonly CORRELATIVE_ID: "correlative_id";
|
|
1308
|
+
readonly UNIQUE_CORRELATIVE_ID: "unique_correlative_id";
|
|
1309
|
+
readonly PONDERATOR: "ponderator";
|
|
1310
|
+
readonly STATUS: "status";
|
|
1311
|
+
readonly TEXT: "text";
|
|
1312
|
+
readonly DESCRIPTION: "description";
|
|
1313
|
+
readonly DURATION: "duration";
|
|
1314
|
+
readonly PROGRESS: "progress";
|
|
1315
|
+
readonly COST: "cost";
|
|
1316
|
+
readonly USED_COST: "used_cost";
|
|
1317
|
+
readonly HH_WORK_TIME: "hhWorkTime";
|
|
1318
|
+
readonly START_DATE: "start_date";
|
|
1319
|
+
readonly END_DATE: "end_date";
|
|
1320
|
+
readonly CONSTRAINT_TYPE: "constraint_type";
|
|
1321
|
+
readonly CONSTRAINT_DATE: "constraint_date";
|
|
1322
|
+
readonly CALENDAR_ID: "calendar_id";
|
|
1323
|
+
readonly CUSTOM_ID: "custom_id";
|
|
1324
|
+
readonly SUBCONTRACT_ID: "subcontractId";
|
|
1325
|
+
readonly RESPONSABLES: "responsables";
|
|
1326
|
+
readonly TAGS: "tags";
|
|
1327
|
+
readonly CUSTOM_PREDECESSORS: "custom_predecessors";
|
|
1328
|
+
readonly CUSTOM_SUCESSORS: "custom_sucessors";
|
|
1329
|
+
};
|
|
1330
|
+
type ColumnName = (typeof COLUMN)[keyof typeof COLUMN];
|
|
1331
|
+
|
|
1332
|
+
type ActivityId = string;
|
|
1333
|
+
type CalendarId = string;
|
|
1334
|
+
|
|
1335
|
+
/**
|
|
1336
|
+
* Role interfaces (Interface Segregation).
|
|
1337
|
+
*
|
|
1338
|
+
* Consumers receive the smallest role they need instead of the whole
|
|
1339
|
+
* `ScheduleState`. A pure-read consumer takes `ActivityReader`; a
|
|
1340
|
+
* read+write consumer takes `ActivityWriter`; a consumer that drives the
|
|
1341
|
+
* per-dispatch write journal takes `WriteJournal`. `ScheduleState`
|
|
1342
|
+
* implements all of them, so passing it anywhere a role is expected is
|
|
1343
|
+
* still valid — the role just narrows what the callee is allowed to touch.
|
|
1344
|
+
*
|
|
1345
|
+
* Signatures here are copied verbatim from `ScheduleState`; the class is
|
|
1346
|
+
* the source of truth. If the class changes a member, change the role to
|
|
1347
|
+
* match (not the other way around).
|
|
1348
|
+
*/
|
|
1349
|
+
|
|
1350
|
+
/** READ surface over the activity/link store and the hierarchy index. */
|
|
1351
|
+
interface ActivityReader {
|
|
1352
|
+
getActivity(activityId: ActivityId$2): EngineActivitySnapshot | null;
|
|
1353
|
+
getAllActivities(): ReadonlyArray<EngineActivitySnapshot>;
|
|
1354
|
+
getAllLinks(): LinkSnapshot[];
|
|
1355
|
+
getLink(linkId: LinkId$1): LinkSnapshot | null;
|
|
1356
|
+
activityExists(activityId: ActivityId$2): boolean;
|
|
1357
|
+
getChildren(parentId: ActivityId$2): ActivityId$2[];
|
|
1358
|
+
getParent(activityId: ActivityId$2): EngineActivitySnapshot | null;
|
|
1359
|
+
getParentId(activityId: ActivityId$2): ActivityId$2 | 0;
|
|
1360
|
+
isChildOf(childId: ActivityId$2, parentId: ActivityId$2): boolean;
|
|
1361
|
+
getAllIds(): readonly ActivityId$2[];
|
|
1362
|
+
activityCount(): number;
|
|
1363
|
+
forEachActivity(visit: (activity: EngineActivitySnapshot, id: ActivityId$2) => void): void;
|
|
1364
|
+
}
|
|
1365
|
+
/** READ + WRITE surface over the store. */
|
|
1366
|
+
interface ActivityWriter extends ActivityReader {
|
|
1367
|
+
setActivityField<Field extends keyof EngineActivitySnapshot>(activityId: ActivityId$2, field: Field, value: EngineActivitySnapshot[Field]): void;
|
|
1368
|
+
/** Bulk write of several fields of one activity in a single getActivity +
|
|
1369
|
+
* write-capture note (vs N setActivityField calls). */
|
|
1370
|
+
setActivityFields(activityId: ActivityId$2, fields: Partial<EngineActivitySnapshot>): void;
|
|
1371
|
+
addLink(link: LinkSnapshot): void;
|
|
1372
|
+
removeLink(linkId: LinkId$1): void;
|
|
1373
|
+
setLinkField<Field extends keyof LinkSnapshot>(linkId: LinkId$1, field: Field, value: LinkSnapshot[Field]): void;
|
|
1374
|
+
addActivity(activity: EngineActivitySnapshot): void;
|
|
1375
|
+
removeActivity(activityId: ActivityId$2): void;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Injected reporting port. The core is UI-agnostic: it cannot `console.*`,
|
|
1380
|
+
* show UI, or know about Sentry. When it hits a recoverable data-quality
|
|
1381
|
+
* issue (`warn`) or a failure it could not complete (`error`), it calls this
|
|
1382
|
+
* port; the consumer (react_client) implements it and decides the outcome
|
|
1383
|
+
* (Sentry, toast, nothing). Same injected-port pattern as `AutoSchedulerPort`.
|
|
1384
|
+
*/
|
|
1385
|
+
interface ScheduleCoreReporter {
|
|
1386
|
+
/** Recoverable data-quality issue; the core continues with its fallback. */
|
|
1387
|
+
warn(message: string, context?: unknown): void;
|
|
1388
|
+
/** A real failure the core could not complete. */
|
|
1389
|
+
error(message: string, cause?: unknown): void;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* initialize-core — the construction + load-pass orchestration extracted from
|
|
1394
|
+
* the `ScheduleCore` constructor. `initializeCore(input)` performs the entire
|
|
1395
|
+
* load sequence (parse → snapshot → state → demote → id generators →
|
|
1396
|
+
* custom-id tracker → optional baseline block → initial passes) and returns
|
|
1397
|
+
* the assembled internals (`CoreInternals`) for the facade to assign. The
|
|
1398
|
+
* facade constructor is assignment-only.
|
|
1399
|
+
*
|
|
1400
|
+
* The load-pass ORDER here is a CONTRACT — see `CURSOR.md`
|
|
1401
|
+
* §"Flujo de core.dispatch paso a paso" and the inline comments below. Do not
|
|
1402
|
+
* reorder, merge, or drop any step.
|
|
1403
|
+
*
|
|
1404
|
+
* The public types (`ScheduleCoreStatus`, `ScheduleCoreInput`) live here so
|
|
1405
|
+
* `initialize-core.ts` never imports from `scheduleCore.ts` (which imports
|
|
1406
|
+
* `initializeCore`), avoiding an import cycle. `scheduleCore.ts` re-exports the
|
|
1407
|
+
* two public types
|
|
1408
|
+
* so `src/index.ts`'s existing `export type { ... } from './init/schedule-core.js'`
|
|
1409
|
+
* keeps resolving unchanged.
|
|
1410
|
+
*/
|
|
1411
|
+
|
|
1412
|
+
type ScheduleCoreStatus = 'ready' | 'destroyed' | 'poisoned';
|
|
1413
|
+
interface ScheduleCoreInput extends BackendInput {
|
|
1414
|
+
/**
|
|
1415
|
+
* Skip the full autoscheduler pass that normally runs from the
|
|
1416
|
+
* constructor (`runInitialPasses`).
|
|
1417
|
+
*
|
|
1418
|
+
* Backend data already ships with `start_date` / `end_date` / `duration`
|
|
1419
|
+
* scheduled — running the full ASAP+ALAP graph at load is redundant and
|
|
1420
|
+
* extremely slow on large schedules (thousands of activities + links).
|
|
1421
|
+
*
|
|
1422
|
+
* When `true`, the constructor still runs the lightweight bottom-up
|
|
1423
|
+
* post-processors (`updateParentBoundsFromChildren`) so parent bounds
|
|
1424
|
+
* and progress rollup are consistent. The autoscheduler runs lazily on
|
|
1425
|
+
* the first `dispatch(...)` call.
|
|
1426
|
+
*
|
|
1427
|
+
* Default: `false` (preserve existing behaviour). The bridge in
|
|
1428
|
+
* `react_client/` sets this to `true` for production loads.
|
|
1429
|
+
*/
|
|
1430
|
+
skipInitialAutoSchedule?: boolean;
|
|
1431
|
+
/**
|
|
1432
|
+
* Baseline points (backend `sectorbaselinepoint[]`, the `activityponts`
|
|
1433
|
+
* endpoint payload) to overlay at load. Each point maps to an activity by
|
|
1434
|
+
* `point.activityId` → `activity.proplannerId`. When present, the constructor
|
|
1435
|
+
* overlays them so the baseline columns + `expected_progress_base` can read the
|
|
1436
|
+
* ACTIVE point. NB: ponderators are NOT recomputed at load (backend ships the
|
|
1437
|
+
* value prod renders verbatim — recomputing diverges); `goCalculatePonderators`
|
|
1438
|
+
* is a save-time op. Omit (or pass `[]`) for projects with no baseline.
|
|
1439
|
+
*/
|
|
1440
|
+
baselinePoints?: readonly BaselinePoint[];
|
|
1441
|
+
/**
|
|
1442
|
+
* Clock port for time-dependent columns (`expected_progress*`). A FUNCTION
|
|
1443
|
+
* (not a frozen instant): the core calls it FRESH each time it needs "today"
|
|
1444
|
+
* (load + every dispatch that recomputes expected_progress), so the value
|
|
1445
|
+
* tracks the real day even on a long-lived session — never congelado.
|
|
1446
|
+
*
|
|
1447
|
+
* Opt-in (no default): omit it → the core does NOT compute `expected_progress`
|
|
1448
|
+
* (preserves the legacy path; keeps the test suite deterministic). Production
|
|
1449
|
+
* injects `() => new Date()`; tests inject `() => fixedDate` for reproducibility.
|
|
1450
|
+
* The core never calls `new Date()` itself — this port is the only time source.
|
|
1451
|
+
*/
|
|
1452
|
+
clock?: () => Date;
|
|
1453
|
+
/**
|
|
1454
|
+
* Critical-path mode at LOAD. The CP is ALWAYS computed; this flag only
|
|
1455
|
+
* decides WHEN `core.ready` is considered settled:
|
|
1456
|
+
* - `false` (default — front): `core.ready` resolves WITHOUT the CP (fast
|
|
1457
|
+
* load); the CP runs async and is delivered via `core.criticalPathReady`.
|
|
1458
|
+
* - `true` (backend, no front): `core.ready` WAITS for the CP — it is part of
|
|
1459
|
+
* the load (blocking). The caller does a single `await core.ready` and has
|
|
1460
|
+
* the dates + CP fields ready.
|
|
1461
|
+
*/
|
|
1462
|
+
criticalPathOnLoad?: boolean;
|
|
1463
|
+
/**
|
|
1464
|
+
* Initial status criterion (`'Baseline' | 'Actual'`) for the `status` column.
|
|
1465
|
+
* Not a backend field (client-only view pref); the client passes the current
|
|
1466
|
+
* value, defaulting to `'Baseline'`. Overrides the sector default when present.
|
|
1467
|
+
* Changed at runtime via `ScheduleCore.setStatusCriteria`.
|
|
1468
|
+
*/
|
|
1469
|
+
statusCriteria?: StatusCriteria;
|
|
1470
|
+
/**
|
|
1471
|
+
* Optional injected port for data-quality warnings and scheduling errors.
|
|
1472
|
+
* Defaults to a no-op when absent (the core never touches console/Sentry).
|
|
1473
|
+
*/
|
|
1474
|
+
reporter?: ScheduleCoreReporter;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
declare class ScheduleCore {
|
|
1478
|
+
private _status;
|
|
1479
|
+
/** True while an async dispatch is suspended inside the write-capture window.
|
|
1480
|
+
* A sync facade mutation running in that gap would corrupt the in-flight
|
|
1481
|
+
* journal, so sync mutators refuse to run while it is set. */
|
|
1482
|
+
private _dispatchInFlight;
|
|
1483
|
+
private readonly coreRuntime;
|
|
1484
|
+
private readonly _undo;
|
|
1485
|
+
private _opQueue;
|
|
1486
|
+
private _mutationEpoch;
|
|
1487
|
+
private _cpComputedEpoch;
|
|
1488
|
+
constructor(input: ScheduleCoreInput);
|
|
1489
|
+
get ready(): Promise<void>;
|
|
1490
|
+
get criticalPathReady(): Promise<{
|
|
1491
|
+
changes: ChangeSet;
|
|
1492
|
+
}>;
|
|
1493
|
+
get status(): ScheduleCoreStatus;
|
|
1494
|
+
getSector(): Readonly<SectorMetadata>;
|
|
1495
|
+
getActivityView(id: ActivityId$1, unit?: OutputUnit): Readonly<Activity> | null;
|
|
1496
|
+
getAllActivitiesView(unit?: OutputUnit): ReadonlyArray<Readonly<Activity>>;
|
|
1497
|
+
getChildrenView(parentId: ActivityId$1 | '0', unit?: OutputUnit): ReadonlyArray<Readonly<Activity>>;
|
|
1498
|
+
getLinkView(id: LinkId, unit?: OutputUnit): Readonly<Link> | null;
|
|
1499
|
+
getAllLinksView(unit?: OutputUnit): ReadonlyArray<Readonly<Link>>;
|
|
1500
|
+
getProperty<Property extends keyof Activity>(activityId: ActivityId$1, property: Property, options?: {
|
|
1501
|
+
unit?: OutputUnit;
|
|
1502
|
+
}): Activity[Property] | null;
|
|
1503
|
+
getAllIds(): readonly string[];
|
|
1504
|
+
forEachActivityId(visit: (id: string) => void): void;
|
|
1505
|
+
getChildrenIds(parentId: ActivityId$1 | '0'): ReadonlyArray<ActivityId$1>;
|
|
1506
|
+
getSelectedActivityIds(): string[];
|
|
1507
|
+
/**
|
|
1508
|
+
* Every activity id in canonical DFS visual order (pre-order from roots,
|
|
1509
|
+
* siblings by `correlative_id` ASC) — ALL activities; filtering by
|
|
1510
|
+
* `visible` is the consumer's job. Memoized in the state and invalidated
|
|
1511
|
+
* on any structural mutation (add/remove/reparent/renumber), so repeated
|
|
1512
|
+
* reads between mutations are O(1).
|
|
1513
|
+
*/
|
|
1514
|
+
getVisualOrderIds(): ReadonlyArray<ActivityId$1>;
|
|
1515
|
+
hasChild(parentId: ActivityId$1 | '0'): boolean;
|
|
1516
|
+
getCalendar(id: CalendarId$1): Readonly<Calendar> | null;
|
|
1517
|
+
getAllCalendars(): ReadonlyArray<Readonly<Calendar>>;
|
|
1518
|
+
getBaseCalendars(): ReadonlyArray<Readonly<Calendar>>;
|
|
1519
|
+
isCustomIdInUse(customId: string, currentCustomId?: string | null): boolean;
|
|
1520
|
+
getProjectWorkHours(): ProjectWorkHours | null;
|
|
1521
|
+
getModifiedLinks(): ReadonlyArray<Readonly<Link>>;
|
|
1522
|
+
markLinksPersisted(): void;
|
|
1523
|
+
getModifiedActivities(): ReadonlyArray<Readonly<Activity>>;
|
|
1524
|
+
markActivitiesPersisted(): void;
|
|
1525
|
+
applyBaselines(points: readonly BaselinePoint[]): ReadonlyArray<Readonly<Activity>>;
|
|
1526
|
+
/**
|
|
1527
|
+
* Whole-tree ponderator recompute (zeroes, then redistributes 100 within
|
|
1528
|
+
* each parent from its children's active baselines, weighted by `criterion`).
|
|
1529
|
+
* Returns the activities whose `ponderator` changed, for a prop-only repaint.
|
|
1530
|
+
* The base rollup (`applyBaselines`) depends on fresh ponderators, so callers
|
|
1531
|
+
* run this before it on a criterion change or baseline save.
|
|
1532
|
+
*/
|
|
1533
|
+
recomputeAllPonderators(criterion: ActivityCreter): ReadonlyArray<Readonly<Activity>>;
|
|
1534
|
+
/**
|
|
1535
|
+
* Recompute the weighted-progress rollup of every parent, bottom-up.
|
|
1536
|
+
* Returns the activities whose `progress` changed, for a prop-only repaint.
|
|
1537
|
+
*/
|
|
1538
|
+
recomputeAllProgressRollup(): ReadonlyArray<Readonly<Activity>>;
|
|
1539
|
+
/**
|
|
1540
|
+
* Set the project's status criterion (the Baseline↔Actual toggle) and
|
|
1541
|
+
* recompute every activity's `status` against it. Stores the criterion so
|
|
1542
|
+
* future dispatches keep deriving status with it. Returns the activities
|
|
1543
|
+
* whose `status` changed, for a prop-only repaint.
|
|
1544
|
+
*/
|
|
1545
|
+
setStatusCriteria(criteria: StatusCriteria): ReadonlyArray<Readonly<Activity>>;
|
|
1546
|
+
allocateActivityId(): ActivityId$1;
|
|
1547
|
+
createActivity(input: Omit<Extract<DispatchAction, {
|
|
1548
|
+
kind: 'activity-create';
|
|
1549
|
+
}>, 'kind'>, outputUnit?: OutputUnit): {
|
|
1550
|
+
ok: true;
|
|
1551
|
+
activity: Readonly<Activity>;
|
|
1552
|
+
changes: ChangeSet;
|
|
1553
|
+
} | {
|
|
1554
|
+
ok: false;
|
|
1555
|
+
reason: string;
|
|
1556
|
+
};
|
|
1557
|
+
private _enqueue;
|
|
1558
|
+
dispatch(action: DispatchAction, options?: DispatchOptions): Promise<DispatchResult>;
|
|
1559
|
+
private _dispatchInner;
|
|
1560
|
+
/**
|
|
1561
|
+
* Recompute the critical path OUT of band and return a repaintable ChangeSet
|
|
1562
|
+
* of the 7 CP fields, or null when the CP is already fresh for the current
|
|
1563
|
+
* state (coalesced). Enqueued on `_opQueue` so it runs serialized against
|
|
1564
|
+
* dispatches — it reads/writes the live tree only when no mutation is in
|
|
1565
|
+
* flight.
|
|
1566
|
+
*
|
|
1567
|
+
* Deferred-CP flow: dispatch with `{ skipCriticalPath: true }` (paints
|
|
1568
|
+
* without the heavy CP pass), then call this after the paint. Bursts
|
|
1569
|
+
* coalesce: only the first enqueued recompute after the last mutation does
|
|
1570
|
+
* the work; later ones see `_cpComputedEpoch === _mutationEpoch` and no-op.
|
|
1571
|
+
*/
|
|
1572
|
+
recomputeCriticalPath(): Promise<{
|
|
1573
|
+
changes: ChangeSet;
|
|
1574
|
+
} | null>;
|
|
1575
|
+
private _recomputeCriticalPathIfStale;
|
|
1576
|
+
private _runCriticalPathAndCapture;
|
|
1577
|
+
undo(options?: {
|
|
1578
|
+
outputUnit?: OutputUnit;
|
|
1579
|
+
}): Promise<ChangeSet | null>;
|
|
1580
|
+
redo(options?: {
|
|
1581
|
+
outputUnit?: OutputUnit;
|
|
1582
|
+
}): Promise<ChangeSet | null>;
|
|
1583
|
+
clearHistory(): void;
|
|
1584
|
+
canUndo(): boolean;
|
|
1585
|
+
canRedo(): boolean;
|
|
1586
|
+
undoDepth(): number;
|
|
1587
|
+
private _resyncCustomIdTrackerFromModel;
|
|
1588
|
+
destroy(): void;
|
|
1589
|
+
private readonly _initPromise;
|
|
1590
|
+
private readonly _criticalPathReady;
|
|
1591
|
+
private readonly _ready;
|
|
1592
|
+
private readonly _saveTracker;
|
|
1593
|
+
private assertReady;
|
|
1594
|
+
/**
|
|
1595
|
+
* Revert the current dispatch's mutations from the write-capture journal. If
|
|
1596
|
+
* the restore ITSELF throws, the state may be a third, partially-inverted
|
|
1597
|
+
* state — worse than either endpoint — so poison the core: refuse all further
|
|
1598
|
+
* dispatches and surface the fault so the host reloads from backend. A failed
|
|
1599
|
+
* rollback is never swallowed.
|
|
1600
|
+
*/
|
|
1601
|
+
private _rollback;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/**
|
|
1605
|
+
* Parse the raw backend payload into the core's normalized internal model.
|
|
1606
|
+
*
|
|
1607
|
+
* Pure function. No side effects, no state. Throws on structural errors
|
|
1608
|
+
* (missing required fields, broken references) per Q4 in SCHEDULE_CORE_API.md.
|
|
1609
|
+
*
|
|
1610
|
+
* Public from the package — consumers can use this to pre-validate before
|
|
1611
|
+
* instantiating ScheduleCore, or to round-trip data through tests.
|
|
1612
|
+
*/
|
|
1613
|
+
|
|
1614
|
+
interface ParsedInput {
|
|
1615
|
+
sector: SectorMetadata;
|
|
1616
|
+
activities: Activity[];
|
|
1617
|
+
links: Link[];
|
|
1618
|
+
calendars: Calendar[];
|
|
1619
|
+
/**
|
|
1620
|
+
* Baseline calendars, kept SEPARATE from the scheduling `calendars`. They
|
|
1621
|
+
* are never used to resolve an activity's calendar — only for the baseline
|
|
1622
|
+
* render. Parsed from the raw `baseCalendars` input (the backend's
|
|
1623
|
+
* `showBaseCalendar`); reshaped here (id+'-base', baseDefault, exceptions
|
|
1624
|
+
* from `baseexceptiondays`) so the core owns the differentiation instead of
|
|
1625
|
+
* the frontend `refreshCalendarsRewrite`.
|
|
1626
|
+
*/
|
|
1627
|
+
baseCalendars: Calendar[];
|
|
1628
|
+
}
|
|
1629
|
+
declare function parseFromBackend(input: BackendInput, reporter?: ScheduleCoreReporter): ParsedInput;
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Returns the ACTIVE baseline point of an activity, or null.
|
|
1633
|
+
*
|
|
1634
|
+
* Ported verbatim from the legacy selector used across the schedule
|
|
1635
|
+
* (`react_client/src/views/ganttContainer/gantt/gantt.helper.js`
|
|
1636
|
+
* calculatePonderators / getDurationRecursively, and the baseline columns
|
|
1637
|
+
* `startBaseCol`…`durationBaseCol`):
|
|
1638
|
+
*
|
|
1639
|
+
* activity.baseline_points.find(b => b.sectorbaselineversion?.active)
|
|
1640
|
+
*
|
|
1641
|
+
* Exactly one version per sector is active. Returns null when the activity
|
|
1642
|
+
* has no `baseline_points`, an empty array, or no point whose version is
|
|
1643
|
+
* active — matching the legacy behavior where such activities get
|
|
1644
|
+
* `ponderator = 0` (baseline-only, no fallback to real values; see memory
|
|
1645
|
+
* `project_ponderator_baseline_fix`).
|
|
1646
|
+
*
|
|
1647
|
+
* The `active` flag is treated as truthy (not strictly `=== true`) to match
|
|
1648
|
+
* the legacy `if (base.sectorbaselineversion.active)` check.
|
|
1649
|
+
*/
|
|
1650
|
+
declare function getActiveBaseline(activity: {
|
|
1651
|
+
baseline_points?: unknown;
|
|
1652
|
+
} | null | undefined): BaselinePoint | null;
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* Surface this function needs from ScheduleState — kept structural so the
|
|
1656
|
+
* function is unit-testable with a plain in-memory adapter.
|
|
1657
|
+
*/
|
|
1658
|
+
interface BaselineApplyAdapter {
|
|
1659
|
+
forEachActivity(visit: (a: Record<string, unknown>, id: string) => void): void;
|
|
1660
|
+
getActivity(id: string): Record<string, unknown> | null;
|
|
1661
|
+
setActivityField(id: string, field: string, value: unknown): void;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Overlay baseline points onto activities, matching `point.activityId`
|
|
1665
|
+
* (backend DB id) against `activity.proplannerId`. Writes the full group of
|
|
1666
|
+
* points (all versions) onto `baseline_points`; the active one is selected
|
|
1667
|
+
* downstream by `getActiveBaseline`. Activities absent from the new set get
|
|
1668
|
+
* `baseline_points` reset to `[]`. Returns the ids of activities whose
|
|
1669
|
+
* `baseline_points` changed (so the caller can repaint just those).
|
|
1670
|
+
*/
|
|
1671
|
+
declare function applyBaselinePoints(adapter: BaselineApplyAdapter, points: readonly BaselinePoint[]): string[];
|
|
1672
|
+
|
|
1673
|
+
interface CalendarLike {
|
|
1674
|
+
calculateDuration(start: Date, end: Date): number;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Expected % a baseline-planned activity should have completed by `now`,
|
|
1678
|
+
* measured in working time against its baseline calendar. Faithful port of
|
|
1679
|
+
* legacy `calculateExpected` (lookahead-common.js): edge rules first, else the
|
|
1680
|
+
* working-time ratio. Returns 0..100. `now` should already be the comparison
|
|
1681
|
+
* instant the caller wants (e.g. end-of-day).
|
|
1682
|
+
*
|
|
1683
|
+
* Shared by the `expected_progress` (live) and `expected_progress_base` columns
|
|
1684
|
+
* (column-owns-everything: the formula is the one piece both columns share).
|
|
1685
|
+
*/
|
|
1686
|
+
declare function expectedProgressFromBaseline(start: Date, end: Date, now: Date, calendar: CalendarLike): number;
|
|
1687
|
+
|
|
1688
|
+
interface ExpectedProgressAdapter {
|
|
1689
|
+
getChildrenIds(parentId: string | 0): readonly string[];
|
|
1690
|
+
getActivity(id: string): Record<string, unknown> | null;
|
|
1691
|
+
setActivityField(id: string, field: string, value: unknown): void;
|
|
1692
|
+
/** Resolve a baseline calendar by its engine id (`${baseCalendarId}-base`). */
|
|
1693
|
+
getBaseCalendar(engineId: string): CalendarLike | null;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Walks the tree from `rootIds`, writing `expected_progress_base` on every
|
|
1697
|
+
* activity: leaves from their baseline calendar, parents as
|
|
1698
|
+
* `Σ(childBase × child.ponderator) / 100` (faithful port of engine.ts
|
|
1699
|
+
* aggregateHierarchy). Returns the ids whose value changed.
|
|
1700
|
+
*/
|
|
1701
|
+
declare function computeExpectedProgress(rootIds: readonly string[], now: Date, adapter: ExpectedProgressAdapter, defaultBaseCalendarId?: string | null): string[];
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Recompute the `ponderator` of EVERY activity in the schedule, in bulk.
|
|
1705
|
+
*
|
|
1706
|
+
* Faithful port of `goCalculatePonderators` in
|
|
1707
|
+
* `react_client/src/views/ganttContainer/gantt/index.js:2008-2038` (the
|
|
1708
|
+
* ponderator part — the progress rollup and save are separate concerns the
|
|
1709
|
+
* caller wires). Two passes, mirroring the legacy:
|
|
1710
|
+
* 1. Zero EVERY activity's ponderator.
|
|
1711
|
+
* 2. For each parent (activity with children), redistribute 100 among its
|
|
1712
|
+
* children via `recomputePonderatorsForParent` (baseline-only).
|
|
1713
|
+
*
|
|
1714
|
+
* IMPORTANT — this is the ONLY ponderator recompute the schedule performs.
|
|
1715
|
+
* Verified against the legacy (2026-06-04): the masterplan does NOT recompute
|
|
1716
|
+
* ponderators on add / delete / move / inline-edit. `goCalculatePonderators`
|
|
1717
|
+
* runs ONLY on two events (see index.js:395/443/625):
|
|
1718
|
+
* - the project criterion (`activity_creter`) changed
|
|
1719
|
+
* (`sectorObject.update_ponderators_masterplan`), or
|
|
1720
|
+
* - a baseline was created / saved.
|
|
1721
|
+
* Both events otherwise reload the schedule. So there is intentionally NO
|
|
1722
|
+
* per-mutation cascade and NO dispatch hook — adding one would diverge from
|
|
1723
|
+
* production (a baselined child's siblings stay as the backend computed them
|
|
1724
|
+
* until the next of those two events). See
|
|
1725
|
+
* [[PONDERATOR_BASELINE_MIGRATION_PLAN]].
|
|
1726
|
+
*
|
|
1727
|
+
* Order-independent: a project child's weight derives from descendant
|
|
1728
|
+
* BASELINES (`getDurationRecursively`), never from other ponderators, so the
|
|
1729
|
+
* order parents are visited in does not affect the result.
|
|
1730
|
+
*/
|
|
1731
|
+
|
|
1732
|
+
declare function recomputeAllPonderators(criterion: ActivityCreter, adapter: ActivityWriter): void;
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* Recompute the weighted-progress rollup of EVERY parent in the schedule,
|
|
1736
|
+
* bottom-up. `parent.progress = Σ(child.progress × child.ponderator / 100)`.
|
|
1737
|
+
*
|
|
1738
|
+
* Faithful port of legacy `check_progress` (editing-flow.js:154-197) — the
|
|
1739
|
+
* second half of `goCalculatePonderators`. After a ponderator recompute
|
|
1740
|
+
* (baseline create/save, see `recomputeAllPonderators`), parent progress is
|
|
1741
|
+
* stale w.r.t. the new weights; this repropagates it.
|
|
1742
|
+
*
|
|
1743
|
+
* PROGRESS-ONLY by design: unlike the dispatch post-processor
|
|
1744
|
+
* `recomputeParentFromChildren` (post-processors/parent-bounds.ts), this does NOT
|
|
1745
|
+
* touch parent start/end/duration — only `progress` and the milestone mirror
|
|
1746
|
+
* `for_disable_milestone_duration`. That keeps it testable in isolation
|
|
1747
|
+
* (no calendars/dates needed) and matches what `check_progress` actually did.
|
|
1748
|
+
*
|
|
1749
|
+
* Order is load-bearing (unlike ponderators, which are order-independent): a
|
|
1750
|
+
* grandparent's rollup consumes its children-parents' ALREADY rolled-up
|
|
1751
|
+
* progress, so parents are visited deepest-first.
|
|
1752
|
+
*
|
|
1753
|
+
* Parity rules:
|
|
1754
|
+
* - child with no/zero ponderator does not contribute;
|
|
1755
|
+
* - a parent whose children contribute nothing keeps its progress frozen;
|
|
1756
|
+
* - `progress` is kept a NUMBER but rounded per level to 2 decimals
|
|
1757
|
+
* (`roundProgressPerLevel`) — legacy `check_progress` recurses on
|
|
1758
|
+
* `toFixed(2)`, so each ancestor consumes its children's 2-decimal value
|
|
1759
|
+
* (MORNING_QUEUE.md — JUN-02).
|
|
1760
|
+
*/
|
|
1761
|
+
|
|
1762
|
+
declare function recomputeAllProgressRollup(adapter: ActivityWriter): void;
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* Task creation pipeline — types.
|
|
1766
|
+
*
|
|
1767
|
+
* Ports the pure parts of `src/assets/js/custom_actions/createTask/` into a
|
|
1768
|
+
* functional pipeline that replay tests (and eventually prod) can invoke
|
|
1769
|
+
* against an adapter instead of the live gantt.
|
|
1770
|
+
*/
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* Shape of the task object AFTER the pipeline configures it.
|
|
1774
|
+
*
|
|
1775
|
+
* Mirrors the canonical `Activity` interface (from `../../types.ts`) plus
|
|
1776
|
+
* a handful of Outbuild-specific fields (responsables, tags, subcontractId,
|
|
1777
|
+
* real_constraint_type) and legacy lifecycle flags (new_task, isNewActivity,
|
|
1778
|
+
* $source, $target) that downstream consumers still read.
|
|
1779
|
+
*
|
|
1780
|
+
* Every field in `Activity` is present here. The defaults for the
|
|
1781
|
+
* non-runtime-computed ones live in `NEW_ACTIVITY_DEFAULTS`.
|
|
1782
|
+
*/
|
|
1783
|
+
interface NewActivityFields {
|
|
1784
|
+
text: string;
|
|
1785
|
+
description: string;
|
|
1786
|
+
type: 'task' | 'milestone' | 'project';
|
|
1787
|
+
start_date: Date;
|
|
1788
|
+
end_date: Date;
|
|
1789
|
+
duration: number;
|
|
1790
|
+
progress: number;
|
|
1791
|
+
constraint_type: ConstraintType | null;
|
|
1792
|
+
constraint_date: Date | null;
|
|
1793
|
+
calendar_id: CalendarId;
|
|
1794
|
+
/** Parent id (string) or `'0'` for root. Always string — no number drift. */
|
|
1795
|
+
parent: ActivityId | '0';
|
|
1796
|
+
auto_scheduling: boolean;
|
|
1797
|
+
hhWorkTime: number;
|
|
1798
|
+
cost: number;
|
|
1799
|
+
used_cost: number;
|
|
1800
|
+
/**
|
|
1801
|
+
* Earned cost: `(progress/100) × activeBaseline.cost`. A fresh activity has
|
|
1802
|
+
* progress 0 and no active baseline, so it starts at 0 — matching what the
|
|
1803
|
+
* load-path `emitRealCost` would compute for a new leaf. Seeded explicitly so
|
|
1804
|
+
* the field is never `undefined` (which surfaced as `NaN` downstream).
|
|
1805
|
+
*/
|
|
1806
|
+
real_cost: number;
|
|
1807
|
+
/** Real worked hours; aggregated from progress submittals. Starts at 0. */
|
|
1808
|
+
real_work: number;
|
|
1809
|
+
/**
|
|
1810
|
+
* Baseline-derived weighting factor used by the parent.progress rollup.
|
|
1811
|
+
* Starts at 0 (no baseline yet); set by the backend on baseline import.
|
|
1812
|
+
* Not editable from the schedule UI — see `project_ponderator_not_editable`.
|
|
1813
|
+
*/
|
|
1814
|
+
ponderator: number;
|
|
1815
|
+
responsables: unknown[];
|
|
1816
|
+
tags: unknown[];
|
|
1817
|
+
subcontractId: string | number | null;
|
|
1818
|
+
real_constraint_type: ConstraintType | null;
|
|
1819
|
+
should_be_showed: boolean;
|
|
1820
|
+
/** View-model visibility bit (`visible !== false` = visible). */
|
|
1821
|
+
visible: boolean;
|
|
1822
|
+
activityModifications: unknown[];
|
|
1823
|
+
pendingRequests: unknown[];
|
|
1824
|
+
/** String form, matches Sequelize BIGINT serialization. */
|
|
1825
|
+
unique_correlative_id: string;
|
|
1826
|
+
start_date_backup: string;
|
|
1827
|
+
new_task: true;
|
|
1828
|
+
isNewActivity: true;
|
|
1829
|
+
/**
|
|
1830
|
+
* Tracks outgoing link ids (this activity is the link source).
|
|
1831
|
+
* Initialized empty; mutated by link-create / link-delete to keep the
|
|
1832
|
+
* index consistent. The snapshot reads it directly, so it must always
|
|
1833
|
+
* exist (not undefined) — see D1 in DIVERGENCES_activity_create.md.
|
|
1834
|
+
*/
|
|
1835
|
+
$source: string[];
|
|
1836
|
+
/** Same as $source for incoming links (this activity is the target). */
|
|
1837
|
+
$target: string[];
|
|
1838
|
+
/** Pass-through bucket for any extra fields the caller provides. */
|
|
1839
|
+
[key: string]: unknown;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
/**
|
|
1843
|
+
* Working-day vocabulary — genuine cross-component primitive (creation
|
|
1844
|
+
* defaults, internal/state context fallbacks, link lag display). The value
|
|
1845
|
+
* is the FALLBACK only; a sector- or calendar-provided hoursPerDay always
|
|
1846
|
+
* takes precedence at every consumer.
|
|
1847
|
+
*/
|
|
1848
|
+
declare const DEFAULT_HOURS_PER_DAY = 8;
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* Defaults for newly-created activities — the **canonical template** that
|
|
1852
|
+
* `buildNewActivity` spreads before applying caller-provided overrides.
|
|
1853
|
+
*
|
|
1854
|
+
* Single source of truth: any documentation that says "a new activity
|
|
1855
|
+
* starts as X" must reference this constant, never inline literals.
|
|
1856
|
+
*
|
|
1857
|
+
* The shape produced by spreading this constant + the runtime-computed
|
|
1858
|
+
* fields below is a complete Activity (every field in the `Activity`
|
|
1859
|
+
* interface from `../../types.ts` is accounted for).
|
|
1860
|
+
*
|
|
1861
|
+
* Fields NOT in this constant are computed at runtime because they depend
|
|
1862
|
+
* on input or context:
|
|
1863
|
+
* - `id` → allocated by `ActivityIdGenerator.next()`
|
|
1864
|
+
* - `parent` → from `DispatchAction.parentId` (or `'0'`)
|
|
1865
|
+
* - `start_date` → inherited from parent or `fallbackStartDate`,
|
|
1866
|
+
* then snapped to the calendar's next
|
|
1867
|
+
* working-time boundary
|
|
1868
|
+
* - `end_date` → calendar-aware via `adapter.calculateEndDate`
|
|
1869
|
+
* - `duration` → override > `sector.hoursPerDay` > `8`
|
|
1870
|
+
* - `calendar_id` → override > `sector.defaultCalendar`
|
|
1871
|
+
* - `unique_correlative_id` → allocated by `UniqueCorrelativeIdGenerator.next()`
|
|
1872
|
+
* - `custom_id` → allocated by `customIdTracker`
|
|
1873
|
+
* - `text` → `'New Master Plan'` when this is the very
|
|
1874
|
+
* first activity of the sector, `'New Activity'`
|
|
1875
|
+
* otherwise (override beats both)
|
|
1876
|
+
* - `correlative_id` → assigned by `recomputeCorrelativeIds` after
|
|
1877
|
+
* the new activity has been inserted in the
|
|
1878
|
+
* parent's children list
|
|
1879
|
+
*
|
|
1880
|
+
* The `real_constraint_type` field is set AFTER the spread of overrides so
|
|
1881
|
+
* it always mirrors the (possibly overridden) `constraint_type`.
|
|
1882
|
+
*/
|
|
1883
|
+
|
|
1884
|
+
declare const NEW_ACTIVITY_DEFAULTS: Readonly<Omit<NewActivityFields, 'id' | 'parent' | 'start_date' | 'end_date' | 'duration' | 'calendar_id' | 'unique_correlative_id' | 'text' | 'start_date_backup'>>;
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* Schedule-core equivalent of the legacy frontend `checkNoUpdatedLinks`.
|
|
1888
|
+
*
|
|
1889
|
+
* Returns the CURRENT links that were MODIFIED relative to a `baseline` (the
|
|
1890
|
+
* last loaded/saved link state). A link is "modified" iff its `lag` or `type`
|
|
1891
|
+
* differs from its baseline counterpart (matched by `id`).
|
|
1892
|
+
*
|
|
1893
|
+
* Why only lag/type: empirically those are the only mutable fields of a link —
|
|
1894
|
+
* `source`/`target` are identity, and the rest are backend/derived. The core
|
|
1895
|
+
* `link-update` intent (`dispatch/types.ts`) changes exactly "lag and/or type",
|
|
1896
|
+
* so this comparison is complete.
|
|
1897
|
+
*
|
|
1898
|
+
* New links (absent from baseline) and deleted links (present only in baseline)
|
|
1899
|
+
* are NOT "modified" — those belong to the unsaved / deleted buckets. The
|
|
1900
|
+
* baseline is supplied by the caller (the save flow's reference snapshot); per
|
|
1901
|
+
* interface discipline this is a pure helper, not a method on the core/port.
|
|
1902
|
+
*/
|
|
1903
|
+
declare function checkNoUpdatedLinks(baseline: readonly Link[], current: readonly Link[]): Link[];
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* Schedule-core equivalent of the legacy frontend `checkNoSavedActivities` /
|
|
1907
|
+
* `checkNoSavedLinks`: an entity is "unsaved" when it has no backend id
|
|
1908
|
+
* (`proplannerId`) — i.e. it was created in-session and not yet persisted.
|
|
1909
|
+
*
|
|
1910
|
+
* Pure helper (same `!proplannerId` criterion as legacy, so 0/null/undefined
|
|
1911
|
+
* all count as unsaved). Works on `core.getAllActivitiesView()` / `getAllLinks()`
|
|
1912
|
+
* or on `gantt.serialize().data` — both carry `proplannerId`. Per interface
|
|
1913
|
+
* discipline this is a helper, not a method on the core/port.
|
|
1914
|
+
*/
|
|
1915
|
+
interface WithProplannerId {
|
|
1916
|
+
proplannerId?: number | null;
|
|
1917
|
+
[key: string]: unknown;
|
|
1918
|
+
}
|
|
1919
|
+
declare const getUnsavedActivities: <T extends WithProplannerId>(activities?: readonly T[]) => T[];
|
|
1920
|
+
|
|
1921
|
+
/**
|
|
1922
|
+
* Returns a microtask-priority yield. Resolves on the next event-loop
|
|
1923
|
+
* tick — the browser is free to paint, handle input, or process other
|
|
1924
|
+
* tasks in between.
|
|
1925
|
+
*/
|
|
1926
|
+
declare function yieldToBrowser(): Promise<void>;
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* Does this dispatch recompute the critical path? The single authority for the
|
|
1930
|
+
* question, shared by the dispatch gate (`resolveDerivedPasses`) and the
|
|
1931
|
+
* bridge, which reads it to defer the CP out of the paint. Mirrors legacy: the
|
|
1932
|
+
* CP re-derives on any scheduling change, and is left STALE for prop-only
|
|
1933
|
+
* intents, the structural reparents (indent/outdent), and edits that touch only
|
|
1934
|
+
* non-scheduling columns.
|
|
1935
|
+
*/
|
|
1936
|
+
declare function willRunCriticalPath(action: DispatchAction): boolean;
|
|
1937
|
+
|
|
1938
|
+
/** Activity types */
|
|
1939
|
+
declare const ACTIVITY_TYPE: {
|
|
1940
|
+
readonly TASK: "task";
|
|
1941
|
+
readonly PROJECT: "project";
|
|
1942
|
+
readonly MILESTONE: "milestone";
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* Root-parent sentinel — single source of truth.
|
|
1947
|
+
*
|
|
1948
|
+
* Root-level activities have meant "root" under several shapes across the
|
|
1949
|
+
* codebase and the recorded data: string `'0'` (post-parse domain model),
|
|
1950
|
+
* numeric `0` (DHTMLX / engine seam), and nullish. `isRootParent` accepts
|
|
1951
|
+
* all of them; `normalizeParentKey` collapses them to the canonical `'0'`.
|
|
1952
|
+
*
|
|
1953
|
+
* NOTE: `'0'` is overloaded elsewhere in the domain (FS link-type code,
|
|
1954
|
+
* correlative ids). These constants are ONLY for the parent axis.
|
|
1955
|
+
*/
|
|
1956
|
+
/** Canonical root-parent id in the post-parse domain model. */
|
|
1957
|
+
declare const ROOT_PARENT_ID: "0";
|
|
1958
|
+
/** True when `parent` denotes the root level, under any historical shape. */
|
|
1959
|
+
declare function isRootParent(parent: unknown): boolean;
|
|
1960
|
+
/** Collapse any root shape to the canonical `'0'`; stringify the rest. */
|
|
1961
|
+
declare function normalizeParentKey(parent: unknown): string;
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* Canonical bidirectional map between DHTMLX link kinds ('0'-'3') and the
|
|
1965
|
+
* backend/display two-letter codes (fs/ss/ff/sf), plus the predecessor
|
|
1966
|
+
* display-string vocabulary.
|
|
1967
|
+
*
|
|
1968
|
+
* Single source of truth — replaces the hand-maintained copies that lived in
|
|
1969
|
+
* parse-predecessor-string (code→number), apply-link-operation
|
|
1970
|
+
* (number→code), boundary/backend parseFromBackend (code→number) and
|
|
1971
|
+
* inverses (number→code). The mutual `satisfies Record<…>` constraints make
|
|
1972
|
+
* omitting or typo'ing a key on either side a compile error.
|
|
1973
|
+
*/
|
|
1974
|
+
|
|
1975
|
+
/** Backend/display two-letter link codes. */
|
|
1976
|
+
type LinkTypeCode = 'fs' | 'ss' | 'ff' | 'sf';
|
|
1977
|
+
/** DHTMLX numeric kind → backend/display code. */
|
|
1978
|
+
declare const LINK_TYPE_CODE: {
|
|
1979
|
+
readonly "0": "fs";
|
|
1980
|
+
readonly "1": "ss";
|
|
1981
|
+
readonly "2": "ff";
|
|
1982
|
+
readonly "3": "sf";
|
|
1983
|
+
};
|
|
1984
|
+
/** Backend/display code → DHTMLX numeric kind (inverse of LINK_TYPE_CODE). */
|
|
1985
|
+
declare const LINK_CODE_TO_TYPE: {
|
|
1986
|
+
readonly fs: "0";
|
|
1987
|
+
readonly ss: "1";
|
|
1988
|
+
readonly ff: "2";
|
|
1989
|
+
readonly sf: "3";
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
/**
|
|
1993
|
+
* Inverse boundary maps (domain → backend vocabulary).
|
|
1994
|
+
*
|
|
1995
|
+
* The forward translations live in `parseFromBackend.ts`
|
|
1996
|
+
* (`CONSTRAINT_LABEL_MAP`, `LINK_CODE_TO_NUMBER`). The *serialization*
|
|
1997
|
+
* core → backend is intentionally NOT owned by this package today — it
|
|
1998
|
+
* lives in the consumer bridge (`react_client/gantt.helper.js`,
|
|
1999
|
+
* `from_number_to_code` / `GanttConstraint` reverse lookup) because the
|
|
2000
|
+
* bridge is the source of truth for what the server expects on save.
|
|
2001
|
+
*
|
|
2002
|
+
* These inverses exist so the mapping is documented and testable next to
|
|
2003
|
+
* its forward counterpart, and so a future `core → backend` layer doesn't
|
|
2004
|
+
* re-derive them by hand. **Internal only — not exported in `src/index.ts`.**
|
|
2005
|
+
* See Obsidian `BOUNDARY_TRANSLATIONS.md` §"Mapeos inversos".
|
|
2006
|
+
*/
|
|
2007
|
+
|
|
2008
|
+
/** Inverse of `CONSTRAINT_LABEL_MAP` — internal code → backend label. */
|
|
2009
|
+
declare const CONSTRAINT_TYPE_TO_LABEL: Record<ConstraintType, string>;
|
|
2010
|
+
|
|
2011
|
+
/**
|
|
2012
|
+
* Canonical catalog of `DispatchAction` kinds.
|
|
2013
|
+
*
|
|
2014
|
+
* The discriminated union in `types.ts` remains the source of truth; this
|
|
2015
|
+
* module exposes it as RUNTIME vocabulary so `.js` consumers (bridge
|
|
2016
|
+
* entry-points, guard tests) stop retyping the wire strings. Exhaustiveness
|
|
2017
|
+
* is compile-checked HERE (not in the guard test — `tsconfig.json` excludes
|
|
2018
|
+
* `*.test.ts` from `tsc --noEmit` and vitest does not typecheck):
|
|
2019
|
+
* - `satisfies Record<string, DispatchActionKind>` rejects any entry whose
|
|
2020
|
+
* value is not a union member (catalog ⊆ union);
|
|
2021
|
+
* - `KIND_CATALOG_COVERS_UNION: Record<DispatchActionKind, true>` requires
|
|
2022
|
+
* one entry per union member (union ⊆ catalog) — adding a kind to
|
|
2023
|
+
* `DispatchAction` without updating this module breaks the build.
|
|
2024
|
+
* The string VALUES are a frozen contract (see `constants.guard.test.ts`).
|
|
2025
|
+
*/
|
|
2026
|
+
|
|
2027
|
+
type DispatchActionKind = DispatchAction['kind'];
|
|
2028
|
+
declare const DISPATCH_ACTION_KIND: {
|
|
2029
|
+
readonly INLINE_EDIT: "inline-edit";
|
|
2030
|
+
readonly DATES_BATCH: "dates-batch";
|
|
2031
|
+
readonly BULK_EDIT: "bulk-edit";
|
|
2032
|
+
readonly LINK_CREATE: "link-create";
|
|
2033
|
+
readonly LINK_UPDATE: "link-update";
|
|
2034
|
+
readonly LINK_DELETE: "link-delete";
|
|
2035
|
+
readonly LINKS_BATCH: "links-batch";
|
|
2036
|
+
readonly ACTIVITY_CREATE: "activity-create";
|
|
2037
|
+
readonly ACTIVITY_DELETE: "activity-delete";
|
|
2038
|
+
readonly ACTIVITY_MOVE: "activity-move";
|
|
2039
|
+
readonly ACTIVITY_INDENT: "activity-indent";
|
|
2040
|
+
readonly ACTIVITY_OUTDENT: "activity-outdent";
|
|
2041
|
+
readonly ACTIVITY_SET_PROGRESS: "activity-set-progress";
|
|
2042
|
+
readonly ACTIVITY_PASTE: "activity-paste";
|
|
2043
|
+
readonly SELECTION_TOGGLE: "selection-toggle";
|
|
2044
|
+
readonly SELECTION_REPLACE: "selection-replace";
|
|
2045
|
+
readonly VISIBILITY_SET: "visibility-set";
|
|
2046
|
+
readonly SIR_SYNC: "sir-sync";
|
|
2047
|
+
};
|
|
2048
|
+
declare const DISPATCH_ACTION_KINDS: ReadonlyArray<DispatchActionKind>;
|
|
2049
|
+
|
|
2050
|
+
/**
|
|
2051
|
+
* Canonical vocabulary of activity-creation triggers.
|
|
2052
|
+
*
|
|
2053
|
+
* The engine's `activity-create` intent does NOT carry this field — these
|
|
2054
|
+
* kinds name the USER trigger the consumer's creation hub routes on before
|
|
2055
|
+
* dispatching (`requestActivityCreation` / `CreateActivityIntent` in
|
|
2056
|
+
* `react_client` core path). The engine owns the vocabulary so the client
|
|
2057
|
+
* and the bridge import ONE canonical source instead of retyping the
|
|
2058
|
+
* literals (CONSTANTES_DECISIONES paso 1). The string VALUES are a frozen
|
|
2059
|
+
* contract — verified verbatim against the client union
|
|
2060
|
+
* (`core/features/createActivity/intents.ts`) on 2026-07-02.
|
|
2061
|
+
*
|
|
2062
|
+
* - `CHILD` — per-row "+" button (runs the lookahead guard).
|
|
2063
|
+
* - `CHILD_CONFIRMED` — lookahead-cleanup modal confirm (guard already passed).
|
|
2064
|
+
* - `LINE` — green bar "add-task-by-line".
|
|
2065
|
+
* - `ROOT` — generic "+" (header / create), no guard.
|
|
2066
|
+
*/
|
|
2067
|
+
declare const CREATION_KIND: {
|
|
2068
|
+
readonly CHILD: "child";
|
|
2069
|
+
readonly CHILD_CONFIRMED: "child-confirmed";
|
|
2070
|
+
readonly LINE: "line";
|
|
2071
|
+
readonly ROOT: "root";
|
|
2072
|
+
};
|
|
2073
|
+
type CreationKind = (typeof CREATION_KIND)[keyof typeof CREATION_KIND];
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* Canonical catalog of dispatch rejection reasons.
|
|
2077
|
+
*
|
|
2078
|
+
* `DispatchResult.reason` stays `string` on purpose: it is the cross-domain
|
|
2079
|
+
* bag that also carries pipeline gate codes (`ParseErrorCode`,
|
|
2080
|
+
* `ValidationReason`, `EditDenialReason`) and the link-engine rejected
|
|
2081
|
+
* verdicts. This module covers the reasons DISPATCH ITSELF produces, so
|
|
2082
|
+
* producers and tests share one compile-checked source. The string VALUES
|
|
2083
|
+
* are a frozen contract — the bridge matches on them.
|
|
2084
|
+
*/
|
|
2085
|
+
declare const REJECTION_REASON: {
|
|
2086
|
+
readonly CANNOT_EDIT: "cannot_edit";
|
|
2087
|
+
readonly PARSE_ERROR: "parse_error";
|
|
2088
|
+
readonly INVALID: "invalid";
|
|
2089
|
+
readonly CUSTOM_ID_DUPLICATE: "custom_id_duplicate";
|
|
2090
|
+
readonly INVALID_LINK_TYPE: "invalid_link_type";
|
|
2091
|
+
readonly INVALID_LINK_LAG: "invalid_link_lag";
|
|
2092
|
+
readonly INVALID_VALUE: "invalid_value";
|
|
2093
|
+
readonly NO_PIPELINE_FOR_COLUMN: "no_pipeline_for_column";
|
|
2094
|
+
readonly ACTIVITY_NOT_FOUND: "activity_not_found";
|
|
2095
|
+
readonly PARENT_NOT_FOUND: "parent_not_found";
|
|
2096
|
+
readonly ACTIVITY_IDS_EMPTY: "activity_ids_empty";
|
|
2097
|
+
readonly ANCHOR_SIBLING_CONFLICT: "anchor_sibling_conflict";
|
|
2098
|
+
readonly ANCHOR_SIBLING_NOT_FOUND: "anchor_sibling_not_found";
|
|
2099
|
+
readonly ANCHOR_SIBLING_WRONG_PARENT: "anchor_sibling_wrong_parent";
|
|
2100
|
+
readonly PARENT_FROZEN_BY_SIR: "parent_frozen_by_sir";
|
|
2101
|
+
readonly CANNOT_MOVE_INTO_OWN_DESCENDANT: "cannot_move_into_own_descendant";
|
|
2102
|
+
readonly INDENT_NO_ELIGIBLE_SIBLING: "indent_no_eligible_sibling";
|
|
2103
|
+
readonly OUTDENT_ROOT_LEVEL_NOT_EDITABLE: "outdent_root_level_not_editable";
|
|
2104
|
+
readonly INVALID_PROGRESS_VALUE: "invalid_progress_value_must_be_0_or_100";
|
|
2105
|
+
};
|
|
2106
|
+
type DispatchRejectReason = (typeof REJECTION_REASON)[keyof typeof REJECTION_REASON];
|
|
2107
|
+
|
|
2108
|
+
/**
|
|
2109
|
+
* Amplitude event names emitted by the dispatch layer (structure mutations).
|
|
2110
|
+
* Analytics CONTRACT — values must stay byte-identical to production.
|
|
2111
|
+
* Column-level entry events live in `columns/shared/trackingEvents.ts`.
|
|
2112
|
+
*/
|
|
2113
|
+
declare const DISPATCH_TRACK_EVENT: {
|
|
2114
|
+
readonly ACTIVITY_CREATION: "schedule_activity_creation";
|
|
2115
|
+
readonly ACTIVITY_DELETION: "schedule_activity_deletion";
|
|
2116
|
+
readonly ACTIVITY_MOVE: "schedule_activity_move";
|
|
2117
|
+
readonly ACTIVITY_INDENT: "schedule_activity_indent";
|
|
2118
|
+
readonly ACTIVITY_OUTDENT: "schedule_activity_outdent";
|
|
2119
|
+
};
|
|
2120
|
+
type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
|
|
2121
|
+
|
|
2122
|
+
export { ACTIVITY_TYPE, type Activity, type ActivityCreter, type ActivityId$1 as ActivityId, type ActivityType, type BackendActivityInput, type BackendCalendar, type BackendCalendarException, type BackendInput, type BackendLink, type BackendScheduleImpactRequests, type BackendSector, type BackendShift, type BaselinePoint, type BaselineVersion, CALENDAR_UNIT, COLUMN, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CREATION_KIND, type Calendar, type CalendarId$1 as CalendarId, type CalendarUnit, type CalendarWorktime, type ChangeSet, type ColumnName, type ConstraintType, type ConstraintWarning, type CreationKind, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_TRACK_EVENT, type DispatchAction, type DispatchActionKind, type DispatchOptions, type DispatchRejectReason, type DispatchResult, type DispatchTrackEvent, type EntityChange, LINK_CODE_TO_TYPE, LINK_TYPE, LINK_TYPE_CODE, type Link, type LinkId, type LinkType, type LinkTypeCode, type LinksBatchOperation, NEW_ACTIVITY_DEFAULTS, type OutputUnit, type ParsedInput, type PastedActivityInput, type PastedLinkInput, type PendingRequest, REJECTION_REASON, ROOT_PARENT_ID, ScheduleCore, type ScheduleCoreInput, type ScheduleCoreReporter, type ScheduleCoreStatus, type ScheduleEffect, type SectorMetadata, type StatusCriteria, type TrackingEvent, WORK_TIME_DIRECTION, type WorkTimeDirection, applyBaselinePoints, checkNoUpdatedLinks, computeExpectedProgress, expectedProgressFromBaseline, getActiveBaseline, getUnsavedActivities, isRootParent, normalizeParentKey, parseFromBackend, recomputeAllPonderators, recomputeAllProgressRollup, willRunCriticalPath, yieldToBrowser };
|