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