@outbuild-company/schedule-core 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/schedule-core.global.js +1 -1
- package/dist/cdn/schedule-core.global.js.map +1 -1
- package/dist/index.cjs +352 -307
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -32
- package/dist/index.d.ts +20 -32
- package/dist/index.js +352 -307
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -16171,132 +16171,241 @@ var WORKING_DAY_TRUE_VALUES = /* @__PURE__ */ new Set([
|
|
|
16171
16171
|
"true"
|
|
16172
16172
|
]);
|
|
16173
16173
|
var DEFAULT_ACTIVITY_CRETER = "DURATION";
|
|
16174
|
-
var VALID_ACTIVITY_CRETER_VALUES = /* @__PURE__ */ new Set(["DURATION", "COST", "HH"]);
|
|
16175
16174
|
var DEFAULT_CONSTRAINT_TYPE = "asap";
|
|
16175
|
+
var CONSTRAINT_LABEL_MAP = {
|
|
16176
|
+
"AS SOON AS POSSIBLE": "asap",
|
|
16177
|
+
"AS LATE AS POSSIBLE": "alap",
|
|
16178
|
+
"START NO EARLIER THAN": "snet",
|
|
16179
|
+
"START NO LATER THAN": "snlt",
|
|
16180
|
+
"FINISH NO EARLIER THAN": "fnet",
|
|
16181
|
+
"FINISH NO LATER THAN": "fnlt",
|
|
16182
|
+
"MUST START ON": "mso",
|
|
16183
|
+
"MUST FINISH ON": "mfo"
|
|
16184
|
+
};
|
|
16176
16185
|
|
|
16177
|
-
// src/boundary/backend/
|
|
16178
|
-
function
|
|
16179
|
-
const
|
|
16180
|
-
|
|
16181
|
-
const
|
|
16182
|
-
|
|
16183
|
-
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16186
|
+
// src/boundary/backend/normalize-activity.ts
|
|
16187
|
+
function normalizeActivities(rawActivities, context) {
|
|
16188
|
+
const activities = rawActivities.map((raw) => parseActivity(raw, context));
|
|
16189
|
+
forceRootProjectToRoot(activities);
|
|
16190
|
+
const activityIds = new Set(activities.map((activity) => activity.id));
|
|
16191
|
+
reparentOrphansToRoot(activities, activityIds, context.reporter);
|
|
16192
|
+
return { activities, activityIds };
|
|
16193
|
+
}
|
|
16194
|
+
function proplannerIdField(raw) {
|
|
16195
|
+
return typeof raw.id === "number" ? { proplannerId: raw.id } : {};
|
|
16196
|
+
}
|
|
16197
|
+
function isAutoSchedulable(progress) {
|
|
16198
|
+
return progress === 0;
|
|
16199
|
+
}
|
|
16200
|
+
function parseConstraintLabel(label, reporter) {
|
|
16201
|
+
if (!label) return null;
|
|
16202
|
+
const normalized = CONSTRAINT_LABEL_MAP[label.toUpperCase()];
|
|
16203
|
+
if (normalized === void 0) {
|
|
16204
|
+
reporter.warn(
|
|
16205
|
+
`[parseFromBackend] unknown constraint label "${label}" \u2014 defaulting to "asap"`
|
|
16189
16206
|
);
|
|
16190
|
-
return
|
|
16191
|
-
}
|
|
16192
|
-
|
|
16193
|
-
(a) => a.correlative_id === 0 && a.type === "project"
|
|
16194
|
-
);
|
|
16195
|
-
if (rootProject) rootProject.parent = ROOT_PARENT_ID;
|
|
16196
|
-
const activityIds = new Set(activities.map((a) => a.id));
|
|
16197
|
-
reparentOrphansToRoot(activities, activityIds, reporter);
|
|
16198
|
-
const links = parseLinksDroppingDanglingReferences(
|
|
16199
|
-
input.links,
|
|
16200
|
-
activityIds,
|
|
16201
|
-
sector.hoursPerDay,
|
|
16202
|
-
reporter
|
|
16203
|
-
);
|
|
16204
|
-
return { sector, activities, links, calendars, baseCalendars };
|
|
16207
|
+
return DEFAULT_CONSTRAINT_TYPE;
|
|
16208
|
+
}
|
|
16209
|
+
return normalized;
|
|
16205
16210
|
}
|
|
16206
|
-
function
|
|
16207
|
-
const
|
|
16208
|
-
const
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
|
|
16211
|
+
function resolveConstraint(raw, reporter) {
|
|
16212
|
+
const constraintType = parseConstraintLabel(raw.constraint, reporter);
|
|
16213
|
+
const constraintTakesDate = constraintType !== "asap" && constraintType !== "alap";
|
|
16214
|
+
const constraintDate = constraintTakesDate && raw.constraint_date ? parseBackendDate(raw.constraint_date) : null;
|
|
16215
|
+
return { constraint_type: constraintType, constraint_date: constraintDate };
|
|
16216
|
+
}
|
|
16217
|
+
function resolveDatesAndDuration(raw) {
|
|
16218
|
+
const startDate = parseBackendDate(raw.start_date);
|
|
16219
|
+
const isMilestone = raw.type === "milestone";
|
|
16220
|
+
return {
|
|
16221
|
+
start_date: startDate,
|
|
16222
|
+
end_date: isMilestone ? startDate : parseBackendDate(raw.end_date),
|
|
16223
|
+
duration: isMilestone ? 0 : Number(raw.duration)
|
|
16214
16224
|
};
|
|
16215
|
-
return parseCalendar(reshaped);
|
|
16216
16225
|
}
|
|
16217
|
-
function
|
|
16218
|
-
const
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
|
|
16226
|
+
function buildLoadTimeBaselines(raw) {
|
|
16227
|
+
const isMilestone = raw.type === "milestone";
|
|
16228
|
+
return {
|
|
16229
|
+
old_progress_registered: Number(raw.progress),
|
|
16230
|
+
old_duration_registered: isMilestone ? 0 : Number(raw.duration),
|
|
16231
|
+
old_cost_registered: Number(raw.cost),
|
|
16232
|
+
old_used_cost_registered: Number(raw.used_cost ?? 0),
|
|
16233
|
+
old_real_cost_registered: Number(raw.real_cost ?? 0),
|
|
16234
|
+
old_hhWorkTime_registered: Number(raw.hhWorkTime)
|
|
16235
|
+
};
|
|
16223
16236
|
}
|
|
16224
16237
|
function resolveCalendarId(rawCalendarId, knownCalendarIds, defaultCalendarId) {
|
|
16225
16238
|
if (rawCalendarId == null) return defaultCalendarId;
|
|
16226
16239
|
const coerced = String(rawCalendarId);
|
|
16227
16240
|
return knownCalendarIds.has(coerced) ? coerced : defaultCalendarId;
|
|
16228
16241
|
}
|
|
16229
|
-
function
|
|
16230
|
-
|
|
16231
|
-
|
|
16232
|
-
|
|
16233
|
-
|
|
16234
|
-
|
|
16235
|
-
|
|
16236
|
-
|
|
16242
|
+
function parseNewActivitiesArray(raw) {
|
|
16243
|
+
if (raw == null || raw === "") return [];
|
|
16244
|
+
if (Array.isArray(raw)) return raw.map(String);
|
|
16245
|
+
if (typeof raw !== "string") return [];
|
|
16246
|
+
try {
|
|
16247
|
+
const parsed2 = JSON.parse(raw);
|
|
16248
|
+
return Array.isArray(parsed2) ? parsed2.map(String) : [];
|
|
16249
|
+
} catch {
|
|
16250
|
+
return [];
|
|
16237
16251
|
}
|
|
16238
|
-
return dedupeLinksBySignature(parsedLinks);
|
|
16239
16252
|
}
|
|
16240
|
-
function
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16253
|
+
function hasUsableId(entry) {
|
|
16254
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
16255
|
+
if (!("id" in entry)) return false;
|
|
16256
|
+
const value = entry.id;
|
|
16257
|
+
return typeof value === "string" || typeof value === "number";
|
|
16258
|
+
}
|
|
16259
|
+
function parsePendingRequests(raw) {
|
|
16260
|
+
if (!Array.isArray(raw)) return [];
|
|
16261
|
+
const requests = [];
|
|
16262
|
+
for (const entry of raw) {
|
|
16263
|
+
if (hasUsableId(entry)) requests.push({ id: String(entry.id) });
|
|
16248
16264
|
}
|
|
16249
|
-
return
|
|
16265
|
+
return requests;
|
|
16250
16266
|
}
|
|
16251
|
-
function
|
|
16252
|
-
const
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16256
|
-
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
const
|
|
16267
|
+
function parseActivity(raw, context) {
|
|
16268
|
+
const id = String(raw.unique_id);
|
|
16269
|
+
const parent = raw.parent_id === ROOT_PARENT_ID ? ROOT_PARENT_ID : String(raw.parent_id);
|
|
16270
|
+
const calendarId = resolveCalendarId(
|
|
16271
|
+
raw.calendarId,
|
|
16272
|
+
context.knownCalendarIds,
|
|
16273
|
+
context.defaultCalendarId
|
|
16274
|
+
);
|
|
16275
|
+
const inheritedDefaultCalendar = raw.calendarId == null || !context.knownCalendarIds.has(String(raw.calendarId));
|
|
16276
|
+
const assignedDefaultCalendar = calendarId != null && inheritedDefaultCalendar;
|
|
16277
|
+
const progress = Number(raw.progress);
|
|
16278
|
+
const { constraint_type: constraintType, constraint_date: constraintDate } = resolveConstraint(raw, context.reporter);
|
|
16279
|
+
const {
|
|
16280
|
+
start_date: startDate,
|
|
16281
|
+
end_date: endDate,
|
|
16282
|
+
duration
|
|
16283
|
+
} = resolveDatesAndDuration(raw);
|
|
16260
16284
|
return {
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16285
|
+
...proplannerIdField(raw),
|
|
16286
|
+
id,
|
|
16287
|
+
parent,
|
|
16288
|
+
text: raw.name,
|
|
16289
|
+
description: raw.description ?? "",
|
|
16290
|
+
type: raw.type,
|
|
16291
|
+
start_date: startDate,
|
|
16292
|
+
end_date: endDate,
|
|
16293
|
+
duration,
|
|
16294
|
+
constraint_type: constraintType,
|
|
16295
|
+
constraint_date: constraintDate,
|
|
16296
|
+
auto_scheduling: isAutoSchedulable(progress),
|
|
16297
|
+
calendar_id: calendarId,
|
|
16298
|
+
assignedDefaultCalendar,
|
|
16299
|
+
progress,
|
|
16300
|
+
cost: Number(raw.cost),
|
|
16301
|
+
used_cost: Number(raw.used_cost ?? 0),
|
|
16302
|
+
real_cost: Number(raw.real_cost ?? 0),
|
|
16303
|
+
hhWorkTime: Number(raw.hhWorkTime),
|
|
16304
|
+
real_work: Number(raw.real_work ?? 0),
|
|
16305
|
+
ponderator: Number(raw.ponderator),
|
|
16306
|
+
correlative_id: raw.correlative_id,
|
|
16307
|
+
is_lookahead: raw.isOnLookahead,
|
|
16308
|
+
visible: raw.should_be_showed !== false,
|
|
16309
|
+
newActivitiesArray: parseNewActivitiesArray(raw.newActivitiesArray),
|
|
16310
|
+
activityModifications: raw.activitymodifications,
|
|
16311
|
+
unique_correlative_id: raw.unique_correlative_id,
|
|
16312
|
+
ganttId: raw.ganttId,
|
|
16313
|
+
sectorId: raw.sectorId,
|
|
16314
|
+
companyId: raw.companyId,
|
|
16315
|
+
subcontractId: raw.subcontractId,
|
|
16316
|
+
subcontract: raw.subcontract,
|
|
16317
|
+
custom_id: raw.custom_id,
|
|
16318
|
+
hasCustomPonderator: raw.hasCustomPonderator,
|
|
16319
|
+
has_childs: raw.has_childs,
|
|
16320
|
+
sumOfDurationRecursively: raw.sumOfDurationRecursively,
|
|
16321
|
+
freeSlack: raw.freeSlack,
|
|
16322
|
+
is_critical: raw.is_critical,
|
|
16323
|
+
hasNewActivities: raw.hasNewActivities,
|
|
16324
|
+
isNewActivity: raw.isNewActivity,
|
|
16325
|
+
tasks: raw.tasks,
|
|
16326
|
+
responsables: raw.responsables,
|
|
16327
|
+
tags: raw.tags,
|
|
16328
|
+
createdAt: raw.createdAt,
|
|
16329
|
+
updatedAt: raw.updatedAt,
|
|
16330
|
+
pendingRequests: parsePendingRequests(raw.pendingRequests),
|
|
16331
|
+
for_disable_milestone_duration: Math.round(duration * context.hoursPerDay),
|
|
16332
|
+
...buildLoadTimeBaselines(raw),
|
|
16333
|
+
...raw.date_origin !== void 0 ? {
|
|
16334
|
+
date_origin: raw.date_origin === null ? null : parseBackendDate(raw.date_origin)
|
|
16335
|
+
} : {},
|
|
16336
|
+
...raw.baseline_points !== void 0 ? { baseline_points: raw.baseline_points } : {}
|
|
16275
16337
|
};
|
|
16276
16338
|
}
|
|
16277
|
-
function
|
|
16278
|
-
|
|
16339
|
+
function forceRootProjectToRoot(activities) {
|
|
16340
|
+
const rootProject = activities.find(
|
|
16341
|
+
(activity) => activity.correlative_id === 0 && activity.type === "project"
|
|
16342
|
+
);
|
|
16343
|
+
if (rootProject) rootProject.parent = ROOT_PARENT_ID;
|
|
16279
16344
|
}
|
|
16280
|
-
function
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
|
|
16345
|
+
function reparentOrphansToRoot(activities, ids, reporter) {
|
|
16346
|
+
for (const activity of activities) {
|
|
16347
|
+
if (activity.parent === ROOT_PARENT_ID) continue;
|
|
16348
|
+
if (!ids.has(activity.parent)) {
|
|
16349
|
+
reporter.warn(
|
|
16350
|
+
`[parseFromBackend] activity "${activity.id}" references unknown parent "${activity.parent}" \u2014 re-parenting to root ('0')`
|
|
16351
|
+
);
|
|
16352
|
+
activity.parent = ROOT_PARENT_ID;
|
|
16353
|
+
}
|
|
16354
|
+
}
|
|
16284
16355
|
}
|
|
16285
|
-
|
|
16286
|
-
|
|
16287
|
-
|
|
16288
|
-
const
|
|
16356
|
+
|
|
16357
|
+
// src/boundary/backend/normalize-calendar.ts
|
|
16358
|
+
function normalizeCalendars(schedulingCalendars, sectorCalendars, rawBaseCalendars) {
|
|
16359
|
+
const hasSchedulingCalendars = schedulingCalendars !== void 0 && schedulingCalendars.length > 0;
|
|
16360
|
+
const source = hasSchedulingCalendars ? schedulingCalendars : sectorCalendars ?? [];
|
|
16361
|
+
const calendars = source.map(parseSchedulingCalendar);
|
|
16362
|
+
const baseCalendars = (rawBaseCalendars ?? []).map(parseBaseCalendar);
|
|
16363
|
+
return {
|
|
16364
|
+
calendars,
|
|
16365
|
+
baseCalendars,
|
|
16366
|
+
knownCalendarIds: new Set(calendars.map((calendar) => calendar.id)),
|
|
16367
|
+
defaultCalendarId: pickDefaultCalendarId(calendars)
|
|
16368
|
+
};
|
|
16369
|
+
}
|
|
16370
|
+
function pickDefaultCalendarId(calendars) {
|
|
16371
|
+
const flaggedDefault = calendars.find((calendar) => calendar.is_default);
|
|
16372
|
+
if (flaggedDefault) return flaggedDefault.id;
|
|
16373
|
+
const firstCalendar = calendars[0];
|
|
16374
|
+
if (firstCalendar) return firstCalendar.id;
|
|
16375
|
+
return null;
|
|
16376
|
+
}
|
|
16377
|
+
function buildCalendar(raw, shape) {
|
|
16378
|
+
const workingMask = parseWorkingDays(raw.working_days);
|
|
16379
|
+
const { days, hours } = parseWorktimeDays(raw, workingMask);
|
|
16380
|
+
const dates = parseExceptionDays(shape.exceptionsSource);
|
|
16289
16381
|
const worktime = { days, hours };
|
|
16290
16382
|
if (dates) worktime.dates = dates;
|
|
16291
|
-
const rawBaseDefault = c.baseDefault;
|
|
16292
16383
|
return {
|
|
16293
|
-
id: String(
|
|
16294
|
-
name:
|
|
16295
|
-
is_default:
|
|
16296
|
-
...
|
|
16384
|
+
id: `${String(raw.id)}${shape.idSuffix}`,
|
|
16385
|
+
name: raw.name,
|
|
16386
|
+
is_default: shape.isDefault,
|
|
16387
|
+
...shape.baseDefault !== void 0 ? { baseDefault: shape.baseDefault } : {},
|
|
16297
16388
|
worktime
|
|
16298
16389
|
};
|
|
16299
16390
|
}
|
|
16391
|
+
function parseSchedulingCalendar(raw) {
|
|
16392
|
+
const rawBaseDefault = raw.baseDefault;
|
|
16393
|
+
const baseDefault = typeof rawBaseDefault === "boolean" ? rawBaseDefault : void 0;
|
|
16394
|
+
return buildCalendar(raw, {
|
|
16395
|
+
idSuffix: "",
|
|
16396
|
+
isDefault: Boolean(raw.is_default),
|
|
16397
|
+
baseDefault,
|
|
16398
|
+
exceptionsSource: raw.exceptiondays
|
|
16399
|
+
});
|
|
16400
|
+
}
|
|
16401
|
+
function parseBaseCalendar(raw) {
|
|
16402
|
+
return buildCalendar(raw, {
|
|
16403
|
+
idSuffix: "-base",
|
|
16404
|
+
isDefault: false,
|
|
16405
|
+
baseDefault: Boolean(raw.is_default),
|
|
16406
|
+
exceptionsSource: raw.baseexceptiondays ?? raw.exceptiondays
|
|
16407
|
+
});
|
|
16408
|
+
}
|
|
16300
16409
|
function parseExceptionDays(exceptions) {
|
|
16301
16410
|
if (!Array.isArray(exceptions) || exceptions.length === 0) return null;
|
|
16302
16411
|
const map = {};
|
|
@@ -16332,9 +16441,8 @@ function parseExceptionDate(raw) {
|
|
|
16332
16441
|
const year = Number(match[1]);
|
|
16333
16442
|
const month = Number(match[2]);
|
|
16334
16443
|
const day = Number(match[3]);
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
}
|
|
16444
|
+
const isValidDate4 = Number.isFinite(year) && Number.isFinite(month) && Number.isFinite(day);
|
|
16445
|
+
if (!isValidDate4) return null;
|
|
16338
16446
|
return Date.UTC(year, month - 1, day);
|
|
16339
16447
|
}
|
|
16340
16448
|
function exceptionToDateValue(exception) {
|
|
@@ -16346,17 +16454,18 @@ function exceptionToDateValue(exception) {
|
|
|
16346
16454
|
}
|
|
16347
16455
|
return void 0;
|
|
16348
16456
|
}
|
|
16457
|
+
var TIME_OF_DAY = /^\d{1,2}:\d{2}$/;
|
|
16349
16458
|
function extractExceptionWindows(shifts) {
|
|
16350
16459
|
const windows = [];
|
|
16351
16460
|
for (const shift of shifts) {
|
|
16352
16461
|
const csvs = splitShiftString(shift?.shift_string);
|
|
16353
16462
|
if (!csvs) continue;
|
|
16354
|
-
|
|
16463
|
+
const isCompactWindow = !csvs.startsCsv.includes(",") && !csvs.endsCsv.includes(",");
|
|
16464
|
+
if (isCompactWindow) {
|
|
16355
16465
|
const start = csvs.startsCsv.trim();
|
|
16356
16466
|
const end = csvs.endsCsv.trim();
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
}
|
|
16467
|
+
const isUsableWindow = !isMissingValue(start) && !isMissingValue(end) && TIME_OF_DAY.test(start) && TIME_OF_DAY.test(end);
|
|
16468
|
+
if (isUsableWindow) windows.push(`${start}-${end}`);
|
|
16360
16469
|
continue;
|
|
16361
16470
|
}
|
|
16362
16471
|
const perDay = shiftCsvToPerDayWindows(csvs.startsCsv, csvs.endsCsv);
|
|
@@ -16366,17 +16475,16 @@ function extractExceptionWindows(shifts) {
|
|
|
16366
16475
|
windows.sort(compareWindowByStart);
|
|
16367
16476
|
return windows;
|
|
16368
16477
|
}
|
|
16369
|
-
var TIME_OF_DAY = /^\d{1,2}:\d{2}$/;
|
|
16370
16478
|
function windowStartMinutes(window2) {
|
|
16371
16479
|
const dash = window2.indexOf("-");
|
|
16372
16480
|
const start = dash > 0 ? window2.slice(0, dash) : window2;
|
|
16373
|
-
const [
|
|
16374
|
-
return (Number(
|
|
16481
|
+
const [hour, minute] = start.split(":");
|
|
16482
|
+
return (Number(hour) || 0) * 60 + (Number(minute) || 0);
|
|
16375
16483
|
}
|
|
16376
|
-
function compareWindowByStart(
|
|
16377
|
-
return windowStartMinutes(
|
|
16484
|
+
function compareWindowByStart(first, second) {
|
|
16485
|
+
return windowStartMinutes(first) - windowStartMinutes(second);
|
|
16378
16486
|
}
|
|
16379
|
-
function parseWorktimeDays(
|
|
16487
|
+
function parseWorktimeDays(raw, workingMask) {
|
|
16380
16488
|
const windowsByDay = Array.from({ length: 7 }, () => []);
|
|
16381
16489
|
const addCsvPair = (startsCsv, endsCsv) => {
|
|
16382
16490
|
const perDay = shiftCsvToPerDayWindows(startsCsv, endsCsv);
|
|
@@ -16386,13 +16494,15 @@ function parseWorktimeDays(c, workingMask) {
|
|
|
16386
16494
|
if (window2 && dayWindows) dayWindows.push(window2);
|
|
16387
16495
|
}
|
|
16388
16496
|
};
|
|
16389
|
-
|
|
16390
|
-
|
|
16497
|
+
const hasNestedShifts = Array.isArray(raw.shifts) && raw.shifts.length > 0;
|
|
16498
|
+
const hasFlatShift = typeof raw.shift_start === "string" && typeof raw.shift_end === "string";
|
|
16499
|
+
if (hasNestedShifts) {
|
|
16500
|
+
for (const shift of raw.shifts) {
|
|
16391
16501
|
const csvs = splitShiftString(shift?.shift_string);
|
|
16392
16502
|
if (csvs) addCsvPair(csvs.startsCsv, csvs.endsCsv);
|
|
16393
16503
|
}
|
|
16394
|
-
} else if (
|
|
16395
|
-
addCsvPair(
|
|
16504
|
+
} else if (hasFlatShift) {
|
|
16505
|
+
addCsvPair(raw.shift_start, raw.shift_end);
|
|
16396
16506
|
}
|
|
16397
16507
|
for (const dayWindows of windowsByDay) dayWindows.sort(compareWindowByStart);
|
|
16398
16508
|
const firstWorkingWindows = windowsByDay.find(
|
|
@@ -16414,11 +16524,10 @@ function shiftCsvToPerDayWindows(startsCsv, endsCsv) {
|
|
|
16414
16524
|
if (typeof startsCsv !== "string" || typeof endsCsv !== "string") {
|
|
16415
16525
|
return empty;
|
|
16416
16526
|
}
|
|
16417
|
-
const starts = startsCsv.split(",").map((
|
|
16418
|
-
const ends = endsCsv.split(",").map((
|
|
16419
|
-
|
|
16420
|
-
|
|
16421
|
-
}
|
|
16527
|
+
const starts = startsCsv.split(",").map((value) => value.trim());
|
|
16528
|
+
const ends = endsCsv.split(",").map((value) => value.trim());
|
|
16529
|
+
const isAlignedWeek = starts.length === ends.length && starts.length > 0 && starts.length <= WEEKDAYS_PER_WEEK;
|
|
16530
|
+
if (!isAlignedWeek) return empty;
|
|
16422
16531
|
const windows = [...empty];
|
|
16423
16532
|
for (let i = 0; i < starts.length; i += 1) {
|
|
16424
16533
|
const start = starts[i];
|
|
@@ -16439,196 +16548,48 @@ function splitShiftString(raw) {
|
|
|
16439
16548
|
}
|
|
16440
16549
|
function parseWorkingDays(csv) {
|
|
16441
16550
|
if (typeof csv !== "string") return [...CALENDAR_FALLBACK_WORKING_DAYS];
|
|
16442
|
-
const parts = csv.split(",").map((
|
|
16443
|
-
if (parts.length !== WEEKDAYS_PER_WEEK)
|
|
16551
|
+
const parts = csv.split(",").map((value) => value.trim());
|
|
16552
|
+
if (parts.length !== WEEKDAYS_PER_WEEK) {
|
|
16444
16553
|
return [...CALENDAR_FALLBACK_WORKING_DAYS];
|
|
16445
|
-
|
|
16554
|
+
}
|
|
16555
|
+
return parts.map((part) => WORKING_DAY_TRUE_VALUES.has(part));
|
|
16446
16556
|
}
|
|
16447
16557
|
var MISSING_SHIFT_VALUES = /* @__PURE__ */ new Set(["false", "null"]);
|
|
16448
16558
|
function isMissingValue(value) {
|
|
16449
16559
|
return !value || MISSING_SHIFT_VALUES.has(value);
|
|
16450
16560
|
}
|
|
16451
|
-
|
|
16452
|
-
|
|
16453
|
-
|
|
16454
|
-
"
|
|
16455
|
-
"sectorId",
|
|
16456
|
-
"companyId",
|
|
16457
|
-
"subcontractId",
|
|
16458
|
-
"subcontract",
|
|
16459
|
-
"custom_id",
|
|
16460
|
-
// Computed flags / metrics from the backend.
|
|
16461
|
-
"hasCustomPonderator",
|
|
16462
|
-
"has_childs",
|
|
16463
|
-
"sumOfDurationRecursively",
|
|
16464
|
-
"freeSlack",
|
|
16465
|
-
"is_critical",
|
|
16466
|
-
"hasNewActivities",
|
|
16467
|
-
"isNewActivity",
|
|
16468
|
-
// Live validation field read by endDatePipeline; explicit so it survives by design.
|
|
16469
|
-
"date_origin",
|
|
16470
|
-
"baseline_points",
|
|
16471
|
-
// Relation arrays.
|
|
16472
|
-
"tasks",
|
|
16473
|
-
"responsables",
|
|
16474
|
-
"tags",
|
|
16475
|
-
// `pendingRequests` is NOT passthrough — parsed explicitly below to the
|
|
16476
|
-
// typed `{ id: string }[]` shape (SIR freeze authority, A1).
|
|
16477
|
-
// Timestamps.
|
|
16478
|
-
"createdAt",
|
|
16479
|
-
"updatedAt"
|
|
16480
|
-
]);
|
|
16481
|
-
function proplannerIdField(a) {
|
|
16482
|
-
return typeof a.id === "number" ? { proplannerId: a.id } : {};
|
|
16483
|
-
}
|
|
16484
|
-
function collectPassthrough(a) {
|
|
16485
|
-
const passthrough = {};
|
|
16486
|
-
for (const key of ACTIVITY_PASSTHROUGH_FIELDS) {
|
|
16487
|
-
if (key in a) {
|
|
16488
|
-
passthrough[key] = a[key];
|
|
16489
|
-
}
|
|
16490
|
-
}
|
|
16491
|
-
return passthrough;
|
|
16492
|
-
}
|
|
16493
|
-
function resolveConstraint(a, reporter) {
|
|
16494
|
-
const constraintType = parseConstraintLabel(a.constraint, reporter);
|
|
16495
|
-
const constraintTakesDate = constraintType !== "asap" && constraintType !== "alap";
|
|
16496
|
-
const constraintDate = constraintTakesDate && a.constraint_date ? parseBackendDate(a.constraint_date) : null;
|
|
16497
|
-
return { constraint_type: constraintType, constraint_date: constraintDate };
|
|
16498
|
-
}
|
|
16499
|
-
function resolveDatesAndDuration(a) {
|
|
16500
|
-
const startDate = parseBackendDate(a.start_date);
|
|
16501
|
-
const isMilestone = a.type === "milestone";
|
|
16502
|
-
return {
|
|
16503
|
-
start_date: startDate,
|
|
16504
|
-
end_date: isMilestone ? startDate : parseBackendDate(a.end_date),
|
|
16505
|
-
duration: isMilestone ? 0 : Number(a.duration)
|
|
16506
|
-
};
|
|
16507
|
-
}
|
|
16508
|
-
function buildLoadTimeBaselines(a) {
|
|
16509
|
-
const isMilestone = a.type === "milestone";
|
|
16510
|
-
return {
|
|
16511
|
-
old_progress_registered: Number(a.progress),
|
|
16512
|
-
old_duration_registered: isMilestone ? 0 : Number(a.duration),
|
|
16513
|
-
old_cost_registered: Number(a.cost),
|
|
16514
|
-
old_used_cost_registered: Number(a.used_cost ?? 0),
|
|
16515
|
-
old_real_cost_registered: Number(a.real_cost ?? 0),
|
|
16516
|
-
old_hhWorkTime_registered: Number(a.hhWorkTime)
|
|
16517
|
-
};
|
|
16518
|
-
}
|
|
16519
|
-
function parseActivity(a, calendarIds, defaultCalendarId, reporter) {
|
|
16520
|
-
const id = String(a.unique_id);
|
|
16521
|
-
const parent = a.parent_id === ROOT_PARENT_ID ? ROOT_PARENT_ID : String(a.parent_id);
|
|
16522
|
-
const calendarId = resolveCalendarId(
|
|
16523
|
-
a.calendarId,
|
|
16524
|
-
calendarIds,
|
|
16525
|
-
defaultCalendarId
|
|
16526
|
-
);
|
|
16527
|
-
const assignedDefaultCalendar = calendarId != null && (a.calendarId == null || !calendarIds.has(String(a.calendarId)));
|
|
16528
|
-
const progress = Number(a.progress);
|
|
16529
|
-
const autoScheduling = isAutoSchedulable(progress);
|
|
16530
|
-
const { constraint_type: constraintType, constraint_date: constraintDate } = resolveConstraint(a, reporter);
|
|
16531
|
-
const {
|
|
16532
|
-
start_date: startDate,
|
|
16533
|
-
end_date: endDate,
|
|
16534
|
-
duration
|
|
16535
|
-
} = resolveDatesAndDuration(a);
|
|
16536
|
-
return {
|
|
16537
|
-
...collectPassthrough(a),
|
|
16538
|
-
...proplannerIdField(a),
|
|
16539
|
-
id,
|
|
16540
|
-
parent,
|
|
16541
|
-
text: a.name,
|
|
16542
|
-
description: a.description ?? "",
|
|
16543
|
-
type: a.type,
|
|
16544
|
-
start_date: startDate,
|
|
16545
|
-
end_date: endDate,
|
|
16546
|
-
duration,
|
|
16547
|
-
constraint_type: constraintType,
|
|
16548
|
-
constraint_date: constraintDate,
|
|
16549
|
-
progress,
|
|
16550
|
-
cost: Number(a.cost),
|
|
16551
|
-
used_cost: Number(a.used_cost ?? 0),
|
|
16552
|
-
real_cost: Number(a.real_cost ?? 0),
|
|
16553
|
-
hhWorkTime: Number(a.hhWorkTime),
|
|
16554
|
-
real_work: Number(a.real_work ?? 0),
|
|
16555
|
-
ponderator: Number(a.ponderator),
|
|
16556
|
-
calendar_id: calendarId,
|
|
16557
|
-
assignedDefaultCalendar,
|
|
16558
|
-
auto_scheduling: autoScheduling,
|
|
16559
|
-
correlative_id: a.correlative_id,
|
|
16560
|
-
is_lookahead: a.isOnLookahead,
|
|
16561
|
-
// View-model visibility bit: only an explicit backend `false` hides.
|
|
16562
|
-
visible: a.should_be_showed !== false,
|
|
16563
|
-
...buildLoadTimeBaselines(a),
|
|
16564
|
-
pendingRequests: parsePendingRequests(a.pendingRequests),
|
|
16565
|
-
activityModifications: a.activitymodifications,
|
|
16566
|
-
// D5 fix (2026-05-19): backend serializes newActivitiesArray as a
|
|
16567
|
-
// JSON-encoded string; the in-memory form must be a real array so dispatch
|
|
16568
|
-
// pipelines can iterate/mutate it. See DIVERGENCES_activity_create.md D5.
|
|
16569
|
-
newActivitiesArray: parseNewActivitiesArray(a.newActivitiesArray)
|
|
16570
|
-
// Cast: TS can't statically verify the `...collectPassthrough` spread
|
|
16571
|
-
// provides every required Activity field (the loop iterates a runtime Set,
|
|
16572
|
-
// not a typed contract). When the BE responds normally every passthrough
|
|
16573
|
-
// field is present; a missing field is a real data bug to surface at the
|
|
16574
|
-
// source, not mask via optional typing. `ActivityRuntime`
|
|
16575
|
-
// (= Activity & DhtmlxHeritageFields) because the literal includes the
|
|
16576
|
-
// old_*_registered baselines (DHTMLX heritage — G1); it is assignable to
|
|
16577
|
-
// Activity.
|
|
16578
|
-
};
|
|
16579
|
-
}
|
|
16580
|
-
function parseNewActivitiesArray(raw) {
|
|
16581
|
-
if (raw == null || raw === "") return [];
|
|
16582
|
-
if (Array.isArray(raw)) return raw.map(String);
|
|
16583
|
-
if (typeof raw !== "string") return [];
|
|
16584
|
-
try {
|
|
16585
|
-
const parsed2 = JSON.parse(raw);
|
|
16586
|
-
return Array.isArray(parsed2) ? parsed2.map(String) : [];
|
|
16587
|
-
} catch {
|
|
16588
|
-
return [];
|
|
16589
|
-
}
|
|
16590
|
-
}
|
|
16591
|
-
function parsePendingRequests(raw) {
|
|
16592
|
-
if (!Array.isArray(raw)) return [];
|
|
16593
|
-
return raw.filter((r) => r != null && typeof r === "object").map((r) => ({ id: String(r.id) }));
|
|
16561
|
+
|
|
16562
|
+
// src/boundary/backend/normalize-link.ts
|
|
16563
|
+
function isLinkTypeCode(value) {
|
|
16564
|
+
return value === "0" || value === "1" || value === "2" || value === "3";
|
|
16594
16565
|
}
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
"FINISH NO LATER THAN": "fnlt",
|
|
16602
|
-
"MUST START ON": "mso",
|
|
16603
|
-
"MUST FINISH ON": "mfo"
|
|
16604
|
-
};
|
|
16605
|
-
function parseConstraintLabel(label, reporter) {
|
|
16606
|
-
if (!label) return null;
|
|
16607
|
-
const normalized = CONSTRAINT_LABEL_MAP[label.toUpperCase()];
|
|
16608
|
-
if (normalized === void 0) {
|
|
16609
|
-
reporter.warn(
|
|
16610
|
-
`[parseFromBackend] unknown constraint label "${label}" \u2014 defaulting to "asap"`
|
|
16611
|
-
);
|
|
16612
|
-
return DEFAULT_CONSTRAINT_TYPE;
|
|
16613
|
-
}
|
|
16614
|
-
return normalized;
|
|
16566
|
+
function resolveLinkType(rawType) {
|
|
16567
|
+
if (typeof rawType !== "string") return null;
|
|
16568
|
+
const fromLabel = parseLinkTypeCode(rawType);
|
|
16569
|
+
if (fromLabel) return fromLabel;
|
|
16570
|
+
if (isLinkTypeCode(rawType)) return rawType;
|
|
16571
|
+
return null;
|
|
16615
16572
|
}
|
|
16616
|
-
function
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16573
|
+
function normalizeLinks(rawLinks, context) {
|
|
16574
|
+
const parsedLinks = [];
|
|
16575
|
+
for (const rawLink of rawLinks) {
|
|
16576
|
+
try {
|
|
16577
|
+
parsedLinks.push(
|
|
16578
|
+
parseLink(rawLink, context.knownActivityIds, context.hoursPerDay)
|
|
16579
|
+
);
|
|
16580
|
+
} catch (parseError2) {
|
|
16581
|
+
context.reporter.warn(
|
|
16582
|
+
"[parseFromBackend] dropping invalid link",
|
|
16583
|
+
parseError2
|
|
16622
16584
|
);
|
|
16623
|
-
activity.parent = ROOT_PARENT_ID;
|
|
16624
16585
|
}
|
|
16625
16586
|
}
|
|
16587
|
+
return dedupeLinksBySignature(parsedLinks);
|
|
16626
16588
|
}
|
|
16627
|
-
function parseLink(
|
|
16628
|
-
const
|
|
16629
|
-
const
|
|
16630
|
-
const
|
|
16631
|
-
const target = String(lr.target_id ?? lr.target ?? "");
|
|
16589
|
+
function parseLink(link, activityIds, hoursPerDay) {
|
|
16590
|
+
const id = String(link.unique_id ?? link.id);
|
|
16591
|
+
const source = String(link.source_id ?? link.source ?? "");
|
|
16592
|
+
const target = String(link.target_id ?? link.target ?? "");
|
|
16632
16593
|
if (!activityIds.has(source)) {
|
|
16633
16594
|
throw new Error(
|
|
16634
16595
|
`[parseFromBackend] link "${id}" source "${source}" does not exist`
|
|
@@ -16639,24 +16600,108 @@ function parseLink(l, activityIds, hoursPerDay) {
|
|
|
16639
16600
|
`[parseFromBackend] link "${id}" target "${target}" does not exist`
|
|
16640
16601
|
);
|
|
16641
16602
|
}
|
|
16642
|
-
const
|
|
16643
|
-
|
|
16644
|
-
|
|
16645
|
-
|
|
16646
|
-
|
|
16603
|
+
const type = resolveLinkType(link.type);
|
|
16604
|
+
if (type === null) {
|
|
16605
|
+
throw new Error(
|
|
16606
|
+
`[parseFromBackend] link "${id}" has invalid type "${String(link.type)}"`
|
|
16607
|
+
);
|
|
16608
|
+
}
|
|
16609
|
+
const ganttIdRaw = link.ganttId;
|
|
16610
|
+
const sectorIdRaw = link.sectorId;
|
|
16611
|
+
const proplannerIdRaw = link.id;
|
|
16647
16612
|
return {
|
|
16648
16613
|
id,
|
|
16649
16614
|
source,
|
|
16650
16615
|
target,
|
|
16651
16616
|
type,
|
|
16652
|
-
|
|
16653
|
-
// HOURS (mirrors `duration`). Convert here — the single backend boundary.
|
|
16654
|
-
lag: lagDaysToHours(Number(lr.lag ?? 0), hoursPerDay),
|
|
16617
|
+
lag: lagDaysToHours(Number(link.lag ?? 0), hoursPerDay),
|
|
16655
16618
|
...typeof ganttIdRaw === "number" ? { ganttId: ganttIdRaw } : {},
|
|
16656
16619
|
...typeof sectorIdRaw === "number" ? { sectorId: sectorIdRaw } : {},
|
|
16657
16620
|
...typeof proplannerIdRaw === "number" ? { proplannerId: proplannerIdRaw } : {}
|
|
16658
16621
|
};
|
|
16659
16622
|
}
|
|
16623
|
+
function dedupeLinksBySignature(links) {
|
|
16624
|
+
const bySignature = /* @__PURE__ */ new Map();
|
|
16625
|
+
for (const link of links) {
|
|
16626
|
+
const signature = `${link.source}-${link.target}-${link.type}`;
|
|
16627
|
+
const existing = bySignature.get(signature);
|
|
16628
|
+
if (!existing || linkIdAsNumber(link) > linkIdAsNumber(existing)) {
|
|
16629
|
+
bySignature.set(signature, link);
|
|
16630
|
+
}
|
|
16631
|
+
}
|
|
16632
|
+
return [...bySignature.values()];
|
|
16633
|
+
}
|
|
16634
|
+
function linkIdAsNumber(link) {
|
|
16635
|
+
const numeric = Number(link.id);
|
|
16636
|
+
return Number.isFinite(numeric) ? numeric : -Infinity;
|
|
16637
|
+
}
|
|
16638
|
+
|
|
16639
|
+
// src/boundary/backend/normalize-sector.ts
|
|
16640
|
+
function parseActivityCreter(raw) {
|
|
16641
|
+
if (typeof raw !== "string") return DEFAULT_ACTIVITY_CRETER;
|
|
16642
|
+
switch (raw.toUpperCase()) {
|
|
16643
|
+
case "DURATION":
|
|
16644
|
+
return "DURATION";
|
|
16645
|
+
case "COST":
|
|
16646
|
+
return "COST";
|
|
16647
|
+
case "HH":
|
|
16648
|
+
return "HH";
|
|
16649
|
+
default:
|
|
16650
|
+
return DEFAULT_ACTIVITY_CRETER;
|
|
16651
|
+
}
|
|
16652
|
+
}
|
|
16653
|
+
function readCustomIdPrefix(sector) {
|
|
16654
|
+
const prefix = sector.custom_id_prefix;
|
|
16655
|
+
const hasPrefix = typeof prefix === "string" && prefix.length > 0;
|
|
16656
|
+
return hasPrefix ? prefix : void 0;
|
|
16657
|
+
}
|
|
16658
|
+
function readCustomIdIncrement(sector) {
|
|
16659
|
+
const increment = sector.custom_id_increment;
|
|
16660
|
+
if (typeof increment === "string") return increment;
|
|
16661
|
+
const isFiniteNumber = typeof increment === "number" && Number.isFinite(increment);
|
|
16662
|
+
return isFiniteNumber ? String(increment) : void 0;
|
|
16663
|
+
}
|
|
16664
|
+
function parseSector(sector) {
|
|
16665
|
+
const customIdPrefix = readCustomIdPrefix(sector);
|
|
16666
|
+
const customIdIncrement = readCustomIdIncrement(sector);
|
|
16667
|
+
const isPrimaveraEndDate = sector.update_duration_for_primavera_for_end_date === true;
|
|
16668
|
+
return {
|
|
16669
|
+
id: String(sector.id),
|
|
16670
|
+
name: sector.name,
|
|
16671
|
+
hoursPerDay: sector.hoursPerDay,
|
|
16672
|
+
hoursPerWeek: sector.hoursPerWeek,
|
|
16673
|
+
dateFormat: sector.dateFormat,
|
|
16674
|
+
projectId: sector.projectId,
|
|
16675
|
+
companyId: sector.companyId,
|
|
16676
|
+
...customIdPrefix !== void 0 ? { customIdPrefix } : {},
|
|
16677
|
+
...customIdIncrement !== void 0 ? { customIdIncrement } : {},
|
|
16678
|
+
...isPrimaveraEndDate ? { updateDurationForPrimaveraEndDate: true } : {},
|
|
16679
|
+
activityCreter: parseActivityCreter(sector.activity_creter),
|
|
16680
|
+
statusCriteria: DEFAULT_STATUS_CRITERIA
|
|
16681
|
+
};
|
|
16682
|
+
}
|
|
16683
|
+
|
|
16684
|
+
// src/boundary/backend/parse-from-backend.ts
|
|
16685
|
+
function parseFromBackend(input, reporter = noopReporter) {
|
|
16686
|
+
const sector = parseSector(input.sector);
|
|
16687
|
+
const { calendars, baseCalendars, knownCalendarIds, defaultCalendarId } = normalizeCalendars(
|
|
16688
|
+
input.calendars,
|
|
16689
|
+
input.sector.calendars,
|
|
16690
|
+
input.baseCalendars
|
|
16691
|
+
);
|
|
16692
|
+
const { activities, activityIds } = normalizeActivities(input.activities, {
|
|
16693
|
+
knownCalendarIds,
|
|
16694
|
+
defaultCalendarId,
|
|
16695
|
+
hoursPerDay: sector.hoursPerDay,
|
|
16696
|
+
reporter
|
|
16697
|
+
});
|
|
16698
|
+
const links = normalizeLinks(input.links, {
|
|
16699
|
+
knownActivityIds: activityIds,
|
|
16700
|
+
hoursPerDay: sector.hoursPerDay,
|
|
16701
|
+
reporter
|
|
16702
|
+
});
|
|
16703
|
+
return { sector, activities, links, calendars, baseCalendars };
|
|
16704
|
+
}
|
|
16660
16705
|
|
|
16661
16706
|
// src/generators/activity-id-generator.ts
|
|
16662
16707
|
function createActivityIdGenerator(options = {}) {
|