@outbuild-company/schedule-core 1.32.0 → 1.33.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/dist/build-fingerprint.json +2 -2
- package/dist/cdn/schedule-core.global.js +1 -1
- package/dist/cdn/schedule-core.global.js.map +1 -1
- package/dist/index.cjs +2333 -795
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +308 -193
- package/dist/index.d.ts +308 -193
- package/dist/index.js +2332 -796
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -344,6 +344,7 @@ var DISPATCH_ACTION_KIND = {
|
|
|
344
344
|
CELL_CLIPBOARD_CLEAR: "cell-clipboard-clear",
|
|
345
345
|
CELL_PASTE_COPIED: "cell-paste-copied",
|
|
346
346
|
PERSISTENCE_ACKNOWLEDGE: "persistence-acknowledge",
|
|
347
|
+
PERSISTENCE_REBASE: "persistence-rebase",
|
|
347
348
|
BASELINE_APPLY: "baseline-apply",
|
|
348
349
|
PONDERATOR_CRITERION_SET: "ponderator-criterion-set",
|
|
349
350
|
STATUS_CRITERIA_SET: "status-criteria-set",
|
|
@@ -789,6 +790,10 @@ function createScheduleNamespaces(engine) {
|
|
|
789
790
|
acknowledge: (checkpoint) => engine.dispatch({
|
|
790
791
|
kind: DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE,
|
|
791
792
|
checkpoint
|
|
793
|
+
}),
|
|
794
|
+
rebase: (remote) => engine.dispatch({
|
|
795
|
+
kind: DISPATCH_ACTION_KIND.PERSISTENCE_REBASE,
|
|
796
|
+
remote
|
|
792
797
|
})
|
|
793
798
|
},
|
|
794
799
|
history: {
|
|
@@ -2445,6 +2450,7 @@ var ACTIVITY_PROPERTY = {
|
|
|
2445
2450
|
DURATION_HOURS: "durationHours",
|
|
2446
2451
|
CONSTRAINT_TYPE: "constraintType",
|
|
2447
2452
|
CONSTRAINT_DATE: "constraintDate",
|
|
2453
|
+
AUTO_SCHEDULING: "autoScheduling",
|
|
2448
2454
|
CALENDAR_ID: "calendarId",
|
|
2449
2455
|
PROGRESS: "progress",
|
|
2450
2456
|
COST: "cost",
|
|
@@ -2452,7 +2458,9 @@ var ACTIVITY_PROPERTY = {
|
|
|
2452
2458
|
REAL_COST: "realCost",
|
|
2453
2459
|
WORK_HOURS: "workHours",
|
|
2454
2460
|
REAL_WORK_HOURS: "realWorkHours",
|
|
2461
|
+
PONDERATOR: "ponderator",
|
|
2455
2462
|
CORRELATIVE_ID: "correlativeId",
|
|
2463
|
+
UNIQUE_CORRELATIVE_ID: "uniqueCorrelativeId",
|
|
2456
2464
|
CUSTOM_ID: "customId",
|
|
2457
2465
|
NEW_ACTIVITIES_ARRAY: "newActivityIds",
|
|
2458
2466
|
HAS_NEW_ACTIVITIES: "hasNewActivities",
|
|
@@ -6849,6 +6857,21 @@ function findPipelineForColumn(columnName) {
|
|
|
6849
6857
|
}
|
|
6850
6858
|
|
|
6851
6859
|
// src/dispatch/types.ts
|
|
6860
|
+
var PERSISTENCE_REBASE_CONFLICT_KIND = {
|
|
6861
|
+
FIELD: "field",
|
|
6862
|
+
DELETED_REMOTELY: "deleted-remotely",
|
|
6863
|
+
DELETED_LOCALLY: "deleted-locally",
|
|
6864
|
+
PARENT_REMOVED: "parent-removed",
|
|
6865
|
+
BECAME_PARENT_REMOTELY: "became-parent-remotely",
|
|
6866
|
+
BECAME_PARENT_LOCALLY: "became-parent-locally",
|
|
6867
|
+
DUPLICATE_LINK: "duplicate-link",
|
|
6868
|
+
ORDER: "order",
|
|
6869
|
+
LINK_CYCLE: "link-cycle"
|
|
6870
|
+
};
|
|
6871
|
+
var PERSISTENCE_REBASE_ENTITY = {
|
|
6872
|
+
ACTIVITY: "activity",
|
|
6873
|
+
LINK: "link"
|
|
6874
|
+
};
|
|
6852
6875
|
var ACTIVITY_BATCH_MODE = {
|
|
6853
6876
|
APPEND: "append",
|
|
6854
6877
|
REPLACE: "replace"
|
|
@@ -18894,284 +18917,2184 @@ function normalizeBaselinePoint(point, reporter, diagnostic2, isMilestone = fals
|
|
|
18894
18917
|
};
|
|
18895
18918
|
}
|
|
18896
18919
|
|
|
18897
|
-
// src/
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
18901
|
-
|
|
18902
|
-
|
|
18920
|
+
// src/boundary/backend/constants.ts
|
|
18921
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
18922
|
+
var EXCEPTION_DATE_FORMAT_REGEX = /^(\d{4})[/-](\d{1,2})[/-](\d{1,2})/;
|
|
18923
|
+
var EXCEPTION_TIME_FORMAT_REGEX = /^\d{4}[/-]\d{1,2}[/-]\d{1,2}[ T](\d{1,2}):(\d{2})/;
|
|
18924
|
+
var WEEKDAYS_PER_WEEK = 7;
|
|
18925
|
+
var EXCEPTION_EVERY_TYPE = {
|
|
18926
|
+
DAILY: "DAILY",
|
|
18927
|
+
WEEKLY: "WEEKLY",
|
|
18928
|
+
MONTHLY: "MONTHLY",
|
|
18929
|
+
YEARLY: "YEARLY"
|
|
18930
|
+
};
|
|
18931
|
+
var MONTHS_PER_YEAR = 12;
|
|
18932
|
+
var CALENDAR_FALLBACK_WORKING_DAYS = [false, true, true, true, true, true, false];
|
|
18933
|
+
var WORKING_DAY_TRUE_VALUES = /* @__PURE__ */ new Set([
|
|
18934
|
+
"1",
|
|
18935
|
+
"true"
|
|
18936
|
+
]);
|
|
18937
|
+
var DEFAULT_ACTIVITY_CRETER = ACTIVITY_PONDERATOR_CRITERION.DURATION;
|
|
18938
|
+
var DEFAULT_CONSTRAINT_TYPE = CONSTRAINT_TYPE.ASAP;
|
|
18939
|
+
var CONSTRAINT_LABEL_MAP = {
|
|
18940
|
+
ASAP: CONSTRAINT_TYPE.ASAP,
|
|
18941
|
+
ALAP: CONSTRAINT_TYPE.ALAP,
|
|
18942
|
+
SNET: CONSTRAINT_TYPE.SNET,
|
|
18943
|
+
SNLT: CONSTRAINT_TYPE.SNLT,
|
|
18944
|
+
FNET: CONSTRAINT_TYPE.FNET,
|
|
18945
|
+
FNLT: CONSTRAINT_TYPE.FNLT,
|
|
18946
|
+
MSO: CONSTRAINT_TYPE.MSO,
|
|
18947
|
+
MFO: CONSTRAINT_TYPE.MFO,
|
|
18948
|
+
"AS SOON AS POSSIBLE": CONSTRAINT_TYPE.ASAP,
|
|
18949
|
+
"AS LATE AS POSSIBLE": CONSTRAINT_TYPE.ALAP,
|
|
18950
|
+
"START NO EARLIER THAN": CONSTRAINT_TYPE.SNET,
|
|
18951
|
+
"START NO LATER THAN": CONSTRAINT_TYPE.SNLT,
|
|
18952
|
+
"FINISH NO EARLIER THAN": CONSTRAINT_TYPE.FNET,
|
|
18953
|
+
"FINISH NO LATER THAN": CONSTRAINT_TYPE.FNLT,
|
|
18954
|
+
"MUST START ON": CONSTRAINT_TYPE.MSO,
|
|
18955
|
+
"MUST FINISH ON": CONSTRAINT_TYPE.MFO
|
|
18956
|
+
};
|
|
18957
|
+
|
|
18958
|
+
// src/boundary/backend/normalize-activity.ts
|
|
18959
|
+
function normalizeActivities(rawActivities, context) {
|
|
18960
|
+
const hiddenIds = [];
|
|
18961
|
+
const activities = rawActivities.map((raw) => {
|
|
18962
|
+
const activity = parseActivity(raw, context);
|
|
18963
|
+
if (raw.should_be_showed === false) hiddenIds.push(activity.id);
|
|
18964
|
+
return activity;
|
|
18903
18965
|
});
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
18914
|
-
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
}
|
|
18922
|
-
const arr = grouped.get(coreId);
|
|
18923
|
-
if (arr) arr.push(p);
|
|
18924
|
-
else grouped.set(coreId, [p]);
|
|
18925
|
-
}
|
|
18926
|
-
const changed2 = [];
|
|
18927
|
-
for (const [coreId, group] of grouped) {
|
|
18928
|
-
const isMilestone = adapter.getActivity(coreId)?.type === ACTIVITY_TYPE.MILESTONE;
|
|
18929
|
-
adapter.setActivityField(
|
|
18930
|
-
coreId,
|
|
18931
|
-
"baselinePoints",
|
|
18932
|
-
group.map(
|
|
18933
|
-
(point) => normalizeBaselinePoint(
|
|
18934
|
-
point,
|
|
18935
|
-
reporter,
|
|
18936
|
-
{
|
|
18937
|
-
record: recordDiagnostic,
|
|
18938
|
-
sampleId: coreId
|
|
18939
|
-
},
|
|
18940
|
-
isMilestone
|
|
18941
|
-
)
|
|
18942
|
-
).filter((point) => point !== null)
|
|
18966
|
+
forceRootProjectToRoot(activities);
|
|
18967
|
+
const activityIds = new Set(activities.map((activity) => activity.id));
|
|
18968
|
+
reparentOrphansToRoot(
|
|
18969
|
+
activities,
|
|
18970
|
+
activityIds,
|
|
18971
|
+
context.reporter,
|
|
18972
|
+
context.recordDiagnostic
|
|
18973
|
+
);
|
|
18974
|
+
return { activities, activityIds, hiddenIds };
|
|
18975
|
+
}
|
|
18976
|
+
function parseConstraintLabel(label, reporter, recordDiagnostic, activityId) {
|
|
18977
|
+
if (!label) return null;
|
|
18978
|
+
const normalized2 = CONSTRAINT_LABEL_MAP[label.toUpperCase()];
|
|
18979
|
+
if (normalized2 === void 0) {
|
|
18980
|
+
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.UNKNOWN_CONSTRAINT, activityId);
|
|
18981
|
+
reporter.warn(
|
|
18982
|
+
`[parseFromBackend] unknown constraint label "${label}" \u2014 defaulting to "asap"`
|
|
18943
18983
|
);
|
|
18944
|
-
|
|
18984
|
+
return DEFAULT_CONSTRAINT_TYPE;
|
|
18945
18985
|
}
|
|
18946
|
-
|
|
18947
|
-
if (grouped.has(id)) return;
|
|
18948
|
-
const existing = a.baselinePoints;
|
|
18949
|
-
if (Array.isArray(existing) && existing.length > 0) {
|
|
18950
|
-
adapter.setActivityField(id, "baselinePoints", []);
|
|
18951
|
-
changed2.push(id);
|
|
18952
|
-
}
|
|
18953
|
-
});
|
|
18954
|
-
return changed2;
|
|
18986
|
+
return normalized2;
|
|
18955
18987
|
}
|
|
18956
|
-
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
workHours: active.workHours
|
|
18967
|
-
};
|
|
18988
|
+
function resolveConstraint(raw, reporter, recordDiagnostic) {
|
|
18989
|
+
const constraintType = parseConstraintLabel(
|
|
18990
|
+
raw.constraint,
|
|
18991
|
+
reporter,
|
|
18992
|
+
recordDiagnostic,
|
|
18993
|
+
String(raw.unique_id)
|
|
18994
|
+
);
|
|
18995
|
+
const constraintTakesDate = constraintType !== CONSTRAINT_TYPE.ASAP && constraintType !== CONSTRAINT_TYPE.ALAP;
|
|
18996
|
+
const constraintDate = constraintTakesDate && raw.constraint_date ? parseBackendDate(raw.constraint_date) : null;
|
|
18997
|
+
return { constraintType, constraintDate };
|
|
18968
18998
|
}
|
|
18969
|
-
function
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
if (!snapshot) {
|
|
18974
|
-
if (activity.baselineSnapshot !== null) {
|
|
18975
|
-
state.setActivityField(id, "baselineSnapshot", null);
|
|
18976
|
-
changed2.push(id);
|
|
18977
|
-
}
|
|
18978
|
-
return;
|
|
18979
|
-
}
|
|
18980
|
-
state.setActivityField(id, "baselineSnapshot", snapshot);
|
|
18981
|
-
changed2.push(id);
|
|
18982
|
-
});
|
|
18983
|
-
return changed2;
|
|
18999
|
+
function resolveCalendarId(rawCalendarId, knownCalendarIds, defaultCalendarId) {
|
|
19000
|
+
if (rawCalendarId == null) return defaultCalendarId;
|
|
19001
|
+
const coerced = String(rawCalendarId);
|
|
19002
|
+
return knownCalendarIds.has(coerced) ? coerced : defaultCalendarId;
|
|
18984
19003
|
}
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
)) {
|
|
18995
|
-
changed2.add(id);
|
|
19004
|
+
function parseNewActivityIds(raw) {
|
|
19005
|
+
if (raw == null || raw === "") return [];
|
|
19006
|
+
if (Array.isArray(raw)) return raw.map(String);
|
|
19007
|
+
if (typeof raw !== "string") return [];
|
|
19008
|
+
try {
|
|
19009
|
+
const parsed2 = JSON.parse(raw);
|
|
19010
|
+
return Array.isArray(parsed2) ? parsed2.map(String) : [];
|
|
19011
|
+
} catch {
|
|
19012
|
+
return [];
|
|
18996
19013
|
}
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19014
|
+
}
|
|
19015
|
+
function hasUsableId(entry) {
|
|
19016
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
19017
|
+
if (!("id" in entry)) return false;
|
|
19018
|
+
const value = entry.id;
|
|
19019
|
+
return typeof value === "string" || typeof value === "number";
|
|
19020
|
+
}
|
|
19021
|
+
function parseIdList(raw) {
|
|
19022
|
+
if (!Array.isArray(raw)) return [];
|
|
19023
|
+
const ids = [];
|
|
19024
|
+
for (const entry of raw) {
|
|
19025
|
+
if (hasUsableId(entry)) ids.push(String(entry.id));
|
|
19000
19026
|
}
|
|
19001
|
-
|
|
19002
|
-
for (const id of emitRealCost(state)) changed2.add(id);
|
|
19003
|
-
return [...changed2];
|
|
19027
|
+
return ids;
|
|
19004
19028
|
}
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
function recomputeAllPonderators(criterion, adapter) {
|
|
19008
|
-
const basisById = /* @__PURE__ */ new Map();
|
|
19009
|
-
adapter.forEachActivity((_a, id) => {
|
|
19010
|
-
adapter.setActivityField(id, COLUMN.PONDERATOR, 0);
|
|
19011
|
-
});
|
|
19012
|
-
adapter.forEachActivity((_a, id) => {
|
|
19013
|
-
if (adapter.getChildren(id).length > 0) {
|
|
19014
|
-
recomputePonderatorsForParent(String(id), criterion, adapter, basisById);
|
|
19015
|
-
}
|
|
19016
|
-
});
|
|
19029
|
+
function parseParentId(rawParentId) {
|
|
19030
|
+
return rawParentId === ROOT_PARENT_ID ? null : String(rawParentId);
|
|
19017
19031
|
}
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
if (deps.now) {
|
|
19024
|
-
runExpectedProgressBase(
|
|
19025
|
-
deps.adapter,
|
|
19026
|
-
deps.now,
|
|
19027
|
-
deps.defaultBaseCalendarId ?? null
|
|
19028
|
-
);
|
|
19029
|
-
applyExpectedProgressLive(deps.adapter, deps.now);
|
|
19032
|
+
function normalizePersistedActivityId(rawId) {
|
|
19033
|
+
if (rawId == null) return null;
|
|
19034
|
+
const isValidIdentity = typeof rawId === "number" && (rawId === 0 || Number.isSafeInteger(rawId) && rawId > 0);
|
|
19035
|
+
if (!isValidIdentity) {
|
|
19036
|
+
throw new Error(`invalid activity proplannerId ${String(rawId)}`);
|
|
19030
19037
|
}
|
|
19031
|
-
|
|
19032
|
-
if (deps.now) applyStatusPass(deps.adapter, deps.sector.statusCriteria);
|
|
19038
|
+
return rawId;
|
|
19033
19039
|
}
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
point.workHours,
|
|
19048
|
-
point.baseCalendarId,
|
|
19049
|
-
point.hoursPerDay,
|
|
19050
|
-
point.hoursPerWeek
|
|
19051
|
-
] : [String(id), null];
|
|
19052
|
-
})
|
|
19040
|
+
function parseProgress(raw, reporter, recordDiagnostic, activityId) {
|
|
19041
|
+
const progress = Number(raw);
|
|
19042
|
+
if (!Number.isFinite(progress)) {
|
|
19043
|
+
throw new TypeError(`invalid activity progress ${String(raw)}`);
|
|
19044
|
+
}
|
|
19045
|
+
const isBelowNotStarted = progress < NOT_STARTED_PROGRESS;
|
|
19046
|
+
const isAboveCompleted = progress > MAX_TOLERATED_PROGRESS;
|
|
19047
|
+
const isWithinRange = !isBelowNotStarted && !isAboveCompleted;
|
|
19048
|
+
if (isWithinRange) return progress;
|
|
19049
|
+
const clamped = isBelowNotStarted ? NOT_STARTED_PROGRESS : COMPLETED_PROGRESS;
|
|
19050
|
+
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE, activityId);
|
|
19051
|
+
reporter.warn(
|
|
19052
|
+
`[parseFromBackend] activity "${activityId}" progress ${String(raw)} is out of range \u2014 clamping to ${clamped}`
|
|
19053
19053
|
);
|
|
19054
|
+
return clamped;
|
|
19054
19055
|
}
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19056
|
+
function parseCriticality(raw, reporter) {
|
|
19057
|
+
if (typeof raw.is_critical === "boolean") return raw.is_critical;
|
|
19058
|
+
reporter.error(
|
|
19059
|
+
"[parseFromBackend] invalid activity is_critical \u2014 defaulting to false",
|
|
19060
|
+
new TypeError(
|
|
19061
|
+
`activity "${raw.unique_id}" received ${typeof raw.is_critical}`
|
|
19062
|
+
)
|
|
19059
19063
|
);
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19064
|
+
return false;
|
|
19065
|
+
}
|
|
19066
|
+
function parseBaselinePoints(raw, reporter, recordDiagnostic, activityId, isMilestone) {
|
|
19067
|
+
if (!Array.isArray(raw)) return [];
|
|
19068
|
+
return raw.map(
|
|
19069
|
+
(point) => normalizeBaselinePoint(
|
|
19070
|
+
point,
|
|
19071
|
+
reporter,
|
|
19072
|
+
{
|
|
19073
|
+
record: recordDiagnostic,
|
|
19074
|
+
sampleId: activityId
|
|
19075
|
+
},
|
|
19076
|
+
isMilestone
|
|
19077
|
+
)
|
|
19078
|
+
).filter((point) => point !== null);
|
|
19079
|
+
}
|
|
19080
|
+
function parseActivity(raw, context) {
|
|
19081
|
+
const calendarId = resolveCalendarId(
|
|
19082
|
+
raw.calendarId,
|
|
19083
|
+
context.knownCalendarIds,
|
|
19084
|
+
context.defaultCalendarId
|
|
19068
19085
|
);
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
}
|
|
19086
|
+
const inheritedDefaultCalendar = raw.calendarId == null || !context.knownCalendarIds.has(String(raw.calendarId));
|
|
19087
|
+
const progress = parseProgress(
|
|
19088
|
+
raw.progress,
|
|
19089
|
+
context.reporter,
|
|
19090
|
+
context.recordDiagnostic,
|
|
19091
|
+
String(raw.unique_id)
|
|
19092
|
+
);
|
|
19093
|
+
const { constraintType, constraintDate } = resolveConstraint(
|
|
19094
|
+
raw,
|
|
19095
|
+
context.reporter,
|
|
19096
|
+
context.recordDiagnostic
|
|
19097
|
+
);
|
|
19098
|
+
const isMilestone = raw.type === ACTIVITY_TYPE.MILESTONE;
|
|
19099
|
+
const startDate = parseBackendDate(raw.start_date);
|
|
19100
|
+
const endDate = isMilestone ? startDate : parseBackendDate(raw.end_date);
|
|
19101
|
+
const durationDays = isMilestone ? 0 : Number(raw.duration);
|
|
19102
|
+
const isCritical = parseCriticality(raw, context.reporter);
|
|
19077
19103
|
return {
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19104
|
+
id: String(raw.unique_id),
|
|
19105
|
+
proplannerId: normalizePersistedActivityId(raw.id),
|
|
19106
|
+
parentId: parseParentId(raw.parent_id),
|
|
19107
|
+
name: raw.name,
|
|
19108
|
+
description: raw.description ?? "",
|
|
19109
|
+
type: raw.type,
|
|
19110
|
+
startDate,
|
|
19111
|
+
endDate,
|
|
19112
|
+
durationHours: durationToHours(durationDays, context.hoursPerDay),
|
|
19113
|
+
constraintType,
|
|
19114
|
+
constraintDate,
|
|
19115
|
+
autoScheduling: isAutoSchedulingEnabledForProgress(progress),
|
|
19116
|
+
calendarId,
|
|
19117
|
+
assignedDefaultCalendar: calendarId != null && inheritedDefaultCalendar,
|
|
19118
|
+
dateOrigin: raw.date_origin == null ? null : parseBackendDate(raw.date_origin),
|
|
19119
|
+
progress,
|
|
19120
|
+
cost: Number(raw.cost),
|
|
19121
|
+
usedCost: Number(raw.used_cost ?? 0),
|
|
19122
|
+
realCost: Number(raw.real_cost ?? 0),
|
|
19123
|
+
workHours: Number(raw.hhWorkTime),
|
|
19124
|
+
realWorkHours: Number(raw.real_work ?? 0),
|
|
19125
|
+
ponderator: Number(raw.ponderator ?? 0),
|
|
19126
|
+
hasCustomPonderator: raw.hasCustomPonderator,
|
|
19127
|
+
correlativeId: raw.correlative_id,
|
|
19128
|
+
uniqueCorrelativeId: raw.unique_correlative_id,
|
|
19129
|
+
customId: normalizeCustomId(raw),
|
|
19130
|
+
subcontractId: raw.subcontractId,
|
|
19131
|
+
isLookahead: raw.isOnLookahead,
|
|
19132
|
+
hasLookaheadTasks: Array.isArray(raw.tasks) && raw.tasks.length > 0,
|
|
19133
|
+
newActivityIds: parseNewActivityIds(raw.newActivitiesArray),
|
|
19134
|
+
hasNewActivities: raw.hasNewActivities ?? false,
|
|
19135
|
+
...raw.isNewActivity === true ? { isNewActivity: true } : {},
|
|
19136
|
+
pendingRequestIds: parseIdList(raw.pendingRequests),
|
|
19137
|
+
responsableIds: parseIdList(raw.responsables),
|
|
19138
|
+
tagIds: parseIdList(raw.tags),
|
|
19139
|
+
baselinePoints: parseBaselinePoints(
|
|
19140
|
+
raw.baseline_points,
|
|
19141
|
+
context.reporter,
|
|
19142
|
+
context.recordDiagnostic,
|
|
19143
|
+
String(raw.unique_id),
|
|
19144
|
+
isMilestone
|
|
19145
|
+
),
|
|
19146
|
+
baselineSnapshot: null,
|
|
19147
|
+
baselineVarianceDays: null,
|
|
19148
|
+
projectBaselineVarianceDays: null,
|
|
19149
|
+
primaryBaselineVarianceDays: null,
|
|
19150
|
+
recursiveDurationHours: durationToHours(
|
|
19151
|
+
Number(raw.sumOfDurationRecursively ?? 0),
|
|
19152
|
+
context.hoursPerDay
|
|
19153
|
+
),
|
|
19154
|
+
calendarDuration: computeCalendarDuration({
|
|
19155
|
+
type: raw.type,
|
|
19156
|
+
startDate,
|
|
19157
|
+
endDate
|
|
19158
|
+
}),
|
|
19159
|
+
isCritical,
|
|
19160
|
+
freeSlackHours: null,
|
|
19161
|
+
expectedProgress: null,
|
|
19162
|
+
expectedProgressBaseline: null,
|
|
19163
|
+
status: null,
|
|
19164
|
+
criticalPath: null
|
|
19086
19165
|
};
|
|
19087
19166
|
}
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
19091
|
-
|
|
19092
|
-
|
|
19167
|
+
function normalizeCustomId(raw) {
|
|
19168
|
+
return raw.has_childs ? null : raw.custom_id;
|
|
19169
|
+
}
|
|
19170
|
+
function forceRootProjectToRoot(activities) {
|
|
19171
|
+
const rootProject = activities.find(
|
|
19172
|
+
(activity) => activity.correlativeId === 0 && activity.type === ACTIVITY_TYPE.PROJECT
|
|
19093
19173
|
);
|
|
19174
|
+
if (rootProject) rootProject.parentId = null;
|
|
19094
19175
|
}
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
|
|
19176
|
+
function reparentOrphansToRoot(activities, ids, reporter, recordDiagnostic) {
|
|
19177
|
+
for (const activity of activities) {
|
|
19178
|
+
if (activity.parentId === null) continue;
|
|
19179
|
+
if (!ids.has(activity.parentId)) {
|
|
19180
|
+
reporter.warn(
|
|
19181
|
+
`[parseFromBackend] activity "${activity.id}" references unknown parent "${activity.parentId}" \u2014 re-parenting to root (null)`
|
|
19182
|
+
);
|
|
19183
|
+
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.ORPHAN_ACTIVITY, activity.id);
|
|
19184
|
+
activity.parentId = null;
|
|
19185
|
+
}
|
|
19098
19186
|
}
|
|
19099
|
-
const allIds = new Set(deps.adapter.getAllIds());
|
|
19100
|
-
const beforeSnap = await snapshotActivities(deps.adapter, allIds);
|
|
19101
|
-
recomputeBaselineWeightedFields(deps, action.criterion);
|
|
19102
|
-
deps.sector.activityCreter = action.criterion;
|
|
19103
|
-
return {
|
|
19104
|
-
ok: true,
|
|
19105
|
-
changes: await assembleChangeSet(deps.adapter, {
|
|
19106
|
-
source: action,
|
|
19107
|
-
beforeSnap,
|
|
19108
|
-
touchedIds: allIds,
|
|
19109
|
-
scheduledIds: [],
|
|
19110
|
-
hoursPerDay: deps.sector.hoursPerDay
|
|
19111
|
-
})
|
|
19112
|
-
};
|
|
19113
19187
|
}
|
|
19114
19188
|
|
|
19115
|
-
// src/
|
|
19116
|
-
|
|
19117
|
-
|
|
19118
|
-
const
|
|
19119
|
-
|
|
19120
|
-
|
|
19121
|
-
|
|
19122
|
-
return {
|
|
19123
|
-
ok: true,
|
|
19124
|
-
changes: await assembleChangeSet(deps.adapter, {
|
|
19125
|
-
source: action,
|
|
19126
|
-
beforeSnap,
|
|
19127
|
-
touchedIds: changedIds,
|
|
19128
|
-
scheduledIds: [],
|
|
19129
|
-
hoursPerDay: deps.sector.hoursPerDay
|
|
19130
|
-
})
|
|
19131
|
-
};
|
|
19189
|
+
// src/boundary/backend/normalize-link.ts
|
|
19190
|
+
function resolveLinkType(rawType) {
|
|
19191
|
+
if (typeof rawType !== "string") return null;
|
|
19192
|
+
const fromLabel = parseLinkTypeCode(rawType);
|
|
19193
|
+
if (fromLabel) return fromLabel;
|
|
19194
|
+
if (isLinkType(rawType)) return rawType;
|
|
19195
|
+
return null;
|
|
19132
19196
|
}
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
|
|
19136
|
-
|
|
19137
|
-
|
|
19138
|
-
for (const activity of deps.adapter.getAllActivities()) {
|
|
19139
|
-
if (!shouldReconcile(activity, deps)) continue;
|
|
19140
|
-
const calendar = deps.ctx.calendars.getCalendar(activity.calendarId);
|
|
19141
|
-
if (!calendar) continue;
|
|
19142
|
-
const startReference = calendar.isWorkTime(
|
|
19143
|
-
activity.startDate,
|
|
19144
|
-
CALENDAR_UNIT.HOUR
|
|
19145
|
-
) ? startOfUtcDay4(activity.startDate) : activity.startDate;
|
|
19146
|
-
const startDate = calendar.getClosestWorkTime({
|
|
19147
|
-
date: startReference,
|
|
19148
|
-
direction: WORK_TIME_DIRECTION.FUTURE,
|
|
19149
|
-
unit: CALENDAR_UNIT.HOUR
|
|
19150
|
-
});
|
|
19151
|
-
const endDate = calendar.calculateEndDate(
|
|
19152
|
-
startDate,
|
|
19153
|
-
activity.durationHours
|
|
19154
|
-
);
|
|
19155
|
-
if (sameInstant(startDate, activity.startDate) && sameInstant(endDate, activity.endDate)) {
|
|
19156
|
-
continue;
|
|
19157
|
-
}
|
|
19158
|
-
const activityId = String(activity.id);
|
|
19159
|
-
beforeSnap.set(activityId, structuredCloneActivity(activity));
|
|
19160
|
-
touchedIds.add(activityId);
|
|
19161
|
-
deps.adapter.setActivityFields(activityId, { startDate, endDate });
|
|
19197
|
+
function normalizePersistedLinkId(rawId) {
|
|
19198
|
+
if (rawId == null) return void 0;
|
|
19199
|
+
const isValidIdentity = typeof rawId === "number" && (rawId === 0 || Number.isSafeInteger(rawId) && rawId > 0);
|
|
19200
|
+
if (!isValidIdentity) {
|
|
19201
|
+
throw new Error(`invalid link proplannerId ${String(rawId)}`);
|
|
19162
19202
|
}
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19169
|
-
|
|
19170
|
-
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
|
|
19174
|
-
|
|
19203
|
+
return rawId;
|
|
19204
|
+
}
|
|
19205
|
+
function normalizeLinks(rawLinks, context) {
|
|
19206
|
+
const parsedLinks = [];
|
|
19207
|
+
for (const rawLink of rawLinks) {
|
|
19208
|
+
try {
|
|
19209
|
+
parsedLinks.push(
|
|
19210
|
+
parseLink(rawLink, context.knownActivityIds, context.hoursPerDay)
|
|
19211
|
+
);
|
|
19212
|
+
} catch (parseError2) {
|
|
19213
|
+
context.recordDiagnostic(
|
|
19214
|
+
LOAD_DIAGNOSTIC_CODE.INVALID_LINK,
|
|
19215
|
+
String(rawLink.unique_id ?? rawLink.id)
|
|
19216
|
+
);
|
|
19217
|
+
context.reporter.warn(
|
|
19218
|
+
"[parseFromBackend] dropping invalid link",
|
|
19219
|
+
parseError2
|
|
19220
|
+
);
|
|
19221
|
+
}
|
|
19222
|
+
}
|
|
19223
|
+
return dedupeLinksBySignature(parsedLinks, context.recordDiagnostic);
|
|
19224
|
+
}
|
|
19225
|
+
function parseLink(link, activityIds, hoursPerDay) {
|
|
19226
|
+
const id = String(link.unique_id ?? link.id);
|
|
19227
|
+
const source = String(link.source_id ?? link.source ?? "");
|
|
19228
|
+
const target = String(link.target_id ?? link.target ?? "");
|
|
19229
|
+
if (!activityIds.has(source)) {
|
|
19230
|
+
throw new Error(
|
|
19231
|
+
`[parseFromBackend] link "${id}" source "${source}" does not exist`
|
|
19232
|
+
);
|
|
19233
|
+
}
|
|
19234
|
+
if (!activityIds.has(target)) {
|
|
19235
|
+
throw new Error(
|
|
19236
|
+
`[parseFromBackend] link "${id}" target "${target}" does not exist`
|
|
19237
|
+
);
|
|
19238
|
+
}
|
|
19239
|
+
const type = resolveLinkType(link.type);
|
|
19240
|
+
if (type === null) {
|
|
19241
|
+
throw new Error(
|
|
19242
|
+
`[parseFromBackend] link "${id}" has invalid type "${String(link.type)}"`
|
|
19243
|
+
);
|
|
19244
|
+
}
|
|
19245
|
+
const ganttIdRaw = link.ganttId;
|
|
19246
|
+
const sectorIdRaw = link.sectorId;
|
|
19247
|
+
const proplannerId = normalizePersistedLinkId(link.id);
|
|
19248
|
+
return {
|
|
19249
|
+
id,
|
|
19250
|
+
source,
|
|
19251
|
+
target,
|
|
19252
|
+
type,
|
|
19253
|
+
lag: lagDaysToHours(Number(link.lag ?? 0), hoursPerDay),
|
|
19254
|
+
freeSlackHours: null,
|
|
19255
|
+
isCritical: false,
|
|
19256
|
+
...typeof ganttIdRaw === "number" ? { ganttId: ganttIdRaw } : {},
|
|
19257
|
+
...typeof sectorIdRaw === "number" ? { sectorId: sectorIdRaw } : {},
|
|
19258
|
+
...proplannerId === void 0 ? {} : { proplannerId }
|
|
19259
|
+
};
|
|
19260
|
+
}
|
|
19261
|
+
function dedupeLinksBySignature(links, recordDiagnostic) {
|
|
19262
|
+
const bySignature = /* @__PURE__ */ new Map();
|
|
19263
|
+
for (const link of links) {
|
|
19264
|
+
const signature = `${link.source}-${link.target}-${link.type}`;
|
|
19265
|
+
const existing = bySignature.get(signature);
|
|
19266
|
+
if (!existing) {
|
|
19267
|
+
bySignature.set(signature, link);
|
|
19268
|
+
continue;
|
|
19269
|
+
}
|
|
19270
|
+
const incomingWins = linkIdAsNumber(link) > linkIdAsNumber(existing);
|
|
19271
|
+
const discarded = incomingWins ? existing : link;
|
|
19272
|
+
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.DUPLICATE_LINK, discarded.id);
|
|
19273
|
+
if (incomingWins) {
|
|
19274
|
+
bySignature.set(signature, link);
|
|
19275
|
+
}
|
|
19276
|
+
}
|
|
19277
|
+
return [...bySignature.values()];
|
|
19278
|
+
}
|
|
19279
|
+
function linkIdAsNumber(link) {
|
|
19280
|
+
const numeric = Number(link.id);
|
|
19281
|
+
return Number.isFinite(numeric) ? numeric : -Infinity;
|
|
19282
|
+
}
|
|
19283
|
+
|
|
19284
|
+
// src/boundary/save/field-policy.ts
|
|
19285
|
+
var SAVE_FIELD_POLICY = {
|
|
19286
|
+
TRACKED: "tracked",
|
|
19287
|
+
IGNORED: "ignored"
|
|
19288
|
+
};
|
|
19289
|
+
|
|
19290
|
+
// src/boundary/save/activity-changes.ts
|
|
19291
|
+
var ACTIVITY_SAVE_FIELD_POLICY = {
|
|
19292
|
+
id: SAVE_FIELD_POLICY.IGNORED,
|
|
19293
|
+
proplannerId: SAVE_FIELD_POLICY.IGNORED,
|
|
19294
|
+
parentId: SAVE_FIELD_POLICY.TRACKED,
|
|
19295
|
+
name: SAVE_FIELD_POLICY.TRACKED,
|
|
19296
|
+
description: SAVE_FIELD_POLICY.TRACKED,
|
|
19297
|
+
type: SAVE_FIELD_POLICY.TRACKED,
|
|
19298
|
+
startDate: SAVE_FIELD_POLICY.TRACKED,
|
|
19299
|
+
endDate: SAVE_FIELD_POLICY.TRACKED,
|
|
19300
|
+
durationHours: SAVE_FIELD_POLICY.TRACKED,
|
|
19301
|
+
constraintType: SAVE_FIELD_POLICY.TRACKED,
|
|
19302
|
+
constraintDate: SAVE_FIELD_POLICY.TRACKED,
|
|
19303
|
+
autoScheduling: SAVE_FIELD_POLICY.TRACKED,
|
|
19304
|
+
calendarId: SAVE_FIELD_POLICY.TRACKED,
|
|
19305
|
+
assignedDefaultCalendar: SAVE_FIELD_POLICY.IGNORED,
|
|
19306
|
+
dateOrigin: SAVE_FIELD_POLICY.TRACKED,
|
|
19307
|
+
progress: SAVE_FIELD_POLICY.TRACKED,
|
|
19308
|
+
cost: SAVE_FIELD_POLICY.TRACKED,
|
|
19309
|
+
usedCost: SAVE_FIELD_POLICY.TRACKED,
|
|
19310
|
+
realCost: SAVE_FIELD_POLICY.TRACKED,
|
|
19311
|
+
workHours: SAVE_FIELD_POLICY.TRACKED,
|
|
19312
|
+
realWorkHours: SAVE_FIELD_POLICY.TRACKED,
|
|
19313
|
+
ponderator: SAVE_FIELD_POLICY.TRACKED,
|
|
19314
|
+
hasCustomPonderator: SAVE_FIELD_POLICY.TRACKED,
|
|
19315
|
+
correlativeId: SAVE_FIELD_POLICY.TRACKED,
|
|
19316
|
+
uniqueCorrelativeId: SAVE_FIELD_POLICY.TRACKED,
|
|
19317
|
+
customId: SAVE_FIELD_POLICY.TRACKED,
|
|
19318
|
+
subcontractId: SAVE_FIELD_POLICY.TRACKED,
|
|
19319
|
+
isLookahead: SAVE_FIELD_POLICY.TRACKED,
|
|
19320
|
+
hasLookaheadTasks: SAVE_FIELD_POLICY.IGNORED,
|
|
19321
|
+
newActivityIds: SAVE_FIELD_POLICY.TRACKED,
|
|
19322
|
+
hasNewActivities: SAVE_FIELD_POLICY.TRACKED,
|
|
19323
|
+
isNewActivity: SAVE_FIELD_POLICY.IGNORED,
|
|
19324
|
+
pendingRequestIds: SAVE_FIELD_POLICY.IGNORED,
|
|
19325
|
+
responsableIds: SAVE_FIELD_POLICY.TRACKED,
|
|
19326
|
+
tagIds: SAVE_FIELD_POLICY.TRACKED,
|
|
19327
|
+
baselinePoints: SAVE_FIELD_POLICY.IGNORED,
|
|
19328
|
+
baselineSnapshot: SAVE_FIELD_POLICY.IGNORED,
|
|
19329
|
+
recursiveDurationHours: SAVE_FIELD_POLICY.IGNORED,
|
|
19330
|
+
isCritical: SAVE_FIELD_POLICY.IGNORED,
|
|
19331
|
+
freeSlackHours: SAVE_FIELD_POLICY.IGNORED,
|
|
19332
|
+
calendarDuration: SAVE_FIELD_POLICY.IGNORED,
|
|
19333
|
+
baselineVarianceDays: SAVE_FIELD_POLICY.IGNORED,
|
|
19334
|
+
projectBaselineVarianceDays: SAVE_FIELD_POLICY.IGNORED,
|
|
19335
|
+
primaryBaselineVarianceDays: SAVE_FIELD_POLICY.IGNORED,
|
|
19336
|
+
expectedProgress: SAVE_FIELD_POLICY.IGNORED,
|
|
19337
|
+
expectedProgressBaseline: SAVE_FIELD_POLICY.IGNORED,
|
|
19338
|
+
status: SAVE_FIELD_POLICY.IGNORED,
|
|
19339
|
+
criticalPath: SAVE_FIELD_POLICY.IGNORED,
|
|
19340
|
+
p6: SAVE_FIELD_POLICY.IGNORED
|
|
19341
|
+
};
|
|
19342
|
+
var PERSISTABLE_ACTIVITY_FIELDS = Object.keys(ACTIVITY_SAVE_FIELD_POLICY).filter((field) => {
|
|
19343
|
+
return ACTIVITY_SAVE_FIELD_POLICY[field] === SAVE_FIELD_POLICY.TRACKED;
|
|
19344
|
+
});
|
|
19345
|
+
var DATE_FIELDS = /* @__PURE__ */ new Set([
|
|
19346
|
+
"startDate",
|
|
19347
|
+
"endDate",
|
|
19348
|
+
"constraintDate",
|
|
19349
|
+
"dateOrigin"
|
|
19350
|
+
]);
|
|
19351
|
+
var ID_ARRAY_FIELDS = /* @__PURE__ */ new Set([
|
|
19352
|
+
"newActivityIds",
|
|
19353
|
+
"responsableIds",
|
|
19354
|
+
"tagIds"
|
|
19355
|
+
]);
|
|
19356
|
+
function projectPersistableActivity(activity) {
|
|
19357
|
+
const entries = PERSISTABLE_ACTIVITY_FIELDS.map(
|
|
19358
|
+
(field) => [field, snapshotFieldValue(field, activity[field])]
|
|
19359
|
+
);
|
|
19360
|
+
return Object.fromEntries(entries);
|
|
19361
|
+
}
|
|
19362
|
+
function snapshotFieldValue(field, value) {
|
|
19363
|
+
if (DATE_FIELDS.has(field)) {
|
|
19364
|
+
return value instanceof Date ? new Date(value) : null;
|
|
19365
|
+
}
|
|
19366
|
+
if (ID_ARRAY_FIELDS.has(field)) {
|
|
19367
|
+
return Array.isArray(value) ? [...value] : [];
|
|
19368
|
+
}
|
|
19369
|
+
return value;
|
|
19370
|
+
}
|
|
19371
|
+
function activityChangedSince(current, saved) {
|
|
19372
|
+
return changedPersistableActivityFields(current, saved).length > 0;
|
|
19373
|
+
}
|
|
19374
|
+
function changedPersistableActivityFields(current, saved) {
|
|
19375
|
+
return PERSISTABLE_ACTIVITY_FIELDS.filter(
|
|
19376
|
+
(field) => !fieldEquals(field, current[field], saved[field])
|
|
19377
|
+
);
|
|
19378
|
+
}
|
|
19379
|
+
function fieldEquals(field, currentValue, savedValue) {
|
|
19380
|
+
if (DATE_FIELDS.has(field)) return dateEquals(currentValue, savedValue);
|
|
19381
|
+
if (ID_ARRAY_FIELDS.has(field))
|
|
19382
|
+
return idArrayEquals(currentValue, savedValue);
|
|
19383
|
+
return (currentValue ?? null) === (savedValue ?? null);
|
|
19384
|
+
}
|
|
19385
|
+
function dateEquals(current, saved) {
|
|
19386
|
+
const currentTime = current instanceof Date ? current.getTime() : null;
|
|
19387
|
+
const savedTime = saved instanceof Date ? saved.getTime() : null;
|
|
19388
|
+
return currentTime === savedTime;
|
|
19389
|
+
}
|
|
19390
|
+
function idArrayEquals(current, saved) {
|
|
19391
|
+
const currentIds = new Set(Array.isArray(current) ? current.map(String) : []);
|
|
19392
|
+
const savedIds = new Set(Array.isArray(saved) ? saved.map(String) : []);
|
|
19393
|
+
if (currentIds.size !== savedIds.size) return false;
|
|
19394
|
+
for (const currentId of currentIds) {
|
|
19395
|
+
if (!savedIds.has(currentId)) return false;
|
|
19396
|
+
}
|
|
19397
|
+
return true;
|
|
19398
|
+
}
|
|
19399
|
+
|
|
19400
|
+
// src/boundary/save/link-changes.ts
|
|
19401
|
+
var LINK_SAVE_FIELD_POLICY = {
|
|
19402
|
+
id: SAVE_FIELD_POLICY.IGNORED,
|
|
19403
|
+
source: SAVE_FIELD_POLICY.TRACKED,
|
|
19404
|
+
target: SAVE_FIELD_POLICY.TRACKED,
|
|
19405
|
+
type: SAVE_FIELD_POLICY.TRACKED,
|
|
19406
|
+
lag: SAVE_FIELD_POLICY.TRACKED,
|
|
19407
|
+
freeSlackHours: SAVE_FIELD_POLICY.IGNORED,
|
|
19408
|
+
isCritical: SAVE_FIELD_POLICY.IGNORED,
|
|
19409
|
+
ganttId: SAVE_FIELD_POLICY.IGNORED,
|
|
19410
|
+
sectorId: SAVE_FIELD_POLICY.IGNORED,
|
|
19411
|
+
proplannerId: SAVE_FIELD_POLICY.IGNORED
|
|
19412
|
+
};
|
|
19413
|
+
var TRACKED_LINK_FIELDS = Object.keys(LINK_SAVE_FIELD_POLICY).filter((field) => {
|
|
19414
|
+
return LINK_SAVE_FIELD_POLICY[field] === SAVE_FIELD_POLICY.TRACKED;
|
|
19415
|
+
});
|
|
19416
|
+
function linkChangedSince(baseline, current) {
|
|
19417
|
+
return TRACKED_LINK_FIELDS.some(
|
|
19418
|
+
(field) => baseline[field] !== current[field]
|
|
19419
|
+
);
|
|
19420
|
+
}
|
|
19421
|
+
function checkNoUpdatedLinks(baseline, current) {
|
|
19422
|
+
const baselineById = new Map(
|
|
19423
|
+
baseline.map((link) => [String(link.id), link])
|
|
19424
|
+
);
|
|
19425
|
+
return current.filter((link) => {
|
|
19426
|
+
const base = baselineById.get(String(link.id));
|
|
19427
|
+
if (!base) return false;
|
|
19428
|
+
return linkChangedSince(base, link);
|
|
19429
|
+
});
|
|
19430
|
+
}
|
|
19431
|
+
|
|
19432
|
+
// src/dispatch/handlers/persistence-rebase-topology.ts
|
|
19433
|
+
var parentKeyOf2 = (parentId) => isRootParent(parentId) ? null : String(parentId);
|
|
19434
|
+
function mergedParents(input) {
|
|
19435
|
+
const parents = /* @__PURE__ */ new Map();
|
|
19436
|
+
for (const local of input.localActivities) {
|
|
19437
|
+
parents.set(String(local.id), parentKeyOf2(local.parentId));
|
|
19438
|
+
}
|
|
19439
|
+
for (const created of input.createActivities) {
|
|
19440
|
+
parents.set(String(created.id), parentKeyOf2(created.parentId));
|
|
19441
|
+
}
|
|
19442
|
+
for (const update of input.updateActivities) {
|
|
19443
|
+
if (update.fields.parentId === void 0) continue;
|
|
19444
|
+
parents.set(update.activityId, parentKeyOf2(update.fields.parentId));
|
|
19445
|
+
}
|
|
19446
|
+
for (const removedId of input.removeActivityIds) parents.delete(removedId);
|
|
19447
|
+
return parents;
|
|
19448
|
+
}
|
|
19449
|
+
function findParentCycle(parents) {
|
|
19450
|
+
const settled = /* @__PURE__ */ new Set();
|
|
19451
|
+
for (const start of parents.keys()) {
|
|
19452
|
+
const path = [];
|
|
19453
|
+
const onPath = /* @__PURE__ */ new Set();
|
|
19454
|
+
let current = start;
|
|
19455
|
+
while (current !== null && current !== void 0 && !settled.has(current)) {
|
|
19456
|
+
if (onPath.has(current)) return path.slice(path.indexOf(current));
|
|
19457
|
+
onPath.add(current);
|
|
19458
|
+
path.push(current);
|
|
19459
|
+
current = parents.get(current);
|
|
19460
|
+
}
|
|
19461
|
+
for (const visited of path) settled.add(visited);
|
|
19462
|
+
}
|
|
19463
|
+
return null;
|
|
19464
|
+
}
|
|
19465
|
+
function restoreRemoteParent(activityId, remote, input) {
|
|
19466
|
+
const existing = input.updateActivities.find(
|
|
19467
|
+
(update) => update.activityId === activityId
|
|
19468
|
+
);
|
|
19469
|
+
if (existing) {
|
|
19470
|
+
const index = input.updateActivities.indexOf(existing);
|
|
19471
|
+
input.updateActivities[index] = {
|
|
19472
|
+
activityId,
|
|
19473
|
+
fields: { ...existing.fields, parentId: remote.parentId }
|
|
19474
|
+
};
|
|
19475
|
+
return;
|
|
19476
|
+
}
|
|
19477
|
+
input.updateActivities.push({
|
|
19478
|
+
activityId,
|
|
19479
|
+
fields: { parentId: remote.parentId }
|
|
19480
|
+
});
|
|
19481
|
+
}
|
|
19482
|
+
function repairParentCycles(input) {
|
|
19483
|
+
const remoteById = new Map(
|
|
19484
|
+
input.remoteActivities.map((remote) => [String(remote.id), remote])
|
|
19485
|
+
);
|
|
19486
|
+
const conflicts = [];
|
|
19487
|
+
let parents = mergedParents(input);
|
|
19488
|
+
let cycle = findParentCycle(parents);
|
|
19489
|
+
while (cycle !== null) {
|
|
19490
|
+
const localChoice = cycle.find((activityId) => {
|
|
19491
|
+
const remote2 = remoteById.get(activityId);
|
|
19492
|
+
return remote2 !== void 0 && parentKeyOf2(remote2.parentId) !== parents.get(activityId);
|
|
19493
|
+
});
|
|
19494
|
+
const remote = localChoice === void 0 ? void 0 : remoteById.get(localChoice);
|
|
19495
|
+
if (localChoice === void 0 || remote === void 0) break;
|
|
19496
|
+
conflicts.push({
|
|
19497
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19498
|
+
id: localChoice,
|
|
19499
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.FIELD,
|
|
19500
|
+
field: ACTIVITY_PROPERTY.PARENT,
|
|
19501
|
+
mine: parents.get(localChoice) ?? null,
|
|
19502
|
+
theirs: parentKeyOf2(remote.parentId)
|
|
19503
|
+
});
|
|
19504
|
+
restoreRemoteParent(localChoice, remote, input);
|
|
19505
|
+
parents = mergedParents(input);
|
|
19506
|
+
cycle = findParentCycle(parents);
|
|
19507
|
+
}
|
|
19508
|
+
return conflicts;
|
|
19509
|
+
}
|
|
19510
|
+
function cyclicActivityCount(adapter) {
|
|
19511
|
+
const edges = projectEffectiveEdges(adapter, adapter.getAllLinks());
|
|
19512
|
+
return circularComponents(edges).reduce(
|
|
19513
|
+
(total, component) => total + component.length,
|
|
19514
|
+
0
|
|
19515
|
+
);
|
|
19516
|
+
}
|
|
19517
|
+
function breakDependencyCycles(adapter, localLinkIdsInCreationOrder, changes) {
|
|
19518
|
+
const removed = /* @__PURE__ */ new Map();
|
|
19519
|
+
const conflicts = [];
|
|
19520
|
+
if (cyclicActivityCount(adapter) === 0) return { conflicts, removed };
|
|
19521
|
+
const withdrawn = [];
|
|
19522
|
+
for (const linkId of localLinkIdsInCreationOrder) {
|
|
19523
|
+
const link = adapter.getLink(linkId);
|
|
19524
|
+
if (!link) continue;
|
|
19525
|
+
withdrawn.push({ ...link });
|
|
19526
|
+
adapter.removeLink(linkId);
|
|
19527
|
+
}
|
|
19528
|
+
let cyclic = cyclicActivityCount(adapter);
|
|
19529
|
+
for (const link of withdrawn) {
|
|
19530
|
+
adapter.addLink(link);
|
|
19531
|
+
const withLink = cyclicActivityCount(adapter);
|
|
19532
|
+
if (withLink <= cyclic) {
|
|
19533
|
+
cyclic = withLink;
|
|
19534
|
+
continue;
|
|
19535
|
+
}
|
|
19536
|
+
adapter.removeLink(String(link.id));
|
|
19537
|
+
removed.set(String(link.id), link);
|
|
19538
|
+
conflicts.push({
|
|
19539
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
19540
|
+
id: String(link.id),
|
|
19541
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.LINK_CYCLE,
|
|
19542
|
+
field: null,
|
|
19543
|
+
mine: snapshotToLink(link),
|
|
19544
|
+
theirs: null
|
|
19545
|
+
});
|
|
19546
|
+
}
|
|
19547
|
+
changes.push(...buildLinkDeletions(new Map(removed)));
|
|
19548
|
+
return { conflicts, removed };
|
|
19549
|
+
}
|
|
19550
|
+
|
|
19551
|
+
// src/dispatch/handlers/persistence-rebase-plan.ts
|
|
19552
|
+
var ORDER_FIELDS = /* @__PURE__ */ new Set([
|
|
19553
|
+
ACTIVITY_PROPERTY.CORRELATIVE_ID,
|
|
19554
|
+
ACTIVITY_PROPERTY.UNIQUE_CORRELATIVE_ID
|
|
19555
|
+
]);
|
|
19556
|
+
var RECOMPUTED_FIELDS = /* @__PURE__ */ new Set([
|
|
19557
|
+
ACTIVITY_PROPERTY.END_DATE
|
|
19558
|
+
]);
|
|
19559
|
+
var RECOMPUTED_PARENT_FIELDS = /* @__PURE__ */ new Set(
|
|
19560
|
+
[
|
|
19561
|
+
ACTIVITY_PROPERTY.START_DATE,
|
|
19562
|
+
ACTIVITY_PROPERTY.DURATION_HOURS,
|
|
19563
|
+
ACTIVITY_PROPERTY.COST,
|
|
19564
|
+
ACTIVITY_PROPERTY.USED_COST,
|
|
19565
|
+
ACTIVITY_PROPERTY.WORK_HOURS
|
|
19566
|
+
]
|
|
19567
|
+
);
|
|
19568
|
+
var FOLLOWS_REMOTE_FIELDS = /* @__PURE__ */ new Set([
|
|
19569
|
+
ACTIVITY_PROPERTY.REAL_WORK_HOURS,
|
|
19570
|
+
ACTIVITY_PROPERTY.HAS_NEW_ACTIVITIES
|
|
19571
|
+
]);
|
|
19572
|
+
var PARENT_DERIVED_FIELDS = /* @__PURE__ */ new Set([
|
|
19573
|
+
...RECOMPUTED_PARENT_FIELDS,
|
|
19574
|
+
ACTIVITY_PROPERTY.PROGRESS,
|
|
19575
|
+
ACTIVITY_PROPERTY.TYPE,
|
|
19576
|
+
ACTIVITY_PROPERTY.REAL_COST,
|
|
19577
|
+
ACTIVITY_PROPERTY.PONDERATOR,
|
|
19578
|
+
ACTIVITY_PROPERTY.AUTO_SCHEDULING
|
|
19579
|
+
]);
|
|
19580
|
+
var UNION_MERGED_FIELDS = /* @__PURE__ */ new Set([
|
|
19581
|
+
ACTIVITY_PROPERTY.NEW_ACTIVITIES_ARRAY
|
|
19582
|
+
]);
|
|
19583
|
+
function planPersistenceRebase(input) {
|
|
19584
|
+
const plan = {
|
|
19585
|
+
createActivities: [],
|
|
19586
|
+
updateActivities: [],
|
|
19587
|
+
removeActivityIds: [],
|
|
19588
|
+
createLinks: [],
|
|
19589
|
+
updateLinks: [],
|
|
19590
|
+
removeLinkIds: [],
|
|
19591
|
+
remoteChanges: [],
|
|
19592
|
+
conflicts: []
|
|
19593
|
+
};
|
|
19594
|
+
planActivities(input, plan);
|
|
19595
|
+
plan.conflicts.push(
|
|
19596
|
+
...repairParentCycles({
|
|
19597
|
+
localActivities: input.localActivities,
|
|
19598
|
+
remoteActivities: input.remoteActivities,
|
|
19599
|
+
createActivities: plan.createActivities,
|
|
19600
|
+
removeActivityIds: plan.removeActivityIds,
|
|
19601
|
+
updateActivities: plan.updateActivities
|
|
19602
|
+
})
|
|
19603
|
+
);
|
|
19604
|
+
planLinks(input, plan);
|
|
19605
|
+
return plan;
|
|
19606
|
+
}
|
|
19607
|
+
function collectParentIds2(activities) {
|
|
19608
|
+
const parentIds = /* @__PURE__ */ new Set();
|
|
19609
|
+
for (const activity of activities) {
|
|
19610
|
+
if (isRootParent(activity.parentId)) continue;
|
|
19611
|
+
parentIds.add(String(activity.parentId));
|
|
19612
|
+
}
|
|
19613
|
+
return parentIds;
|
|
19614
|
+
}
|
|
19615
|
+
function collectLinkTargetIds(links) {
|
|
19616
|
+
return new Set(links.map((link) => String(link.target)));
|
|
19617
|
+
}
|
|
19618
|
+
function indexStructure(input) {
|
|
19619
|
+
return {
|
|
19620
|
+
localParentIds: collectParentIds2(input.localActivities),
|
|
19621
|
+
remoteParentIds: collectParentIds2(input.remoteActivities),
|
|
19622
|
+
localTargetIds: collectLinkTargetIds(input.localLinks),
|
|
19623
|
+
remoteTargetIds: collectLinkTargetIds(input.remoteLinks)
|
|
19624
|
+
};
|
|
19625
|
+
}
|
|
19626
|
+
function mergeContextFor(activityId, structure) {
|
|
19627
|
+
return {
|
|
19628
|
+
local: {
|
|
19629
|
+
isParent: structure.localParentIds.has(activityId),
|
|
19630
|
+
hasPredecessors: structure.localTargetIds.has(activityId)
|
|
19631
|
+
},
|
|
19632
|
+
remote: {
|
|
19633
|
+
isParent: structure.remoteParentIds.has(activityId),
|
|
19634
|
+
hasPredecessors: structure.remoteTargetIds.has(activityId)
|
|
19635
|
+
}
|
|
19636
|
+
};
|
|
19637
|
+
}
|
|
19638
|
+
function isRecomputedOnSide(field, side, activity) {
|
|
19639
|
+
if (RECOMPUTED_FIELDS.has(field)) return true;
|
|
19640
|
+
if (side.isParent && RECOMPUTED_PARENT_FIELDS.has(field)) return true;
|
|
19641
|
+
const isScheduledStart = field === ACTIVITY_PROPERTY.START_DATE && side.hasPredecessors && activity.autoScheduling;
|
|
19642
|
+
if (isScheduledStart) return true;
|
|
19643
|
+
return field === ACTIVITY_PROPERTY.PONDERATOR && !activity.hasCustomPonderator;
|
|
19644
|
+
}
|
|
19645
|
+
function isDerivedOnSide(field, side, activity) {
|
|
19646
|
+
if (isRecomputedOnSide(field, side, activity)) return true;
|
|
19647
|
+
if (FOLLOWS_REMOTE_FIELDS.has(field)) return true;
|
|
19648
|
+
return side.isParent && PARENT_DERIVED_FIELDS.has(field);
|
|
19649
|
+
}
|
|
19650
|
+
function sourceEditsOf(activity, base, side) {
|
|
19651
|
+
if (base === null) return /* @__PURE__ */ new Set();
|
|
19652
|
+
return new Set(
|
|
19653
|
+
changedPersistableActivityFields(activity, base).filter(
|
|
19654
|
+
(field) => !isDerivedOnSide(field, side, activity)
|
|
19655
|
+
)
|
|
19656
|
+
);
|
|
19657
|
+
}
|
|
19658
|
+
function planActivities(input, plan) {
|
|
19659
|
+
const structure = indexStructure(input);
|
|
19660
|
+
const localById = new Map(
|
|
19661
|
+
input.localActivities.map((activity) => [String(activity.id), activity])
|
|
19662
|
+
);
|
|
19663
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
19664
|
+
for (const remote of input.remoteActivities) {
|
|
19665
|
+
const activityId = String(remote.id);
|
|
19666
|
+
remoteIds.add(activityId);
|
|
19667
|
+
const base = input.checkpoint.checkpointActivity(activityId);
|
|
19668
|
+
const local = localById.get(activityId) ?? null;
|
|
19669
|
+
const context = mergeContextFor(activityId, structure);
|
|
19670
|
+
planRemoteActivity(activityId, remote, base, local, context, plan);
|
|
19671
|
+
}
|
|
19672
|
+
for (const activityId of input.checkpoint.checkpointActivityIds()) {
|
|
19673
|
+
if (remoteIds.has(activityId)) continue;
|
|
19674
|
+
const local = localById.get(activityId);
|
|
19675
|
+
if (!local) continue;
|
|
19676
|
+
const base = input.checkpoint.checkpointActivity(activityId);
|
|
19677
|
+
planRemotelyDeletedActivity(activityId, base, local, plan);
|
|
19678
|
+
}
|
|
19679
|
+
}
|
|
19680
|
+
function planRemoteActivity(activityId, remote, base, local, context, plan) {
|
|
19681
|
+
if (base === null) {
|
|
19682
|
+
if (local === null) {
|
|
19683
|
+
plan.createActivities.push(remote);
|
|
19684
|
+
return;
|
|
19685
|
+
}
|
|
19686
|
+
planActivityFields(activityId, remote, null, local, context, plan);
|
|
19687
|
+
return;
|
|
19688
|
+
}
|
|
19689
|
+
const remoteFields = changedPersistableActivityFields(remote, base);
|
|
19690
|
+
if (local === null) {
|
|
19691
|
+
if (remoteFields.length === 0) return;
|
|
19692
|
+
plan.createActivities.push(remote);
|
|
19693
|
+
plan.conflicts.push({
|
|
19694
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19695
|
+
id: activityId,
|
|
19696
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.DELETED_LOCALLY,
|
|
19697
|
+
field: null,
|
|
19698
|
+
mine: null,
|
|
19699
|
+
theirs: projectPersistableActivity(remote)
|
|
19700
|
+
});
|
|
19701
|
+
return;
|
|
19702
|
+
}
|
|
19703
|
+
planActivityFields(activityId, remote, base, local, context, plan);
|
|
19704
|
+
}
|
|
19705
|
+
var MERGE_SIDE = { LOCAL: "local", REMOTE: "remote" };
|
|
19706
|
+
function typeSideOf(context) {
|
|
19707
|
+
if (context.local.isParent) return MERGE_SIDE.LOCAL;
|
|
19708
|
+
if (context.remote.isParent) return MERGE_SIDE.REMOTE;
|
|
19709
|
+
return null;
|
|
19710
|
+
}
|
|
19711
|
+
function winningSide(merge, field) {
|
|
19712
|
+
const remoteChanged = merge.remoteFields.includes(field);
|
|
19713
|
+
const localChanged = merge.localChangedFields.has(field);
|
|
19714
|
+
if (field === ACTIVITY_PROPERTY.TYPE) {
|
|
19715
|
+
if (!localChanged) return MERGE_SIDE.REMOTE;
|
|
19716
|
+
if (!remoteChanged) return MERGE_SIDE.LOCAL;
|
|
19717
|
+
return typeSideOf(merge.context) ?? MERGE_SIDE.REMOTE;
|
|
19718
|
+
}
|
|
19719
|
+
if (isRecomputedOnSide(field, merge.context.local, merge.local)) {
|
|
19720
|
+
return MERGE_SIDE.REMOTE;
|
|
19721
|
+
}
|
|
19722
|
+
if (!localChanged) return MERGE_SIDE.REMOTE;
|
|
19723
|
+
if (merge.remoteSourceFields.has(field)) return MERGE_SIDE.REMOTE;
|
|
19724
|
+
const isParentOnBothSides = merge.context.local.isParent && merge.context.remote.isParent;
|
|
19725
|
+
const followsRemote = FOLLOWS_REMOTE_FIELDS.has(field) || isParentOnBothSides && PARENT_DERIVED_FIELDS.has(field);
|
|
19726
|
+
if (remoteChanged && followsRemote) return MERGE_SIDE.REMOTE;
|
|
19727
|
+
return MERGE_SIDE.LOCAL;
|
|
19728
|
+
}
|
|
19729
|
+
function planActivityFields(activityId, remote, base, local, context, plan) {
|
|
19730
|
+
const localPersistable = projectPersistableActivity(local);
|
|
19731
|
+
const remotePersistable = projectPersistableActivity(remote);
|
|
19732
|
+
const differsFromLocal = changedPersistableActivityFields(
|
|
19733
|
+
remote,
|
|
19734
|
+
localPersistable
|
|
19735
|
+
);
|
|
19736
|
+
if (differsFromLocal.length === 0) return;
|
|
19737
|
+
const remoteFields = base === null ? differsFromLocal : changedPersistableActivityFields(remote, base);
|
|
19738
|
+
const merge = {
|
|
19739
|
+
activityId,
|
|
19740
|
+
remote,
|
|
19741
|
+
local,
|
|
19742
|
+
remotePersistable,
|
|
19743
|
+
localPersistable,
|
|
19744
|
+
remoteFields,
|
|
19745
|
+
localFields: sourceEditsOf(local, base, context.local),
|
|
19746
|
+
localChangedFields: new Set(
|
|
19747
|
+
base === null ? [] : changedPersistableActivityFields(local, base)
|
|
19748
|
+
),
|
|
19749
|
+
remoteSourceFields: base === null ? new Set(differsFromLocal) : sourceEditsOf(remote, base, context.remote),
|
|
19750
|
+
context
|
|
19751
|
+
};
|
|
19752
|
+
planBecameParentConflicts(merge, plan);
|
|
19753
|
+
const fields = mergeFields(merge, differsFromLocal, plan);
|
|
19754
|
+
if (Object.keys(fields).length === 0) return;
|
|
19755
|
+
plan.updateActivities.push({ activityId, fields });
|
|
19756
|
+
}
|
|
19757
|
+
function mergeFields(merge, differsFromLocal, plan) {
|
|
19758
|
+
const { remotePersistable } = merge;
|
|
19759
|
+
const fields = {};
|
|
19760
|
+
for (const field of differsFromLocal) {
|
|
19761
|
+
if (ORDER_FIELDS.has(field) || field === ACTIVITY_PROPERTY.AUTO_SCHEDULING) {
|
|
19762
|
+
continue;
|
|
19763
|
+
}
|
|
19764
|
+
if (UNION_MERGED_FIELDS.has(field)) {
|
|
19765
|
+
mergeUnionField(merge, field, fields);
|
|
19766
|
+
continue;
|
|
19767
|
+
}
|
|
19768
|
+
if (field === ACTIVITY_PROPERTY.HAS_NEW_ACTIVITIES && fields.hasNewActivities !== void 0) {
|
|
19769
|
+
continue;
|
|
19770
|
+
}
|
|
19771
|
+
if (winningSide(merge, field) === MERGE_SIDE.LOCAL) continue;
|
|
19772
|
+
Reflect.set(fields, field, remotePersistable[field]);
|
|
19773
|
+
if (merge.remoteSourceFields.has(field)) {
|
|
19774
|
+
reportSourceField(merge, field, plan);
|
|
19775
|
+
}
|
|
19776
|
+
}
|
|
19777
|
+
if (fields.progress !== void 0 && !merge.context.local.isParent) {
|
|
19778
|
+
fields.autoScheduling = isAutoSchedulingEnabledForProgress(fields.progress);
|
|
19779
|
+
}
|
|
19780
|
+
keepMilestoneDurationConsistent(merge, fields, plan);
|
|
19781
|
+
return fields;
|
|
19782
|
+
}
|
|
19783
|
+
function mergeUnionField(merge, field, fields) {
|
|
19784
|
+
const { localPersistable, remotePersistable } = merge;
|
|
19785
|
+
const merged = unionOfIds(localPersistable[field], remotePersistable[field]);
|
|
19786
|
+
Reflect.set(fields, field, merged);
|
|
19787
|
+
Reflect.set(
|
|
19788
|
+
fields,
|
|
19789
|
+
ACTIVITY_PROPERTY.HAS_NEW_ACTIVITIES,
|
|
19790
|
+
merged.length > 0 || localPersistable.hasNewActivities || remotePersistable.hasNewActivities
|
|
19791
|
+
);
|
|
19792
|
+
}
|
|
19793
|
+
function keepMilestoneDurationConsistent(merge, fields, plan) {
|
|
19794
|
+
const finalType = fields.type ?? merge.local.type;
|
|
19795
|
+
const finalDuration = fields.durationHours ?? merge.local.durationHours;
|
|
19796
|
+
if (finalType !== ACTIVITY_TYPE.MILESTONE || finalDuration === 0) return;
|
|
19797
|
+
const remoteSetDuration = fields.durationHours !== void 0;
|
|
19798
|
+
const field = remoteSetDuration ? ACTIVITY_PROPERTY.TYPE : ACTIVITY_PROPERTY.DURATION_HOURS;
|
|
19799
|
+
if (remoteSetDuration) {
|
|
19800
|
+
fields.type = merge.remote.type;
|
|
19801
|
+
} else {
|
|
19802
|
+
fields.durationHours = 0;
|
|
19803
|
+
}
|
|
19804
|
+
plan.conflicts.push({
|
|
19805
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19806
|
+
id: merge.activityId,
|
|
19807
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.FIELD,
|
|
19808
|
+
field,
|
|
19809
|
+
mine: merge.localPersistable[field],
|
|
19810
|
+
theirs: remoteSetDuration ? merge.remote.type : 0
|
|
19811
|
+
});
|
|
19812
|
+
}
|
|
19813
|
+
function reportSourceField(merge, field, plan) {
|
|
19814
|
+
const mine = merge.localPersistable[field];
|
|
19815
|
+
const theirs = merge.remotePersistable[field];
|
|
19816
|
+
if (merge.localFields.has(field)) {
|
|
19817
|
+
plan.conflicts.push({
|
|
19818
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19819
|
+
id: merge.activityId,
|
|
19820
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.FIELD,
|
|
19821
|
+
field,
|
|
19822
|
+
mine,
|
|
19823
|
+
theirs
|
|
19824
|
+
});
|
|
19825
|
+
return;
|
|
19826
|
+
}
|
|
19827
|
+
plan.remoteChanges.push({
|
|
19828
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19829
|
+
id: merge.activityId,
|
|
19830
|
+
field,
|
|
19831
|
+
before: mine,
|
|
19832
|
+
after: theirs
|
|
19833
|
+
});
|
|
19834
|
+
}
|
|
19835
|
+
function unionOfIds(mine, theirs) {
|
|
19836
|
+
const merged = /* @__PURE__ */ new Set();
|
|
19837
|
+
for (const value of Array.isArray(mine) ? mine : [])
|
|
19838
|
+
merged.add(String(value));
|
|
19839
|
+
for (const value of Array.isArray(theirs) ? theirs : [])
|
|
19840
|
+
merged.add(String(value));
|
|
19841
|
+
return [...merged];
|
|
19842
|
+
}
|
|
19843
|
+
function leafValuesOf(fields, activity) {
|
|
19844
|
+
const picked = {};
|
|
19845
|
+
for (const field of fields) {
|
|
19846
|
+
if (!PARENT_DERIVED_FIELDS.has(field)) continue;
|
|
19847
|
+
if (field === ACTIVITY_PROPERTY.TYPE) continue;
|
|
19848
|
+
Reflect.set(picked, field, activity[field]);
|
|
19849
|
+
}
|
|
19850
|
+
return picked;
|
|
19851
|
+
}
|
|
19852
|
+
function planBecameParentConflicts(merge, plan) {
|
|
19853
|
+
const { context, localPersistable, remotePersistable } = merge;
|
|
19854
|
+
if (context.local.isParent === context.remote.isParent) return;
|
|
19855
|
+
const droppedLeafValues = context.remote.isParent ? leafValuesOf(merge.localFields, localPersistable) : leafValuesOf(merge.remoteFields, remotePersistable);
|
|
19856
|
+
if (Object.keys(droppedLeafValues).length === 0) return;
|
|
19857
|
+
const conflict = context.remote.isParent ? {
|
|
19858
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.BECAME_PARENT_REMOTELY,
|
|
19859
|
+
mine: droppedLeafValues,
|
|
19860
|
+
theirs: remotePersistable
|
|
19861
|
+
} : {
|
|
19862
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.BECAME_PARENT_LOCALLY,
|
|
19863
|
+
mine: localPersistable,
|
|
19864
|
+
theirs: droppedLeafValues
|
|
19865
|
+
};
|
|
19866
|
+
plan.conflicts.push({
|
|
19867
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19868
|
+
id: merge.activityId,
|
|
19869
|
+
field: null,
|
|
19870
|
+
...conflict
|
|
19871
|
+
});
|
|
19872
|
+
}
|
|
19873
|
+
function planRemotelyDeletedActivity(activityId, base, local, plan) {
|
|
19874
|
+
plan.removeActivityIds.push(activityId);
|
|
19875
|
+
const localEdited = base !== null && changedPersistableActivityFields(local, base).length > 0;
|
|
19876
|
+
if (!localEdited) return;
|
|
19877
|
+
plan.conflicts.push({
|
|
19878
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
19879
|
+
id: activityId,
|
|
19880
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.DELETED_REMOTELY,
|
|
19881
|
+
field: null,
|
|
19882
|
+
mine: projectPersistableActivity(local),
|
|
19883
|
+
theirs: null
|
|
19884
|
+
});
|
|
19885
|
+
}
|
|
19886
|
+
function planLinks(input, plan) {
|
|
19887
|
+
const localById = new Map(
|
|
19888
|
+
input.localLinks.map((link) => [String(link.id), link])
|
|
19889
|
+
);
|
|
19890
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
19891
|
+
const createdRemotely = [];
|
|
19892
|
+
for (const remote of input.remoteLinks) {
|
|
19893
|
+
const linkId = String(remote.id);
|
|
19894
|
+
remoteIds.add(linkId);
|
|
19895
|
+
const base = input.checkpoint.checkpointLink(linkId);
|
|
19896
|
+
const local = localById.get(linkId) ?? null;
|
|
19897
|
+
if (base === null && local === null) createdRemotely.push(remote);
|
|
19898
|
+
planRemoteLink(linkId, remote, base, local, plan);
|
|
19899
|
+
}
|
|
19900
|
+
for (const linkId of input.checkpoint.checkpointLinkIds()) {
|
|
19901
|
+
if (remoteIds.has(linkId)) continue;
|
|
19902
|
+
const local = localById.get(linkId);
|
|
19903
|
+
if (!local) continue;
|
|
19904
|
+
const base = input.checkpoint.checkpointLink(linkId);
|
|
19905
|
+
planRemotelyDeletedLink(linkId, base, local, plan);
|
|
19906
|
+
}
|
|
19907
|
+
planDuplicateLinks(input, createdRemotely, remoteIds, plan);
|
|
19908
|
+
}
|
|
19909
|
+
function endpointPairKey(link) {
|
|
19910
|
+
return [String(link.source), String(link.target)].sort().join("|");
|
|
19911
|
+
}
|
|
19912
|
+
function planDuplicateLinks(input, createdRemotely, remoteIds, plan) {
|
|
19913
|
+
if (createdRemotely.length === 0) return;
|
|
19914
|
+
const remoteByPair = new Map(
|
|
19915
|
+
createdRemotely.map((remote) => [endpointPairKey(remote), remote])
|
|
19916
|
+
);
|
|
19917
|
+
for (const local of input.localLinks) {
|
|
19918
|
+
const linkId = String(local.id);
|
|
19919
|
+
const isUnsavedLocally = !remoteIds.has(linkId) && input.checkpoint.checkpointLink(linkId) === null;
|
|
19920
|
+
if (!isUnsavedLocally) continue;
|
|
19921
|
+
const remote = remoteByPair.get(endpointPairKey(local));
|
|
19922
|
+
if (!remote) continue;
|
|
19923
|
+
plan.removeLinkIds.push(linkId);
|
|
19924
|
+
const mine = persistableLinkOf(local);
|
|
19925
|
+
const theirs = persistableLinkOf(remote);
|
|
19926
|
+
if (changedLinkFields(mine, theirs).length === 0) continue;
|
|
19927
|
+
plan.conflicts.push({
|
|
19928
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
19929
|
+
id: String(remote.id),
|
|
19930
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.DUPLICATE_LINK,
|
|
19931
|
+
field: null,
|
|
19932
|
+
mine,
|
|
19933
|
+
theirs
|
|
19934
|
+
});
|
|
19935
|
+
}
|
|
19936
|
+
}
|
|
19937
|
+
function changedLinkFields(current, saved) {
|
|
19938
|
+
return TRACKED_LINK_FIELDS.filter((field) => current[field] !== saved[field]);
|
|
19939
|
+
}
|
|
19940
|
+
function planRemoteLink(linkId, remote, base, local, plan) {
|
|
19941
|
+
if (base === null) {
|
|
19942
|
+
if (local === null) {
|
|
19943
|
+
plan.createLinks.push(remote);
|
|
19944
|
+
return;
|
|
19945
|
+
}
|
|
19946
|
+
planLinkFields(linkId, remote, null, local, plan);
|
|
19947
|
+
return;
|
|
19948
|
+
}
|
|
19949
|
+
if (local === null) {
|
|
19950
|
+
if (changedLinkFields(remote, base).length === 0) return;
|
|
19951
|
+
plan.createLinks.push(remote);
|
|
19952
|
+
plan.conflicts.push({
|
|
19953
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
19954
|
+
id: linkId,
|
|
19955
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.DELETED_LOCALLY,
|
|
19956
|
+
field: null,
|
|
19957
|
+
mine: null,
|
|
19958
|
+
theirs: persistableLinkOf(remote)
|
|
19959
|
+
});
|
|
19960
|
+
return;
|
|
19961
|
+
}
|
|
19962
|
+
planLinkFields(linkId, remote, base, local, plan);
|
|
19963
|
+
}
|
|
19964
|
+
function persistableLinkOf(link) {
|
|
19965
|
+
return {
|
|
19966
|
+
source: String(link.source),
|
|
19967
|
+
target: String(link.target),
|
|
19968
|
+
type: link.type,
|
|
19969
|
+
lag: Number(link.lag)
|
|
19970
|
+
};
|
|
19971
|
+
}
|
|
19972
|
+
function planLinkFields(linkId, remote, base, local, plan) {
|
|
19973
|
+
const remoteLink = persistableLinkOf(remote);
|
|
19974
|
+
const localLink = persistableLinkOf(local);
|
|
19975
|
+
const remoteFields = base === null ? changedLinkFields(remoteLink, localLink) : changedLinkFields(remoteLink, base);
|
|
19976
|
+
if (remoteFields.length === 0) return;
|
|
19977
|
+
const localFields = new Set(
|
|
19978
|
+
base === null ? [] : changedLinkFields(localLink, base)
|
|
19979
|
+
);
|
|
19980
|
+
const differsFromLocal = new Set(
|
|
19981
|
+
changedLinkFields(remoteLink, localLink)
|
|
19982
|
+
);
|
|
19983
|
+
const fields = {};
|
|
19984
|
+
for (const field of remoteFields) {
|
|
19985
|
+
if (!differsFromLocal.has(field)) continue;
|
|
19986
|
+
Reflect.set(fields, field, remoteLink[field]);
|
|
19987
|
+
if (localFields.has(field)) {
|
|
19988
|
+
plan.conflicts.push({
|
|
19989
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
19990
|
+
id: linkId,
|
|
19991
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.FIELD,
|
|
19992
|
+
field,
|
|
19993
|
+
mine: localLink[field],
|
|
19994
|
+
theirs: remoteLink[field]
|
|
19995
|
+
});
|
|
19996
|
+
continue;
|
|
19997
|
+
}
|
|
19998
|
+
plan.remoteChanges.push({
|
|
19999
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
20000
|
+
id: linkId,
|
|
20001
|
+
field,
|
|
20002
|
+
before: localLink[field],
|
|
20003
|
+
after: remoteLink[field]
|
|
20004
|
+
});
|
|
20005
|
+
}
|
|
20006
|
+
if (Object.keys(fields).length === 0) return;
|
|
20007
|
+
plan.updateLinks.push({ linkId, fields });
|
|
20008
|
+
}
|
|
20009
|
+
function planRemotelyDeletedLink(linkId, base, local, plan) {
|
|
20010
|
+
plan.removeLinkIds.push(linkId);
|
|
20011
|
+
const localEdited = base !== null && changedLinkFields(persistableLinkOf(local), base).length > 0;
|
|
20012
|
+
if (!localEdited) return;
|
|
20013
|
+
plan.conflicts.push({
|
|
20014
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
20015
|
+
id: linkId,
|
|
20016
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.DELETED_REMOTELY,
|
|
20017
|
+
field: null,
|
|
20018
|
+
mine: persistableLinkOf(local),
|
|
20019
|
+
theirs: null
|
|
20020
|
+
});
|
|
20021
|
+
}
|
|
20022
|
+
|
|
20023
|
+
// src/dispatch/handlers/persistence-rebase-order.ts
|
|
20024
|
+
var parentKeyOf3 = (parentId) => isRootParent(parentId) ? ROOT_PARENT_ID : String(parentId);
|
|
20025
|
+
var sortValueOf = (correlativeId) => {
|
|
20026
|
+
const value = Number(correlativeId);
|
|
20027
|
+
return Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
|
|
20028
|
+
};
|
|
20029
|
+
function siblingOrdersOf(entries) {
|
|
20030
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
20031
|
+
for (const entry of entries) {
|
|
20032
|
+
const key = parentKeyOf3(entry.parentId);
|
|
20033
|
+
const siblings = grouped.get(key) ?? [];
|
|
20034
|
+
siblings.push(entry);
|
|
20035
|
+
grouped.set(key, siblings);
|
|
20036
|
+
}
|
|
20037
|
+
const orders = /* @__PURE__ */ new Map();
|
|
20038
|
+
for (const [key, siblings] of grouped) {
|
|
20039
|
+
siblings.sort(
|
|
20040
|
+
(left, right) => sortValueOf(left.correlativeId) - sortValueOf(right.correlativeId) || left.id.localeCompare(right.id)
|
|
20041
|
+
);
|
|
20042
|
+
orders.set(
|
|
20043
|
+
key,
|
|
20044
|
+
siblings.map((entry) => entry.id)
|
|
20045
|
+
);
|
|
20046
|
+
}
|
|
20047
|
+
return orders;
|
|
20048
|
+
}
|
|
20049
|
+
var entryOf = (activity) => ({
|
|
20050
|
+
id: String(activity.id),
|
|
20051
|
+
parentId: activity.parentId,
|
|
20052
|
+
correlativeId: activity.correlativeId
|
|
20053
|
+
});
|
|
20054
|
+
function captureSiblingOrders(localActivities, remoteActivities, checkpoint) {
|
|
20055
|
+
const baseEntries = [];
|
|
20056
|
+
for (const activityId of checkpoint.checkpointActivityIds()) {
|
|
20057
|
+
const saved = checkpoint.checkpointActivity(activityId);
|
|
20058
|
+
if (saved === null) continue;
|
|
20059
|
+
baseEntries.push({
|
|
20060
|
+
id: activityId,
|
|
20061
|
+
parentId: saved.parentId,
|
|
20062
|
+
correlativeId: saved.correlativeId
|
|
20063
|
+
});
|
|
20064
|
+
}
|
|
20065
|
+
return {
|
|
20066
|
+
local: siblingOrdersOf(localActivities.map(entryOf)),
|
|
20067
|
+
remote: siblingOrdersOf(remoteActivities.map(entryOf)),
|
|
20068
|
+
base: siblingOrdersOf(baseEntries)
|
|
20069
|
+
};
|
|
20070
|
+
}
|
|
20071
|
+
function isReordered(side, base) {
|
|
20072
|
+
const inBase = new Set(base);
|
|
20073
|
+
const inSide = new Set(side);
|
|
20074
|
+
const sideCommon = side.filter((activityId) => inBase.has(activityId));
|
|
20075
|
+
const baseCommon = base.filter((activityId) => inSide.has(activityId));
|
|
20076
|
+
return sideCommon.some(
|
|
20077
|
+
(activityId, index) => baseCommon[index] !== activityId
|
|
20078
|
+
);
|
|
20079
|
+
}
|
|
20080
|
+
function weave(primary, secondary, members) {
|
|
20081
|
+
const merged = primary.filter((activityId) => members.has(activityId));
|
|
20082
|
+
let anchorIndex = -1;
|
|
20083
|
+
for (const activityId of secondary) {
|
|
20084
|
+
const placedAt = merged.indexOf(activityId);
|
|
20085
|
+
if (placedAt !== -1) {
|
|
20086
|
+
anchorIndex = placedAt;
|
|
20087
|
+
continue;
|
|
20088
|
+
}
|
|
20089
|
+
if (!members.has(activityId)) continue;
|
|
20090
|
+
merged.splice(anchorIndex + 1, 0, activityId);
|
|
20091
|
+
anchorIndex += 1;
|
|
20092
|
+
}
|
|
20093
|
+
for (const activityId of members) {
|
|
20094
|
+
if (!merged.includes(activityId)) merged.push(activityId);
|
|
20095
|
+
}
|
|
20096
|
+
return merged;
|
|
20097
|
+
}
|
|
20098
|
+
function planParentOrder(parentKey, members, sources) {
|
|
20099
|
+
const local = sources.local.get(parentKey) ?? [];
|
|
20100
|
+
const remote = sources.remote.get(parentKey) ?? [];
|
|
20101
|
+
const base = sources.base.get(parentKey) ?? [];
|
|
20102
|
+
const localReordered = isReordered(local, base);
|
|
20103
|
+
const remoteReordered = isReordered(remote, base);
|
|
20104
|
+
const keepsLocalOrder = localReordered && !remoteReordered;
|
|
20105
|
+
const order = keepsLocalOrder ? weave(local, remote, members) : weave(remote, local, members);
|
|
20106
|
+
const overridesLocalOrder = localReordered && remoteReordered && isReordered(local, order);
|
|
20107
|
+
return {
|
|
20108
|
+
order,
|
|
20109
|
+
conflict: overridesLocalOrder ? {
|
|
20110
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
20111
|
+
id: parentKey,
|
|
20112
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.ORDER,
|
|
20113
|
+
field: ACTIVITY_PROPERTY.CORRELATIVE_ID,
|
|
20114
|
+
mine: local.filter((activityId) => members.has(activityId)),
|
|
20115
|
+
theirs: order
|
|
20116
|
+
} : null
|
|
20117
|
+
};
|
|
20118
|
+
}
|
|
20119
|
+
function currentSiblings(adapter) {
|
|
20120
|
+
return siblingOrdersOf(adapter.getAllActivities().map(entryOf));
|
|
20121
|
+
}
|
|
20122
|
+
function applySiblingOrder(adapter, sources) {
|
|
20123
|
+
const { orders, conflicts } = planSiblingOrders(adapter, sources);
|
|
20124
|
+
return { shifts: renumberCorrelatives(adapter, orders), conflicts };
|
|
20125
|
+
}
|
|
20126
|
+
function planSiblingOrders(adapter, sources) {
|
|
20127
|
+
const orders = /* @__PURE__ */ new Map();
|
|
20128
|
+
const conflicts = [];
|
|
20129
|
+
for (const [parentKey, siblings] of currentSiblings(adapter)) {
|
|
20130
|
+
const planned = planParentOrder(parentKey, new Set(siblings), sources);
|
|
20131
|
+
orders.set(parentKey, planned.order);
|
|
20132
|
+
if (planned.conflict !== null) conflicts.push(planned.conflict);
|
|
20133
|
+
}
|
|
20134
|
+
return { orders, conflicts };
|
|
20135
|
+
}
|
|
20136
|
+
function renumberCorrelatives(adapter, orders) {
|
|
20137
|
+
const shifts = [];
|
|
20138
|
+
let counter = 0;
|
|
20139
|
+
const pending = [...orders.get(ROOT_PARENT_ID) ?? []].reverse();
|
|
20140
|
+
while (pending.length > 0) {
|
|
20141
|
+
const activityId = pending.pop();
|
|
20142
|
+
if (activityId === void 0) break;
|
|
20143
|
+
const shift = writeCorrelative(adapter, activityId, counter);
|
|
20144
|
+
if (shift !== null) shifts.push(shift);
|
|
20145
|
+
counter += 1;
|
|
20146
|
+
pushChildrenInReverse(pending, orders.get(activityId) ?? []);
|
|
20147
|
+
}
|
|
20148
|
+
return shifts;
|
|
20149
|
+
}
|
|
20150
|
+
function writeCorrelative(adapter, activityId, correlativeId) {
|
|
20151
|
+
const activity = adapter.getActivity(activityId);
|
|
20152
|
+
if (!activity || activity.correlativeId === correlativeId) return null;
|
|
20153
|
+
const before = Number(activity.correlativeId);
|
|
20154
|
+
adapter.setActivityField(
|
|
20155
|
+
activityId,
|
|
20156
|
+
ACTIVITY_PROPERTY.CORRELATIVE_ID,
|
|
20157
|
+
correlativeId
|
|
20158
|
+
);
|
|
20159
|
+
return { activityId, before: Number.isFinite(before) ? before : void 0 };
|
|
20160
|
+
}
|
|
20161
|
+
function pushChildrenInReverse(pending, children) {
|
|
20162
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
20163
|
+
const childId = children[index];
|
|
20164
|
+
if (childId !== void 0) pending.push(childId);
|
|
20165
|
+
}
|
|
20166
|
+
}
|
|
20167
|
+
|
|
20168
|
+
// src/dispatch/handlers/persistence-rebase-rekey.ts
|
|
20169
|
+
var hasNoPersistedIdentity = (proplannerId) => proplannerId === null || proplannerId === void 0 || proplannerId === 0;
|
|
20170
|
+
var isUnpersisted = (activity) => hasNoPersistedIdentity(activity.proplannerId);
|
|
20171
|
+
function nextFreeLinkId(deps, taken) {
|
|
20172
|
+
let candidate = deps.linkIdGen.next();
|
|
20173
|
+
const isTaken = (linkId) => deps.adapter.getLink(linkId) !== null || deps.isLinkIdReserved(String(linkId)) || taken.has(String(linkId));
|
|
20174
|
+
while (isTaken(candidate)) candidate = deps.linkIdGen.next();
|
|
20175
|
+
return candidate;
|
|
20176
|
+
}
|
|
20177
|
+
function planLinkRekeys(remoteLinks, deps) {
|
|
20178
|
+
const remoteLinkIds = /* @__PURE__ */ new Set();
|
|
20179
|
+
for (const remote of remoteLinks) {
|
|
20180
|
+
remoteLinkIds.add(String(remote.id));
|
|
20181
|
+
deps.linkIdGen.bump(remote.id);
|
|
20182
|
+
}
|
|
20183
|
+
const taken = new Set(remoteLinkIds);
|
|
20184
|
+
const rekeys = [];
|
|
20185
|
+
for (const local of deps.adapter.getAllLinks()) {
|
|
20186
|
+
const linkId = String(local.id);
|
|
20187
|
+
if (!remoteLinkIds.has(linkId)) continue;
|
|
20188
|
+
if (!hasNoPersistedIdentity(local.proplannerId)) continue;
|
|
20189
|
+
const nextId = String(nextFreeLinkId(deps, taken));
|
|
20190
|
+
taken.add(nextId);
|
|
20191
|
+
rekeys.push({ from: linkId, to: nextId });
|
|
20192
|
+
}
|
|
20193
|
+
return rekeys;
|
|
20194
|
+
}
|
|
20195
|
+
function remoteCustomIdOwners(remoteActivities) {
|
|
20196
|
+
const owners = /* @__PURE__ */ new Map();
|
|
20197
|
+
for (const remote of remoteActivities) {
|
|
20198
|
+
if (remote.customId) owners.set(remote.customId, String(remote.id));
|
|
20199
|
+
}
|
|
20200
|
+
return owners;
|
|
20201
|
+
}
|
|
20202
|
+
function isLocalCustomIdEdit(local, deps) {
|
|
20203
|
+
const saved = deps.persistenceCheckpoint.checkpointActivity(String(local.id));
|
|
20204
|
+
return saved === null || saved.customId !== local.customId;
|
|
20205
|
+
}
|
|
20206
|
+
function planCustomIdRekeys(remoteActivities, deps) {
|
|
20207
|
+
const owners = remoteCustomIdOwners(remoteActivities);
|
|
20208
|
+
deps.customIdTracker.populateFromActivities(remoteActivities);
|
|
20209
|
+
const rekeys = [];
|
|
20210
|
+
for (const local of deps.adapter.getVisualOrderIds()) {
|
|
20211
|
+
const activity = deps.adapter.getActivity(local);
|
|
20212
|
+
if (!activity?.customId) continue;
|
|
20213
|
+
const owner = owners.get(activity.customId);
|
|
20214
|
+
if (owner === void 0) continue;
|
|
20215
|
+
const isSameEntity = owner === String(activity.id) && !isUnpersisted(activity);
|
|
20216
|
+
if (isSameEntity) continue;
|
|
20217
|
+
if (!isLocalCustomIdEdit(activity, deps)) continue;
|
|
20218
|
+
rekeys.push({
|
|
20219
|
+
activityId: activity.id,
|
|
20220
|
+
nextCustomId: deps.customIdTracker.generateCustomIdForRestoredChild(
|
|
20221
|
+
activity.customId
|
|
20222
|
+
)
|
|
20223
|
+
});
|
|
20224
|
+
}
|
|
20225
|
+
return rekeys;
|
|
20226
|
+
}
|
|
20227
|
+
function nextFreeActivityId(deps, claimed) {
|
|
20228
|
+
let candidate = deps.activityIdGen.next();
|
|
20229
|
+
const isTaken = (activityId) => deps.adapter.activityExists(activityId) || deps.isActivityIdReserved(String(activityId)) || claimed.has(String(activityId));
|
|
20230
|
+
while (isTaken(candidate)) candidate = deps.activityIdGen.next();
|
|
20231
|
+
return candidate;
|
|
20232
|
+
}
|
|
20233
|
+
function planLocalRekeys(remote, deps) {
|
|
20234
|
+
const remoteActivities = remote.activities;
|
|
20235
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
20236
|
+
const remoteUniqueCorrelativeIds = /* @__PURE__ */ new Set();
|
|
20237
|
+
for (const remote2 of remoteActivities) {
|
|
20238
|
+
remoteIds.add(String(remote2.id));
|
|
20239
|
+
remoteUniqueCorrelativeIds.add(String(remote2.uniqueCorrelativeId));
|
|
20240
|
+
deps.activityIdGen.bump(remote2.id);
|
|
20241
|
+
deps.uidGen.bump(remote2.uniqueCorrelativeId);
|
|
20242
|
+
}
|
|
20243
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
20244
|
+
const activities = [];
|
|
20245
|
+
const uniqueCorrelativeIds = [];
|
|
20246
|
+
for (const local of deps.adapter.getAllActivities()) {
|
|
20247
|
+
if (!isUnpersisted(local)) continue;
|
|
20248
|
+
const localId = String(local.id);
|
|
20249
|
+
if (remoteIds.has(localId)) {
|
|
20250
|
+
const nextId = nextFreeActivityId(deps, claimed);
|
|
20251
|
+
claimed.add(String(nextId));
|
|
20252
|
+
activities.push({
|
|
20253
|
+
from: localId,
|
|
20254
|
+
to: String(nextId),
|
|
20255
|
+
parentId: deps.adapter.getParent(local.id)?.id ?? null,
|
|
20256
|
+
childIds: [...deps.adapter.getChildren(local.id)],
|
|
20257
|
+
outgoingLinkIds: [...deps.adapter.getOutgoingLinkIds(local.id)],
|
|
20258
|
+
incomingLinkIds: [...deps.adapter.getIncomingLinkIds(local.id)]
|
|
20259
|
+
});
|
|
20260
|
+
}
|
|
20261
|
+
if (remoteUniqueCorrelativeIds.has(String(local.uniqueCorrelativeId))) {
|
|
20262
|
+
uniqueCorrelativeIds.push({
|
|
20263
|
+
activityId: local.id,
|
|
20264
|
+
nextUniqueCorrelativeId: deps.uidGen.next()
|
|
20265
|
+
});
|
|
20266
|
+
}
|
|
20267
|
+
}
|
|
20268
|
+
return {
|
|
20269
|
+
activities,
|
|
20270
|
+
links: planLinkRekeys(remote.links, deps),
|
|
20271
|
+
uniqueCorrelativeIds,
|
|
20272
|
+
customIds: planCustomIdRekeys(remoteActivities, deps)
|
|
20273
|
+
};
|
|
20274
|
+
}
|
|
20275
|
+
function collectRekeyTouchedIds(plan) {
|
|
20276
|
+
const touched = /* @__PURE__ */ new Set();
|
|
20277
|
+
for (const rekey of plan.activities) {
|
|
20278
|
+
touched.add(rekey.from);
|
|
20279
|
+
touched.add(rekey.to);
|
|
20280
|
+
if (rekey.parentId !== null) touched.add(String(rekey.parentId));
|
|
20281
|
+
for (const childId of rekey.childIds) touched.add(String(childId));
|
|
20282
|
+
}
|
|
20283
|
+
for (const rekey of plan.uniqueCorrelativeIds) {
|
|
20284
|
+
touched.add(String(rekey.activityId));
|
|
20285
|
+
}
|
|
20286
|
+
for (const rekey of plan.customIds) touched.add(String(rekey.activityId));
|
|
20287
|
+
return touched;
|
|
20288
|
+
}
|
|
20289
|
+
function replaceId(ids, from, to) {
|
|
20290
|
+
return ids.map((candidate) => String(candidate) === from ? to : candidate);
|
|
20291
|
+
}
|
|
20292
|
+
function moveActivityIdentity(rekey, adapter) {
|
|
20293
|
+
const current = adapter.getActivity(rekey.from);
|
|
20294
|
+
if (!current) return;
|
|
20295
|
+
const relocated = structuredCloneActivity(current);
|
|
20296
|
+
relocated.id = rekey.to;
|
|
20297
|
+
const wasChecked = adapter.isChecked(rekey.from);
|
|
20298
|
+
const wasVisible = adapter.isVisible(rekey.from);
|
|
20299
|
+
const lastStartDate = adapter.getLastStartDate(rekey.from);
|
|
20300
|
+
const promotionSnapshot = adapter.getPromotionSnapshot(rekey.from);
|
|
20301
|
+
const progressFallback = adapter.getProgressFallback(rekey.from);
|
|
20302
|
+
adapter.removeActivity(rekey.from);
|
|
20303
|
+
adapter.addActivity(relocated);
|
|
20304
|
+
if (wasChecked) adapter.setChecked(rekey.to, true);
|
|
20305
|
+
if (!wasVisible) adapter.setVisible(rekey.to, false);
|
|
20306
|
+
if (lastStartDate) adapter.setLastStartDate(rekey.to, lastStartDate);
|
|
20307
|
+
if (promotionSnapshot) {
|
|
20308
|
+
adapter.setPromotionSnapshot(rekey.to, promotionSnapshot);
|
|
20309
|
+
}
|
|
20310
|
+
if (progressFallback !== null) {
|
|
20311
|
+
adapter.setProgressFallback(rekey.to, progressFallback);
|
|
20312
|
+
}
|
|
20313
|
+
for (const childId of rekey.childIds) {
|
|
20314
|
+
adapter.setActivityFields(childId, { parentId: rekey.to });
|
|
20315
|
+
}
|
|
20316
|
+
for (const linkId of rekey.outgoingLinkIds) {
|
|
20317
|
+
const link = adapter.getLink(linkId);
|
|
20318
|
+
if (link) adapter.setLinkEndpoints(linkId, rekey.to, link.target);
|
|
20319
|
+
}
|
|
20320
|
+
for (const linkId of rekey.incomingLinkIds) {
|
|
20321
|
+
const link = adapter.getLink(linkId);
|
|
20322
|
+
if (link) adapter.setLinkEndpoints(linkId, link.source, rekey.to);
|
|
20323
|
+
}
|
|
20324
|
+
const parent = adapter.getParent(rekey.to);
|
|
20325
|
+
if (!parent) return;
|
|
20326
|
+
const parentTracksNewActivity = parent.newActivityIds.some(
|
|
20327
|
+
(candidate) => String(candidate) === rekey.from
|
|
20328
|
+
);
|
|
20329
|
+
if (!parentTracksNewActivity) return;
|
|
20330
|
+
adapter.setActivityFields(parent.id, {
|
|
20331
|
+
newActivityIds: replaceId(parent.newActivityIds, rekey.from, rekey.to)
|
|
20332
|
+
});
|
|
20333
|
+
}
|
|
20334
|
+
function moveLinkIdentities(rekeys, adapter) {
|
|
20335
|
+
const before = /* @__PURE__ */ new Map();
|
|
20336
|
+
const after = [];
|
|
20337
|
+
for (const rekey of rekeys) {
|
|
20338
|
+
const current = adapter.getLink(rekey.from);
|
|
20339
|
+
if (!current) continue;
|
|
20340
|
+
before.set(rekey.from, { ...current });
|
|
20341
|
+
adapter.removeLink(rekey.from);
|
|
20342
|
+
const relocated = { ...current, id: rekey.to };
|
|
20343
|
+
adapter.addLink(relocated);
|
|
20344
|
+
after.push(relocated);
|
|
20345
|
+
}
|
|
20346
|
+
return { before, after };
|
|
20347
|
+
}
|
|
20348
|
+
function applyLocalRekeys(plan, adapter) {
|
|
20349
|
+
for (const rekey of plan.uniqueCorrelativeIds) {
|
|
20350
|
+
adapter.setActivityField(
|
|
20351
|
+
rekey.activityId,
|
|
20352
|
+
ACTIVITY_PROPERTY.UNIQUE_CORRELATIVE_ID,
|
|
20353
|
+
rekey.nextUniqueCorrelativeId
|
|
20354
|
+
);
|
|
20355
|
+
}
|
|
20356
|
+
for (const rekey of plan.customIds) {
|
|
20357
|
+
adapter.setActivityField(
|
|
20358
|
+
rekey.activityId,
|
|
20359
|
+
ACTIVITY_PROPERTY.CUSTOM_ID,
|
|
20360
|
+
rekey.nextCustomId
|
|
20361
|
+
);
|
|
20362
|
+
}
|
|
20363
|
+
for (const rekey of plan.activities) moveActivityIdentity(rekey, adapter);
|
|
20364
|
+
return moveLinkIdentities(plan.links, adapter);
|
|
20365
|
+
}
|
|
20366
|
+
|
|
20367
|
+
// src/dispatch/handlers/persistence-rebase.ts
|
|
20368
|
+
function pickDefaultCalendarId(calendars) {
|
|
20369
|
+
for (const calendar of calendars.values()) {
|
|
20370
|
+
if (calendar.is_default) return String(calendar.id);
|
|
20371
|
+
}
|
|
20372
|
+
return null;
|
|
20373
|
+
}
|
|
20374
|
+
function namesUnknownCalendar(action, calendars) {
|
|
20375
|
+
return action.remote.activities.some(
|
|
20376
|
+
(row2) => row2.calendarId != null && !calendars.has(String(row2.calendarId))
|
|
20377
|
+
);
|
|
20378
|
+
}
|
|
20379
|
+
function normalizeRemote(action, deps) {
|
|
20380
|
+
const { activities, activityIds } = normalizeActivities(
|
|
20381
|
+
[...action.remote.activities],
|
|
20382
|
+
{
|
|
20383
|
+
knownCalendarIds: new Set(deps.calendars.keys()),
|
|
20384
|
+
defaultCalendarId: pickDefaultCalendarId(deps.calendars),
|
|
20385
|
+
hoursPerDay: deps.sector.hoursPerDay,
|
|
20386
|
+
reporter: deps.reporter,
|
|
20387
|
+
recordDiagnostic: noopLoadDiagnosticRecorder
|
|
20388
|
+
}
|
|
20389
|
+
);
|
|
20390
|
+
const links = normalizeLinks([...action.remote.links], {
|
|
20391
|
+
knownActivityIds: activityIds,
|
|
20392
|
+
hoursPerDay: deps.sector.hoursPerDay,
|
|
20393
|
+
reporter: deps.reporter,
|
|
20394
|
+
recordDiagnostic: noopLoadDiagnosticRecorder
|
|
20395
|
+
});
|
|
20396
|
+
return { activities, links };
|
|
20397
|
+
}
|
|
20398
|
+
function hasPersistedRows(remote) {
|
|
20399
|
+
return remote.activities.every(
|
|
20400
|
+
(activity) => typeof activity.proplannerId === "number" && activity.proplannerId > 0
|
|
20401
|
+
);
|
|
20402
|
+
}
|
|
20403
|
+
async function dispatchPersistenceRebase(action, options, deps) {
|
|
20404
|
+
const { adapter, scheduler, sector } = deps;
|
|
20405
|
+
if (namesUnknownCalendar(action, deps.calendars)) {
|
|
20406
|
+
return { ok: false, reason: REJECTION_REASON.INVALID };
|
|
20407
|
+
}
|
|
20408
|
+
const remote = normalizeRemote(action, deps);
|
|
20409
|
+
if (!hasPersistedRows(remote)) {
|
|
20410
|
+
return { ok: false, reason: REJECTION_REASON.INVALID };
|
|
20411
|
+
}
|
|
20412
|
+
scheduler.invalidateAllCaches();
|
|
20413
|
+
const localLinkOrder = unsavedLinkIdsInCreationOrder(adapter);
|
|
20414
|
+
const rekeys = await rekeyCollidingLocalRows(remote, deps);
|
|
20415
|
+
const plan = planPersistenceRebase({
|
|
20416
|
+
remoteActivities: remote.activities,
|
|
20417
|
+
remoteLinks: remote.links,
|
|
20418
|
+
localActivities: adapter.getAllActivities(),
|
|
20419
|
+
localLinks: adapter.getAllLinks(),
|
|
20420
|
+
checkpoint: deps.persistenceCheckpoint
|
|
20421
|
+
});
|
|
20422
|
+
const siblingSources = captureSiblingOrders(
|
|
20423
|
+
adapter.getAllActivities(),
|
|
20424
|
+
remote.activities,
|
|
20425
|
+
deps.persistenceCheckpoint
|
|
20426
|
+
);
|
|
20427
|
+
const removedActivityIds = new Set(plan.removeActivityIds);
|
|
20428
|
+
const touchedIds = collectTouchedActivityIds(plan, adapter);
|
|
20429
|
+
const beforeSnap = await snapshotBeforeRekeys(adapter, touchedIds, rekeys);
|
|
20430
|
+
for (const activityId of rekeys.touchedIds) touchedIds.add(activityId);
|
|
20431
|
+
const beforeLinks = captureLinksBefore(plan, removedActivityIds, adapter);
|
|
20432
|
+
const viewStateChanges2 = captureDeletedViewState(adapter, removedActivityIds);
|
|
20433
|
+
for (const linkId of beforeLinks.keys()) adapter.removeLink(linkId);
|
|
20434
|
+
for (const activityId of removedActivityIds)
|
|
20435
|
+
adapter.removeActivity(activityId);
|
|
20436
|
+
for (const activity of plan.createActivities) {
|
|
20437
|
+
adapter.addActivity(structuredCloneActivity(activity));
|
|
20438
|
+
deps.uidGen.bump(activity.uniqueCorrelativeId);
|
|
20439
|
+
deps.customIdTracker.populateFromActivities([activity]);
|
|
20440
|
+
}
|
|
20441
|
+
for (const merge of plan.updateActivities) {
|
|
20442
|
+
adapter.setActivityFields(merge.activityId, merge.fields);
|
|
20443
|
+
}
|
|
20444
|
+
deriveLeafEndDates(plan, adapter);
|
|
20445
|
+
const orphanConflicts = reparentOrphans(
|
|
20446
|
+
adapter,
|
|
20447
|
+
deps.persistenceCheckpoint,
|
|
20448
|
+
touchedIds
|
|
20449
|
+
);
|
|
20450
|
+
demoteEmptiedParents(deps, touchedIds);
|
|
20451
|
+
normalizeMergedParents(adapter, touchedIds);
|
|
20452
|
+
recomputeTouchedRollups(adapter, touchedIds);
|
|
20453
|
+
const linkChanges = [
|
|
20454
|
+
...rekeyedLinkChanges(rekeys.links),
|
|
20455
|
+
...applyLinkPlan(plan, beforeLinks, adapter)
|
|
20456
|
+
];
|
|
20457
|
+
const droppedLinkConflicts = dropLinksWithoutEndpoints(adapter, linkChanges);
|
|
20458
|
+
const cycleRepair = breakDependencyCycles(
|
|
20459
|
+
adapter,
|
|
20460
|
+
followRekeys(localLinkOrder, rekeys.plan.links),
|
|
20461
|
+
linkChanges
|
|
20462
|
+
);
|
|
20463
|
+
const createdIds = new Set(
|
|
20464
|
+
plan.createActivities.map((activity) => String(activity.id))
|
|
20465
|
+
);
|
|
20466
|
+
const insertedIds = /* @__PURE__ */ new Set([
|
|
20467
|
+
...createdIds,
|
|
20468
|
+
...rekeys.plan.activities.map((rekey) => rekey.to)
|
|
20469
|
+
]);
|
|
20470
|
+
const siblingOrder = applySiblingOrder(adapter, siblingSources);
|
|
20471
|
+
const correlativeShifts = [
|
|
20472
|
+
...siblingOrder.shifts,
|
|
20473
|
+
...recomputeCorrelativeIds(adapter)
|
|
20474
|
+
].filter((shift) => !insertedIds.has(shift.activityId));
|
|
20475
|
+
const { scheduledIds } = await runPostMutation(
|
|
20476
|
+
{
|
|
20477
|
+
adapter,
|
|
20478
|
+
scheduler,
|
|
20479
|
+
sector,
|
|
20480
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId,
|
|
20481
|
+
runAutomaticP6Schedule: deps.runAutomaticP6Schedule
|
|
20482
|
+
},
|
|
20483
|
+
{
|
|
20484
|
+
action,
|
|
20485
|
+
autoscheduleFrom: AUTOSCHEDULE_SCOPE.ROOTS,
|
|
20486
|
+
recomputeParentsFrom: withChildrenOfParents(touchedIds, adapter),
|
|
20487
|
+
recomputeParentProgress: true,
|
|
20488
|
+
now: deps.now,
|
|
20489
|
+
options
|
|
20490
|
+
}
|
|
20491
|
+
);
|
|
20492
|
+
alignParentAutoScheduling(adapter, touchedIds);
|
|
20493
|
+
const changes = await assembleChangeSet(adapter, {
|
|
20494
|
+
source: action,
|
|
20495
|
+
beforeSnap,
|
|
20496
|
+
touchedIds,
|
|
20497
|
+
scheduledIds,
|
|
20498
|
+
links: linkChanges,
|
|
20499
|
+
hoursPerDay: sector.hoursPerDay,
|
|
20500
|
+
correlativeShifts
|
|
20501
|
+
});
|
|
20502
|
+
const report = {
|
|
20503
|
+
remoteChanges: plan.remoteChanges,
|
|
20504
|
+
conflicts: [
|
|
20505
|
+
...plan.conflicts,
|
|
20506
|
+
...orphanConflicts,
|
|
20507
|
+
...siblingOrder.conflicts,
|
|
20508
|
+
...droppedLinkConflicts,
|
|
20509
|
+
...cycleRepair.conflicts
|
|
20510
|
+
],
|
|
20511
|
+
createdActivityIds: [...createdIds],
|
|
20512
|
+
removedActivityIds: [...removedActivityIds],
|
|
20513
|
+
rekeyedActivities: rekeys.plan.activities.map(({ from, to }) => ({
|
|
20514
|
+
from,
|
|
20515
|
+
to
|
|
20516
|
+
})),
|
|
20517
|
+
rekeyedLinks: rekeys.plan.links,
|
|
20518
|
+
createdLinkIds: plan.createLinks.map((link) => String(link.id)),
|
|
20519
|
+
removedLinkIds: linkIdsGoneFromState(linkChanges, adapter)
|
|
20520
|
+
};
|
|
20521
|
+
return {
|
|
20522
|
+
ok: true,
|
|
20523
|
+
changes: viewStateChanges2.length > 0 ? { ...changes, viewState: viewStateChanges2 } : changes,
|
|
20524
|
+
rebase: report,
|
|
20525
|
+
__beforeSnap: beforeSnap,
|
|
20526
|
+
__beforeLinks: new Map([
|
|
20527
|
+
...rekeys.links.before,
|
|
20528
|
+
...beforeLinks,
|
|
20529
|
+
...cycleRepair.removed
|
|
20530
|
+
]),
|
|
20531
|
+
__rebaseCheckpoint: { activities: remote.activities, links: remote.links }
|
|
20532
|
+
};
|
|
20533
|
+
}
|
|
20534
|
+
function linkIdsGoneFromState(linkChanges, adapter) {
|
|
20535
|
+
return linkChanges.filter((change) => change.kind === ENTITY_CHANGE_KIND.DELETED).map((change) => String(change.id)).filter((linkId) => !adapter.getLink(linkId));
|
|
20536
|
+
}
|
|
20537
|
+
async function rekeyCollidingLocalRows(remote, deps) {
|
|
20538
|
+
const plan = planLocalRekeys(remote, deps);
|
|
20539
|
+
const touchedIds = collectRekeyTouchedIds(plan);
|
|
20540
|
+
const beforeSnap = await snapshotActivities(deps.adapter, touchedIds);
|
|
20541
|
+
const links = applyLocalRekeys(plan, deps.adapter);
|
|
20542
|
+
return { plan, touchedIds, beforeSnap, links };
|
|
20543
|
+
}
|
|
20544
|
+
function rekeyedLinkChanges(links) {
|
|
20545
|
+
const changes = [
|
|
20546
|
+
...buildLinkDeletions(new Map(links.before))
|
|
20547
|
+
];
|
|
20548
|
+
for (const after of links.after) {
|
|
20549
|
+
changes.push({
|
|
20550
|
+
id: String(after.id),
|
|
20551
|
+
kind: ENTITY_CHANGE_KIND.CREATED,
|
|
20552
|
+
fields: {
|
|
20553
|
+
source: { before: void 0, after: String(after.source) },
|
|
20554
|
+
target: { before: void 0, after: String(after.target) },
|
|
20555
|
+
type: { before: void 0, after: after.type },
|
|
20556
|
+
lag: { before: void 0, after: Number(after.lag) }
|
|
20557
|
+
},
|
|
20558
|
+
after: snapshotToLink(after)
|
|
20559
|
+
});
|
|
20560
|
+
}
|
|
20561
|
+
return changes;
|
|
20562
|
+
}
|
|
20563
|
+
async function snapshotBeforeRekeys(adapter, touchedIds, rekeys) {
|
|
20564
|
+
const touchedOutsideRekey = new Set(
|
|
20565
|
+
[...touchedIds].filter((activityId) => !rekeys.touchedIds.has(activityId))
|
|
20566
|
+
);
|
|
20567
|
+
return new Map([
|
|
20568
|
+
...await snapshotActivities(adapter, touchedOutsideRekey),
|
|
20569
|
+
...rekeys.beforeSnap
|
|
20570
|
+
]);
|
|
20571
|
+
}
|
|
20572
|
+
function deriveLeafEndDates(plan, adapter) {
|
|
20573
|
+
for (const merge of plan.updateActivities) {
|
|
20574
|
+
const activity = adapter.getActivity(merge.activityId);
|
|
20575
|
+
if (!activity || adapter.hasChildren(activity.id)) continue;
|
|
20576
|
+
adapter.setActivityField(
|
|
20577
|
+
activity.id,
|
|
20578
|
+
ACTIVITY_PROPERTY.END_DATE,
|
|
20579
|
+
adapter.calculateEndDate({
|
|
20580
|
+
startDate: activity.startDate,
|
|
20581
|
+
durationHours: activity.durationHours,
|
|
20582
|
+
task: activity
|
|
20583
|
+
})
|
|
20584
|
+
);
|
|
20585
|
+
}
|
|
20586
|
+
}
|
|
20587
|
+
function unsavedLinkIdsInCreationOrder(adapter) {
|
|
20588
|
+
return adapter.getAllLinks().filter((link) => {
|
|
20589
|
+
const proplannerId = link.proplannerId;
|
|
20590
|
+
return proplannerId === null || proplannerId === void 0 || proplannerId === 0;
|
|
20591
|
+
}).map((link) => String(link.id)).sort((left, right) => Number(left) - Number(right));
|
|
20592
|
+
}
|
|
20593
|
+
function followRekeys(linkIds, rekeys) {
|
|
20594
|
+
const renamed = new Map(rekeys.map(({ from, to }) => [from, to]));
|
|
20595
|
+
return linkIds.map((linkId) => renamed.get(linkId) ?? linkId);
|
|
20596
|
+
}
|
|
20597
|
+
function alignParentAutoScheduling(adapter, touchedIds) {
|
|
20598
|
+
for (const activityId of touchedIds) {
|
|
20599
|
+
if (!adapter.hasChildren(activityId)) continue;
|
|
20600
|
+
const activity = adapter.getActivity(activityId);
|
|
20601
|
+
if (!activity) continue;
|
|
20602
|
+
const autoScheduling = isAutoSchedulingEnabledForProgress(
|
|
20603
|
+
activity.progress
|
|
20604
|
+
);
|
|
20605
|
+
if (activity.autoScheduling === autoScheduling) continue;
|
|
20606
|
+
adapter.setActivityField(
|
|
20607
|
+
activityId,
|
|
20608
|
+
ACTIVITY_PROPERTY.AUTO_SCHEDULING,
|
|
20609
|
+
autoScheduling
|
|
20610
|
+
);
|
|
20611
|
+
}
|
|
20612
|
+
}
|
|
20613
|
+
function normalizeMergedParents(adapter, touchedIds) {
|
|
20614
|
+
for (const activityId of touchedIds) {
|
|
20615
|
+
if (!adapter.hasChildren(activityId)) continue;
|
|
20616
|
+
const activity = adapter.getActivity(activityId);
|
|
20617
|
+
if (!activity) continue;
|
|
20618
|
+
if (activity.type !== ACTIVITY_TYPE.PROJECT) {
|
|
20619
|
+
adapter.setActivityField(
|
|
20620
|
+
activityId,
|
|
20621
|
+
ACTIVITY_PROPERTY.TYPE,
|
|
20622
|
+
ACTIVITY_TYPE.PROJECT
|
|
20623
|
+
);
|
|
20624
|
+
}
|
|
20625
|
+
if (activity.customId) {
|
|
20626
|
+
adapter.setActivityField(activityId, ACTIVITY_PROPERTY.CUSTOM_ID, null);
|
|
20627
|
+
}
|
|
20628
|
+
}
|
|
20629
|
+
}
|
|
20630
|
+
function demoteEmptiedParents(deps, touchedIds) {
|
|
20631
|
+
const { adapter } = deps;
|
|
20632
|
+
for (const activityId of touchedIds) {
|
|
20633
|
+
const activity = adapter.getActivity(activityId);
|
|
20634
|
+
if (!activity || activity.type !== ACTIVITY_TYPE.PROJECT) continue;
|
|
20635
|
+
if (isRootParent(activity.parentId) || adapter.hasChildren(activityId)) {
|
|
20636
|
+
continue;
|
|
20637
|
+
}
|
|
20638
|
+
const demotion = buildParentDemotionMutations({
|
|
20639
|
+
parent: activity,
|
|
20640
|
+
remainingChildIds: [],
|
|
20641
|
+
defaultDurationHours: deps.sector.hoursPerDay,
|
|
20642
|
+
promotionSnapshot: adapter.getPromotionSnapshot(activityId),
|
|
20643
|
+
computeEndDate: (startDate, durationHours) => adapter.calculateEndDate({ startDate, durationHours, task: activity }),
|
|
20644
|
+
dataDateCapability: deps.dataDateCapability,
|
|
20645
|
+
hasCompleteSchedulingState: deps.hasCompleteSchedulingState
|
|
20646
|
+
});
|
|
20647
|
+
if (!demotion) continue;
|
|
20648
|
+
adapter.setActivityFields(activityId, demotion.fields);
|
|
20649
|
+
const previousSiblingId = findPrevSiblingHelper(activityId, adapter);
|
|
20650
|
+
const previousCustomId = previousSiblingId ? adapter.getActivity(previousSiblingId)?.customId : null;
|
|
20651
|
+
adapter.setActivityField(
|
|
20652
|
+
activityId,
|
|
20653
|
+
ACTIVITY_PROPERTY.CUSTOM_ID,
|
|
20654
|
+
deps.customIdTracker.generateCustomIdForRestoredChild(
|
|
20655
|
+
previousCustomId || null
|
|
20656
|
+
)
|
|
20657
|
+
);
|
|
20658
|
+
}
|
|
20659
|
+
}
|
|
20660
|
+
function recomputeTouchedRollups(adapter, touchedIds) {
|
|
20661
|
+
const parentIds = /* @__PURE__ */ new Set();
|
|
20662
|
+
for (const activityId of touchedIds) {
|
|
20663
|
+
const activity = adapter.getActivity(activityId);
|
|
20664
|
+
if (!activity) continue;
|
|
20665
|
+
if (adapter.hasChildren(activityId)) parentIds.add(activityId);
|
|
20666
|
+
if (!isRootParent(activity.parentId)) {
|
|
20667
|
+
parentIds.add(String(activity.parentId));
|
|
20668
|
+
}
|
|
20669
|
+
}
|
|
20670
|
+
for (const parentId of parentIds) {
|
|
20671
|
+
recomputeRollupCascadesForParent(parentId, adapter);
|
|
20672
|
+
}
|
|
20673
|
+
}
|
|
20674
|
+
function withChildrenOfParents(activityIds, adapter) {
|
|
20675
|
+
const seeds = new Set(activityIds);
|
|
20676
|
+
for (const activityId of activityIds) {
|
|
20677
|
+
for (const childId of adapter.getChildren(activityId)) {
|
|
20678
|
+
seeds.add(String(childId));
|
|
20679
|
+
}
|
|
20680
|
+
}
|
|
20681
|
+
return seeds;
|
|
20682
|
+
}
|
|
20683
|
+
function collectTouchedActivityIds(plan, adapter) {
|
|
20684
|
+
const touched = /* @__PURE__ */ new Set();
|
|
20685
|
+
const touchParent = (parentId) => {
|
|
20686
|
+
if (parentId === null || parentId === void 0) return;
|
|
20687
|
+
if (isRootParent(parentId)) return;
|
|
20688
|
+
touched.add(String(parentId));
|
|
20689
|
+
};
|
|
20690
|
+
for (const activityId of plan.removeActivityIds) {
|
|
20691
|
+
touched.add(activityId);
|
|
20692
|
+
touchParent(adapter.getActivity(activityId)?.parentId);
|
|
20693
|
+
}
|
|
20694
|
+
for (const activity of plan.createActivities) {
|
|
20695
|
+
touched.add(String(activity.id));
|
|
20696
|
+
touchParent(activity.parentId);
|
|
20697
|
+
}
|
|
20698
|
+
for (const merge of plan.updateActivities) {
|
|
20699
|
+
touched.add(merge.activityId);
|
|
20700
|
+
touchParent(adapter.getActivity(merge.activityId)?.parentId);
|
|
20701
|
+
if (merge.fields.parentId !== void 0) touchParent(merge.fields.parentId);
|
|
20702
|
+
}
|
|
20703
|
+
return touched;
|
|
20704
|
+
}
|
|
20705
|
+
function captureLinksBefore(plan, removedActivityIds, adapter) {
|
|
20706
|
+
const before = /* @__PURE__ */ new Map();
|
|
20707
|
+
const incident = collectIncidentLinkIds(removedActivityIds, adapter);
|
|
20708
|
+
for (const linkId of [...plan.removeLinkIds, ...incident]) {
|
|
20709
|
+
const link = adapter.getLink(linkId);
|
|
20710
|
+
if (link) before.set(String(linkId), { ...link });
|
|
20711
|
+
}
|
|
20712
|
+
return before;
|
|
20713
|
+
}
|
|
20714
|
+
function nearestSurvivingAncestor(missingParentId, adapter, checkpoint) {
|
|
20715
|
+
const visited = /* @__PURE__ */ new Set();
|
|
20716
|
+
let current = missingParentId;
|
|
20717
|
+
while (current !== null && !visited.has(current)) {
|
|
20718
|
+
if (adapter.activityExists(current)) return current;
|
|
20719
|
+
visited.add(current);
|
|
20720
|
+
const saved = checkpoint.checkpointActivity(current);
|
|
20721
|
+
if (!saved || isRootParent(saved.parentId)) return null;
|
|
20722
|
+
current = String(saved.parentId);
|
|
20723
|
+
}
|
|
20724
|
+
return null;
|
|
20725
|
+
}
|
|
20726
|
+
function reparentOrphans(adapter, checkpoint, touchedIds) {
|
|
20727
|
+
const conflicts = [];
|
|
20728
|
+
for (const activity of adapter.getAllActivities()) {
|
|
20729
|
+
if (isRootParent(activity.parentId)) continue;
|
|
20730
|
+
if (adapter.activityExists(String(activity.parentId))) continue;
|
|
20731
|
+
const previousParentId = String(activity.parentId);
|
|
20732
|
+
const adoptiveParentId = nearestSurvivingAncestor(
|
|
20733
|
+
previousParentId,
|
|
20734
|
+
adapter,
|
|
20735
|
+
checkpoint
|
|
20736
|
+
);
|
|
20737
|
+
adapter.setActivityField(
|
|
20738
|
+
activity.id,
|
|
20739
|
+
ACTIVITY_PROPERTY.PARENT,
|
|
20740
|
+
adoptiveParentId
|
|
20741
|
+
);
|
|
20742
|
+
touchedIds.add(String(activity.id));
|
|
20743
|
+
if (adoptiveParentId !== null) touchedIds.add(adoptiveParentId);
|
|
20744
|
+
conflicts.push({
|
|
20745
|
+
entity: PERSISTENCE_REBASE_ENTITY.ACTIVITY,
|
|
20746
|
+
id: String(activity.id),
|
|
20747
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.PARENT_REMOVED,
|
|
20748
|
+
field: ACTIVITY_PROPERTY.PARENT,
|
|
20749
|
+
mine: previousParentId,
|
|
20750
|
+
theirs: adoptiveParentId ?? ROOT_PARENT_ID
|
|
20751
|
+
});
|
|
20752
|
+
}
|
|
20753
|
+
return conflicts;
|
|
20754
|
+
}
|
|
20755
|
+
function applyLinkPlan(plan, beforeLinks, adapter) {
|
|
20756
|
+
const changes = [
|
|
20757
|
+
...buildLinkDeletions(new Map(beforeLinks))
|
|
20758
|
+
];
|
|
20759
|
+
for (const link of plan.createLinks) {
|
|
20760
|
+
if (adapter.getLink(link.id)) continue;
|
|
20761
|
+
adapter.addLink({ ...link, freeSlackHours: null, isCritical: false });
|
|
20762
|
+
const after = adapter.getLink(link.id);
|
|
20763
|
+
if (!after) continue;
|
|
20764
|
+
changes.push({
|
|
20765
|
+
id: String(link.id),
|
|
20766
|
+
kind: ENTITY_CHANGE_KIND.CREATED,
|
|
20767
|
+
fields: {
|
|
20768
|
+
source: { before: void 0, after: String(after.source) },
|
|
20769
|
+
target: { before: void 0, after: String(after.target) },
|
|
20770
|
+
type: { before: void 0, after: after.type },
|
|
20771
|
+
lag: { before: void 0, after: Number(after.lag) }
|
|
20772
|
+
},
|
|
20773
|
+
after: snapshotToLink(after)
|
|
20774
|
+
});
|
|
20775
|
+
}
|
|
20776
|
+
for (const merge of plan.updateLinks) {
|
|
20777
|
+
const before = adapter.getLink(merge.linkId);
|
|
20778
|
+
if (!before) continue;
|
|
20779
|
+
const beforeView = snapshotToLink(before);
|
|
20780
|
+
const fields = {};
|
|
20781
|
+
for (const [field, value] of Object.entries(merge.fields)) {
|
|
20782
|
+
adapter.setLinkFieldDynamic(merge.linkId, field, value);
|
|
20783
|
+
fields[field] = { before: Reflect.get(beforeView, field), after: value };
|
|
20784
|
+
}
|
|
20785
|
+
const after = adapter.getLink(merge.linkId);
|
|
20786
|
+
if (!after) continue;
|
|
20787
|
+
changes.push({
|
|
20788
|
+
id: String(merge.linkId),
|
|
20789
|
+
kind: ENTITY_CHANGE_KIND.UPDATED,
|
|
20790
|
+
fields,
|
|
20791
|
+
after: snapshotToLink(after)
|
|
20792
|
+
});
|
|
20793
|
+
}
|
|
20794
|
+
return changes;
|
|
20795
|
+
}
|
|
20796
|
+
function dropLinksWithoutEndpoints(adapter, changes) {
|
|
20797
|
+
const conflicts = [];
|
|
20798
|
+
for (const link of adapter.getAllLinks()) {
|
|
20799
|
+
const missingEndpoint = [link.source, link.target].find(
|
|
20800
|
+
(activityId) => !adapter.activityExists(String(activityId))
|
|
20801
|
+
);
|
|
20802
|
+
if (missingEndpoint === void 0) continue;
|
|
20803
|
+
const linkId = String(link.id);
|
|
20804
|
+
const before = { ...link };
|
|
20805
|
+
const sourceIsMissing = String(link.source) === String(missingEndpoint);
|
|
20806
|
+
adapter.removeLink(linkId);
|
|
20807
|
+
changes.push(...buildLinkDeletions(/* @__PURE__ */ new Map([[linkId, before]])));
|
|
20808
|
+
conflicts.push({
|
|
20809
|
+
entity: PERSISTENCE_REBASE_ENTITY.LINK,
|
|
20810
|
+
id: linkId,
|
|
20811
|
+
kind: PERSISTENCE_REBASE_CONFLICT_KIND.DELETED_REMOTELY,
|
|
20812
|
+
field: sourceIsMissing ? LINK_PROPERTY.SOURCE : LINK_PROPERTY.TARGET,
|
|
20813
|
+
mine: snapshotToLink(before),
|
|
20814
|
+
theirs: null
|
|
20815
|
+
});
|
|
20816
|
+
}
|
|
20817
|
+
return conflicts;
|
|
20818
|
+
}
|
|
20819
|
+
|
|
20820
|
+
// src/baselines/apply-baseline-points.ts
|
|
20821
|
+
function applyBaselinePoints(adapter, points, reporter, recordDiagnostic = noopLoadDiagnosticRecorder) {
|
|
20822
|
+
const idByProplanner = /* @__PURE__ */ new Map();
|
|
20823
|
+
adapter.forEachActivity((a, id) => {
|
|
20824
|
+
const pid = a.proplannerId;
|
|
20825
|
+
if (typeof pid === "number") idByProplanner.set(pid, id);
|
|
20826
|
+
});
|
|
20827
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
20828
|
+
for (const p of points) {
|
|
20829
|
+
const activityId = p.activityId;
|
|
20830
|
+
if (typeof activityId !== "number") {
|
|
20831
|
+
recordDiagnostic(
|
|
20832
|
+
LOAD_DIAGNOSTIC_CODE.INVALID_BASELINE_POINT,
|
|
20833
|
+
String(activityId ?? "unknown")
|
|
20834
|
+
);
|
|
20835
|
+
continue;
|
|
20836
|
+
}
|
|
20837
|
+
const coreId = idByProplanner.get(activityId);
|
|
20838
|
+
if (coreId === void 0) {
|
|
20839
|
+
recordDiagnostic(
|
|
20840
|
+
LOAD_DIAGNOSTIC_CODE.INVALID_BASELINE_POINT,
|
|
20841
|
+
String(activityId)
|
|
20842
|
+
);
|
|
20843
|
+
continue;
|
|
20844
|
+
}
|
|
20845
|
+
const arr = grouped.get(coreId);
|
|
20846
|
+
if (arr) arr.push(p);
|
|
20847
|
+
else grouped.set(coreId, [p]);
|
|
20848
|
+
}
|
|
20849
|
+
const changed2 = [];
|
|
20850
|
+
for (const [coreId, group] of grouped) {
|
|
20851
|
+
const isMilestone = adapter.getActivity(coreId)?.type === ACTIVITY_TYPE.MILESTONE;
|
|
20852
|
+
adapter.setActivityField(
|
|
20853
|
+
coreId,
|
|
20854
|
+
"baselinePoints",
|
|
20855
|
+
group.map(
|
|
20856
|
+
(point) => normalizeBaselinePoint(
|
|
20857
|
+
point,
|
|
20858
|
+
reporter,
|
|
20859
|
+
{
|
|
20860
|
+
record: recordDiagnostic,
|
|
20861
|
+
sampleId: coreId
|
|
20862
|
+
},
|
|
20863
|
+
isMilestone
|
|
20864
|
+
)
|
|
20865
|
+
).filter((point) => point !== null)
|
|
20866
|
+
);
|
|
20867
|
+
changed2.push(coreId);
|
|
20868
|
+
}
|
|
20869
|
+
adapter.forEachActivity((a, id) => {
|
|
20870
|
+
if (grouped.has(id)) return;
|
|
20871
|
+
const existing = a.baselinePoints;
|
|
20872
|
+
if (Array.isArray(existing) && existing.length > 0) {
|
|
20873
|
+
adapter.setActivityField(id, "baselinePoints", []);
|
|
20874
|
+
changed2.push(id);
|
|
20875
|
+
}
|
|
20876
|
+
});
|
|
20877
|
+
return changed2;
|
|
20878
|
+
}
|
|
20879
|
+
|
|
20880
|
+
// src/baselines/baseline-snapshot.ts
|
|
20881
|
+
function buildBaselineSnapshot(activity) {
|
|
20882
|
+
const active = getActiveBaseline(activity);
|
|
20883
|
+
if (!active) return null;
|
|
20884
|
+
return {
|
|
20885
|
+
startDate: active.startDate,
|
|
20886
|
+
endDate: active.endDate,
|
|
20887
|
+
durationDays: active.durationDays,
|
|
20888
|
+
cost: active.cost,
|
|
20889
|
+
workHours: active.workHours
|
|
20890
|
+
};
|
|
20891
|
+
}
|
|
20892
|
+
function emitBaselineSnapshotColumns(state) {
|
|
20893
|
+
const changed2 = [];
|
|
20894
|
+
state.forEachActivity((activity, id) => {
|
|
20895
|
+
const snapshot = buildBaselineSnapshot(activity);
|
|
20896
|
+
if (!snapshot) {
|
|
20897
|
+
if (activity.baselineSnapshot !== null) {
|
|
20898
|
+
state.setActivityField(id, "baselineSnapshot", null);
|
|
20899
|
+
changed2.push(id);
|
|
20900
|
+
}
|
|
20901
|
+
return;
|
|
20902
|
+
}
|
|
20903
|
+
state.setActivityField(id, "baselineSnapshot", snapshot);
|
|
20904
|
+
changed2.push(id);
|
|
20905
|
+
});
|
|
20906
|
+
return changed2;
|
|
20907
|
+
}
|
|
20908
|
+
|
|
20909
|
+
// src/baselines/apply-baseline-overlay.ts
|
|
20910
|
+
function applyBaselineOverlay(state, points, now, reporter, defaultBaseCalendarId = null, recordDiagnostic = noopLoadDiagnosticRecorder) {
|
|
20911
|
+
const changed2 = /* @__PURE__ */ new Set();
|
|
20912
|
+
for (const id of applyBaselinePoints(
|
|
20913
|
+
state,
|
|
20914
|
+
points,
|
|
20915
|
+
reporter,
|
|
20916
|
+
recordDiagnostic
|
|
20917
|
+
)) {
|
|
20918
|
+
changed2.add(id);
|
|
20919
|
+
}
|
|
20920
|
+
if (now) {
|
|
20921
|
+
for (const id of runExpectedProgressBase(state, now, defaultBaseCalendarId))
|
|
20922
|
+
changed2.add(id);
|
|
20923
|
+
}
|
|
20924
|
+
for (const id of emitBaselineSnapshotColumns(state)) changed2.add(id);
|
|
20925
|
+
for (const id of emitRealCost(state)) changed2.add(id);
|
|
20926
|
+
return [...changed2];
|
|
20927
|
+
}
|
|
20928
|
+
|
|
20929
|
+
// src/propagations/downward/recompute-all-ponderators.ts
|
|
20930
|
+
function recomputeAllPonderators(criterion, adapter) {
|
|
20931
|
+
const basisById = /* @__PURE__ */ new Map();
|
|
20932
|
+
adapter.forEachActivity((_a, id) => {
|
|
20933
|
+
adapter.setActivityField(id, COLUMN.PONDERATOR, 0);
|
|
20934
|
+
});
|
|
20935
|
+
adapter.forEachActivity((_a, id) => {
|
|
20936
|
+
if (adapter.getChildren(id).length > 0) {
|
|
20937
|
+
recomputePonderatorsForParent(String(id), criterion, adapter, basisById);
|
|
20938
|
+
}
|
|
20939
|
+
});
|
|
20940
|
+
}
|
|
20941
|
+
|
|
20942
|
+
// src/dispatch/shared/baseline-weighted-fields.ts
|
|
20943
|
+
function recomputeBaselineWeightedFields(deps, criterion) {
|
|
20944
|
+
recomputeAllPonderators(criterion, deps.adapter);
|
|
20945
|
+
recomputeAllProgressRollup(deps.adapter, criterion);
|
|
20946
|
+
if (deps.now) {
|
|
20947
|
+
runExpectedProgressBase(
|
|
20948
|
+
deps.adapter,
|
|
20949
|
+
deps.now,
|
|
20950
|
+
deps.defaultBaseCalendarId ?? null
|
|
20951
|
+
);
|
|
20952
|
+
applyExpectedProgressLive(deps.adapter, deps.now);
|
|
20953
|
+
}
|
|
20954
|
+
emitRealCost(deps.adapter);
|
|
20955
|
+
if (deps.now) applyStatusPass(deps.adapter, deps.sector.statusCriteria);
|
|
20956
|
+
}
|
|
20957
|
+
|
|
20958
|
+
// src/dispatch/handlers/baseline-apply.ts
|
|
20959
|
+
function activeBaselineSignature(deps) {
|
|
20960
|
+
return JSON.stringify(
|
|
20961
|
+
deps.adapter.getAllIds().map((id) => {
|
|
20962
|
+
const point = deps.adapter.getActivity(id)?.baselinePoints.find((candidate) => candidate.isActiveVersion);
|
|
20963
|
+
return point ? [
|
|
20964
|
+
String(id),
|
|
20965
|
+
point.versionId,
|
|
20966
|
+
point.startDate?.getTime() ?? null,
|
|
20967
|
+
point.endDate?.getTime() ?? null,
|
|
20968
|
+
point.durationDays,
|
|
20969
|
+
point.cost,
|
|
20970
|
+
point.workHours,
|
|
20971
|
+
point.baseCalendarId,
|
|
20972
|
+
point.hoursPerDay,
|
|
20973
|
+
point.hoursPerWeek
|
|
20974
|
+
] : [String(id), null];
|
|
20975
|
+
})
|
|
20976
|
+
);
|
|
20977
|
+
}
|
|
20978
|
+
async function dispatchBaselineApply(action, deps) {
|
|
20979
|
+
const beforeSnap = await snapshotActivities(
|
|
20980
|
+
deps.adapter,
|
|
20981
|
+
new Set(deps.adapter.getAllIds())
|
|
20982
|
+
);
|
|
20983
|
+
const activeBaselineBefore = activeBaselineSignature(deps);
|
|
20984
|
+
const changedIds = applyBaselineOverlay(
|
|
20985
|
+
deps.adapter,
|
|
20986
|
+
action.points,
|
|
20987
|
+
deps.now,
|
|
20988
|
+
deps.reporter,
|
|
20989
|
+
deps.defaultBaseCalendarId ?? null,
|
|
20990
|
+
void 0
|
|
20991
|
+
);
|
|
20992
|
+
applyBaselineVariancePass(deps.adapter, deps.sector.hoursPerDay);
|
|
20993
|
+
applyRoleBaselineVariancePass(deps.adapter, deps.sector.hoursPerDay);
|
|
20994
|
+
if (activeBaselineSignature(deps) !== activeBaselineBefore) {
|
|
20995
|
+
recomputeBaselineWeightedFields(
|
|
20996
|
+
deps,
|
|
20997
|
+
deps.sector.activityCreter ?? ACTIVITY_PONDERATOR_CRITERION.DURATION
|
|
20998
|
+
);
|
|
20999
|
+
}
|
|
21000
|
+
return {
|
|
21001
|
+
ok: true,
|
|
21002
|
+
changes: await assembleChangeSet(deps.adapter, {
|
|
21003
|
+
source: action,
|
|
21004
|
+
beforeSnap,
|
|
21005
|
+
touchedIds: changedIds,
|
|
21006
|
+
scheduledIds: [],
|
|
21007
|
+
hoursPerDay: deps.sector.hoursPerDay
|
|
21008
|
+
})
|
|
21009
|
+
};
|
|
21010
|
+
}
|
|
21011
|
+
|
|
21012
|
+
// src/dispatch/handlers/ponderator-criterion-set.ts
|
|
21013
|
+
function isValidCriterion(value) {
|
|
21014
|
+
return Object.values(ACTIVITY_PONDERATOR_CRITERION).includes(
|
|
21015
|
+
value
|
|
21016
|
+
);
|
|
21017
|
+
}
|
|
21018
|
+
async function dispatchPonderatorCriterionSet(action, deps) {
|
|
21019
|
+
if (!isValidCriterion(action.criterion)) {
|
|
21020
|
+
return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
|
|
21021
|
+
}
|
|
21022
|
+
const allIds = new Set(deps.adapter.getAllIds());
|
|
21023
|
+
const beforeSnap = await snapshotActivities(deps.adapter, allIds);
|
|
21024
|
+
recomputeBaselineWeightedFields(deps, action.criterion);
|
|
21025
|
+
deps.sector.activityCreter = action.criterion;
|
|
21026
|
+
return {
|
|
21027
|
+
ok: true,
|
|
21028
|
+
changes: await assembleChangeSet(deps.adapter, {
|
|
21029
|
+
source: action,
|
|
21030
|
+
beforeSnap,
|
|
21031
|
+
touchedIds: allIds,
|
|
21032
|
+
scheduledIds: [],
|
|
21033
|
+
hoursPerDay: deps.sector.hoursPerDay
|
|
21034
|
+
})
|
|
21035
|
+
};
|
|
21036
|
+
}
|
|
21037
|
+
|
|
21038
|
+
// src/dispatch/handlers/status-criteria-set.ts
|
|
21039
|
+
async function dispatchStatusCriteriaSet(action, deps) {
|
|
21040
|
+
const resolved = resolveStatusCriteria(action.criteria);
|
|
21041
|
+
const allIds = new Set(deps.adapter.getAllIds());
|
|
21042
|
+
const beforeSnap = await snapshotActivities(deps.adapter, allIds);
|
|
21043
|
+
const changedIds = applyStatusPass(deps.adapter, resolved);
|
|
21044
|
+
deps.sector.statusCriteria = resolved;
|
|
21045
|
+
return {
|
|
21046
|
+
ok: true,
|
|
21047
|
+
changes: await assembleChangeSet(deps.adapter, {
|
|
21048
|
+
source: action,
|
|
21049
|
+
beforeSnap,
|
|
21050
|
+
touchedIds: changedIds,
|
|
21051
|
+
scheduledIds: [],
|
|
21052
|
+
hoursPerDay: deps.sector.hoursPerDay
|
|
21053
|
+
})
|
|
21054
|
+
};
|
|
21055
|
+
}
|
|
21056
|
+
|
|
21057
|
+
// src/dispatch/handlers/calendar-reconcile.ts
|
|
21058
|
+
async function dispatchCalendarReconcile(action, options, deps) {
|
|
21059
|
+
const beforeSnap = /* @__PURE__ */ new Map();
|
|
21060
|
+
const touchedIds = /* @__PURE__ */ new Set();
|
|
21061
|
+
for (const activity of deps.adapter.getAllActivities()) {
|
|
21062
|
+
if (!shouldReconcile(activity, deps)) continue;
|
|
21063
|
+
const calendar = deps.ctx.calendars.getCalendar(activity.calendarId);
|
|
21064
|
+
if (!calendar) continue;
|
|
21065
|
+
const startReference = calendar.isWorkTime(
|
|
21066
|
+
activity.startDate,
|
|
21067
|
+
CALENDAR_UNIT.HOUR
|
|
21068
|
+
) ? startOfUtcDay4(activity.startDate) : activity.startDate;
|
|
21069
|
+
const startDate = calendar.getClosestWorkTime({
|
|
21070
|
+
date: startReference,
|
|
21071
|
+
direction: WORK_TIME_DIRECTION.FUTURE,
|
|
21072
|
+
unit: CALENDAR_UNIT.HOUR
|
|
21073
|
+
});
|
|
21074
|
+
const endDate = calendar.calculateEndDate(
|
|
21075
|
+
startDate,
|
|
21076
|
+
activity.durationHours
|
|
21077
|
+
);
|
|
21078
|
+
if (sameInstant(startDate, activity.startDate) && sameInstant(endDate, activity.endDate)) {
|
|
21079
|
+
continue;
|
|
21080
|
+
}
|
|
21081
|
+
const activityId = String(activity.id);
|
|
21082
|
+
beforeSnap.set(activityId, structuredCloneActivity(activity));
|
|
21083
|
+
touchedIds.add(activityId);
|
|
21084
|
+
deps.adapter.setActivityFields(activityId, { startDate, endDate });
|
|
21085
|
+
}
|
|
21086
|
+
const { scheduledIds } = await runPostMutation(
|
|
21087
|
+
{
|
|
21088
|
+
adapter: deps.adapter,
|
|
21089
|
+
scheduler: deps.scheduler,
|
|
21090
|
+
sector: deps.sector,
|
|
21091
|
+
defaultBaseCalendarId: deps.defaultBaseCalendarId,
|
|
21092
|
+
runAutomaticP6Schedule: deps.runAutomaticP6Schedule
|
|
21093
|
+
},
|
|
21094
|
+
{
|
|
21095
|
+
action,
|
|
21096
|
+
autoscheduleFrom: AUTOSCHEDULE_SCOPE.ROOTS,
|
|
21097
|
+
recomputeParentsFrom: touchedIds,
|
|
19175
21098
|
now: deps.now,
|
|
19176
21099
|
options
|
|
19177
21100
|
}
|
|
@@ -19869,6 +21792,9 @@ async function performDispatch(action, options, deps) {
|
|
|
19869
21792
|
if (action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE) {
|
|
19870
21793
|
return dispatchPersistenceAcknowledge(action, deps);
|
|
19871
21794
|
}
|
|
21795
|
+
if (action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_REBASE) {
|
|
21796
|
+
return dispatchPersistenceRebase(action, options, deps);
|
|
21797
|
+
}
|
|
19872
21798
|
if (action.kind === DISPATCH_ACTION_KIND.BASELINE_APPLY) {
|
|
19873
21799
|
return dispatchBaselineApply(action, deps);
|
|
19874
21800
|
}
|
|
@@ -19975,18 +21901,63 @@ function selectionDeps(deps) {
|
|
|
19975
21901
|
}
|
|
19976
21902
|
|
|
19977
21903
|
// src/columns/shared/field-types.ts
|
|
19978
|
-
var
|
|
21904
|
+
var DATE_FIELDS2 = /* @__PURE__ */ new Set([
|
|
19979
21905
|
"startDate",
|
|
19980
21906
|
"endDate",
|
|
19981
21907
|
"constraintDate"
|
|
19982
21908
|
]);
|
|
19983
21909
|
function coerceActivityFieldValue(field, value) {
|
|
19984
|
-
if (
|
|
21910
|
+
if (DATE_FIELDS2.has(field) && typeof value === "string") {
|
|
19985
21911
|
return new Date(value);
|
|
19986
21912
|
}
|
|
19987
21913
|
return value;
|
|
19988
21914
|
}
|
|
19989
21915
|
|
|
21916
|
+
// src/calendar/closest-past-work-time.ts
|
|
21917
|
+
var MS_PER_MINUTE = 60 * 1e3;
|
|
21918
|
+
var MS_PER_DAY = 24 * 60 * MS_PER_MINUTE;
|
|
21919
|
+
var MINUTES_PER_HOUR = 60;
|
|
21920
|
+
var MAX_LOOKBACK_DAYS = 3660;
|
|
21921
|
+
var LAST_MINUTE_OF_DAY_MS = MS_PER_DAY - MS_PER_MINUTE;
|
|
21922
|
+
var WINDOW_SEPARATOR = "-";
|
|
21923
|
+
var TIME_SEPARATOR = ":";
|
|
21924
|
+
function closestPastWorkTime(calendar, date2, unit) {
|
|
21925
|
+
if (calendar.isWorkTime({ date: date2, unit })) return date2;
|
|
21926
|
+
const dayStart = utcDayStart(date2);
|
|
21927
|
+
if (unit !== CALENDAR_UNIT.DAY) {
|
|
21928
|
+
const elapsed = date2.getTime() - dayStart;
|
|
21929
|
+
const sameDayEnd = latestWindowEnd(calendar, dayStart, elapsed);
|
|
21930
|
+
if (sameDayEnd !== null) return new Date(dayStart + sameDayEnd);
|
|
21931
|
+
}
|
|
21932
|
+
const searchesFromWorkingDay = calendar.getWorkHours(date2).length > 0;
|
|
21933
|
+
for (let back = 1; back <= MAX_LOOKBACK_DAYS; back++) {
|
|
21934
|
+
const candidate = dayStart - back * MS_PER_DAY;
|
|
21935
|
+
const lastEnd = latestWindowEnd(calendar, candidate, MS_PER_DAY);
|
|
21936
|
+
if (lastEnd === null) continue;
|
|
21937
|
+
if (unit !== CALENDAR_UNIT.DAY) {
|
|
21938
|
+
const end = searchesFromWorkingDay ? Math.min(lastEnd, LAST_MINUTE_OF_DAY_MS) : lastEnd;
|
|
21939
|
+
return new Date(candidate + end);
|
|
21940
|
+
}
|
|
21941
|
+
return new Date(candidate + MS_PER_DAY);
|
|
21942
|
+
}
|
|
21943
|
+
throw new Error(
|
|
21944
|
+
`No working day found within ${MAX_LOOKBACK_DAYS} days before ${date2.toISOString()}`
|
|
21945
|
+
);
|
|
21946
|
+
}
|
|
21947
|
+
function utcDayStart(date2) {
|
|
21948
|
+
return Date.UTC(date2.getUTCFullYear(), date2.getUTCMonth(), date2.getUTCDate());
|
|
21949
|
+
}
|
|
21950
|
+
function latestWindowEnd(calendar, dayStart, atOrBefore) {
|
|
21951
|
+
const ends = calendar.getWorkHours(new Date(dayStart)).map(windowEndMs).filter((end) => end <= atOrBefore);
|
|
21952
|
+
return ends.length ? Math.max(...ends) : null;
|
|
21953
|
+
}
|
|
21954
|
+
function windowEndMs(window2) {
|
|
21955
|
+
const end = window2.split(WINDOW_SEPARATOR)[1] ?? "";
|
|
21956
|
+
const [hours = "0", minutes = "0"] = end.split(TIME_SEPARATOR);
|
|
21957
|
+
const endMs = (Number(hours) * MINUTES_PER_HOUR + Number(minutes)) * MS_PER_MINUTE;
|
|
21958
|
+
return endMs;
|
|
21959
|
+
}
|
|
21960
|
+
|
|
19990
21961
|
// src/calendar/adapter.ts
|
|
19991
21962
|
var WORK_CALENDAR_UNIT = { MINUTE: "minute" };
|
|
19992
21963
|
function createWorkCalendarAdapter(instance, options = {}) {
|
|
@@ -20002,6 +21973,9 @@ function createWorkCalendarAdapter(instance, options = {}) {
|
|
|
20002
21973
|
function wrapWorkCalendar(calendar, defaultUnit) {
|
|
20003
21974
|
return {
|
|
20004
21975
|
getClosestWorkTime(query) {
|
|
21976
|
+
if (query.direction === WORK_TIME_DIRECTION.PAST) {
|
|
21977
|
+
return closestPastWorkTime(calendar, query.date, query.unit);
|
|
21978
|
+
}
|
|
20005
21979
|
return calendar.getClosestWorkTime({
|
|
20006
21980
|
date: query.date,
|
|
20007
21981
|
dir: query.direction,
|
|
@@ -22898,7 +24872,7 @@ var CalendarCalculator = class {
|
|
|
22898
24872
|
this.reader = setup.reader;
|
|
22899
24873
|
}
|
|
22900
24874
|
resolveCalendar(task) {
|
|
22901
|
-
const calendarId =
|
|
24875
|
+
const calendarId = resolveCalendarId2(task);
|
|
22902
24876
|
return this.reader.getCalendar(calendarId ?? DEFAULT_CALENDAR_ID);
|
|
22903
24877
|
}
|
|
22904
24878
|
getClosestWorkTime(params) {
|
|
@@ -22955,14 +24929,14 @@ function memoize(cache, key, compute) {
|
|
|
22955
24929
|
return result;
|
|
22956
24930
|
}
|
|
22957
24931
|
var DEFAULT_CALENDAR_KEY = "__default__";
|
|
22958
|
-
function
|
|
24932
|
+
function resolveCalendarId2(task) {
|
|
22959
24933
|
if (!task) return null;
|
|
22960
24934
|
const calendarId = task.calendarId;
|
|
22961
24935
|
if (calendarId == null) return null;
|
|
22962
24936
|
return String(calendarId);
|
|
22963
24937
|
}
|
|
22964
24938
|
function calendarKey(task) {
|
|
22965
|
-
return
|
|
24939
|
+
return resolveCalendarId2(task) ?? DEFAULT_CALENDAR_KEY;
|
|
22966
24940
|
}
|
|
22967
24941
|
|
|
22968
24942
|
// src/internal/state/hierarchy-index.ts
|
|
@@ -24247,43 +26221,6 @@ function computeProjectWorkHours(calendars) {
|
|
|
24247
26221
|
return { startHour: earliestTime(starts), endHour: latestTime(ends) };
|
|
24248
26222
|
}
|
|
24249
26223
|
|
|
24250
|
-
// src/boundary/backend/constants.ts
|
|
24251
|
-
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
24252
|
-
var EXCEPTION_DATE_FORMAT_REGEX = /^(\d{4})[/-](\d{1,2})[/-](\d{1,2})/;
|
|
24253
|
-
var WEEKDAYS_PER_WEEK = 7;
|
|
24254
|
-
var EXCEPTION_EVERY_TYPE = {
|
|
24255
|
-
DAILY: "DAILY",
|
|
24256
|
-
WEEKLY: "WEEKLY",
|
|
24257
|
-
MONTHLY: "MONTHLY",
|
|
24258
|
-
YEARLY: "YEARLY"
|
|
24259
|
-
};
|
|
24260
|
-
var MONTHS_PER_YEAR = 12;
|
|
24261
|
-
var CALENDAR_FALLBACK_WORKING_DAYS = [false, true, true, true, true, true, false];
|
|
24262
|
-
var WORKING_DAY_TRUE_VALUES = /* @__PURE__ */ new Set([
|
|
24263
|
-
"1",
|
|
24264
|
-
"true"
|
|
24265
|
-
]);
|
|
24266
|
-
var DEFAULT_ACTIVITY_CRETER = ACTIVITY_PONDERATOR_CRITERION.DURATION;
|
|
24267
|
-
var DEFAULT_CONSTRAINT_TYPE = CONSTRAINT_TYPE.ASAP;
|
|
24268
|
-
var CONSTRAINT_LABEL_MAP = {
|
|
24269
|
-
ASAP: CONSTRAINT_TYPE.ASAP,
|
|
24270
|
-
ALAP: CONSTRAINT_TYPE.ALAP,
|
|
24271
|
-
SNET: CONSTRAINT_TYPE.SNET,
|
|
24272
|
-
SNLT: CONSTRAINT_TYPE.SNLT,
|
|
24273
|
-
FNET: CONSTRAINT_TYPE.FNET,
|
|
24274
|
-
FNLT: CONSTRAINT_TYPE.FNLT,
|
|
24275
|
-
MSO: CONSTRAINT_TYPE.MSO,
|
|
24276
|
-
MFO: CONSTRAINT_TYPE.MFO,
|
|
24277
|
-
"AS SOON AS POSSIBLE": CONSTRAINT_TYPE.ASAP,
|
|
24278
|
-
"AS LATE AS POSSIBLE": CONSTRAINT_TYPE.ALAP,
|
|
24279
|
-
"START NO EARLIER THAN": CONSTRAINT_TYPE.SNET,
|
|
24280
|
-
"START NO LATER THAN": CONSTRAINT_TYPE.SNLT,
|
|
24281
|
-
"FINISH NO EARLIER THAN": CONSTRAINT_TYPE.FNET,
|
|
24282
|
-
"FINISH NO LATER THAN": CONSTRAINT_TYPE.FNLT,
|
|
24283
|
-
"MUST START ON": CONSTRAINT_TYPE.MSO,
|
|
24284
|
-
"MUST FINISH ON": CONSTRAINT_TYPE.MFO
|
|
24285
|
-
};
|
|
24286
|
-
|
|
24287
26224
|
// src/boundary/backend/normalize-calendar.ts
|
|
24288
26225
|
function normalizeCalendars(schedulingCalendars, sectorCalendars, rawBaseCalendars, exceptionRange = null) {
|
|
24289
26226
|
const hasSchedulingCalendars = schedulingCalendars !== void 0 && schedulingCalendars.length > 0;
|
|
@@ -24298,10 +26235,10 @@ function normalizeCalendars(schedulingCalendars, sectorCalendars, rawBaseCalenda
|
|
|
24298
26235
|
calendars,
|
|
24299
26236
|
baseCalendars,
|
|
24300
26237
|
knownCalendarIds: new Set(calendars.map((calendar) => calendar.id)),
|
|
24301
|
-
defaultCalendarId:
|
|
26238
|
+
defaultCalendarId: pickDefaultCalendarId2(calendars)
|
|
24302
26239
|
};
|
|
24303
26240
|
}
|
|
24304
|
-
function
|
|
26241
|
+
function pickDefaultCalendarId2(calendars) {
|
|
24305
26242
|
let firstCalendarId = null;
|
|
24306
26243
|
for (const calendar of calendars) {
|
|
24307
26244
|
firstCalendarId ??= calendar.id;
|
|
@@ -24366,14 +26303,24 @@ function parseExceptionDays(exceptions, range) {
|
|
|
24366
26303
|
}
|
|
24367
26304
|
function expandRangeToUtcKeys(from, to, range, everyType) {
|
|
24368
26305
|
const fromTs = parseExceptionDate(from);
|
|
24369
|
-
const
|
|
24370
|
-
if (fromTs === null ||
|
|
26306
|
+
const toDay = parseExceptionDate(to);
|
|
26307
|
+
if (fromTs === null || toDay === null) return [];
|
|
26308
|
+
const toTs = endsAtMidnightAfterStart(from, to, fromTs, toDay) ? toDay - DAY_MS : toDay;
|
|
24371
26309
|
if (range && (toTs < range.start || fromTs > range.end)) return [];
|
|
24372
26310
|
const first = range ? Math.max(fromTs, range.start) : fromTs;
|
|
24373
26311
|
const last = range ? Math.min(toTs, range.end) : toTs;
|
|
24374
26312
|
const timestamps = steppedTimestamps(fromTs, first, last, everyType);
|
|
24375
26313
|
return timestamps.map(String);
|
|
24376
26314
|
}
|
|
26315
|
+
function endsAtMidnightAfterStart(from, to, fromDay, toDay) {
|
|
26316
|
+
if (toDay <= fromDay) return false;
|
|
26317
|
+
return isMidnight(to) && !isMidnight(from);
|
|
26318
|
+
}
|
|
26319
|
+
function isMidnight(raw) {
|
|
26320
|
+
if (typeof raw !== "string") return false;
|
|
26321
|
+
const match = EXCEPTION_TIME_FORMAT_REGEX.exec(raw.trim());
|
|
26322
|
+
return match !== null && Number(match[1]) === 0 && Number(match[2]) === 0;
|
|
26323
|
+
}
|
|
24377
26324
|
function steppedTimestamps(anchor, first, last, everyType) {
|
|
24378
26325
|
switch (everyType) {
|
|
24379
26326
|
case EXCEPTION_EVERY_TYPE.DAILY:
|
|
@@ -24548,7 +26495,7 @@ function buildPipelineContext(adapter, options) {
|
|
|
24548
26495
|
return {
|
|
24549
26496
|
calendars: adapter.calendarReader,
|
|
24550
26497
|
hasCalendar: (calendarId) => options.calendars.has(calendarId),
|
|
24551
|
-
getDefaultCalendarId: () =>
|
|
26498
|
+
getDefaultCalendarId: () => pickDefaultCalendarId2(options.calendars.values()),
|
|
24552
26499
|
hierarchy: createStateBackedHierarchyReader(adapter),
|
|
24553
26500
|
activityReader: createStateBackedActivityReader(adapter),
|
|
24554
26501
|
dateFormat: options.dateFormat ?? "YYYY-MM-DD",
|
|
@@ -24665,7 +26612,8 @@ function mergeCoalesced(top, next) {
|
|
|
24665
26612
|
var DISPATCH_HISTORY_POLICY = {
|
|
24666
26613
|
RECORD: "record",
|
|
24667
26614
|
SKIP: "skip",
|
|
24668
|
-
CHECKPOINT_ON_SUCCESS: "checkpoint-on-success"
|
|
26615
|
+
CHECKPOINT_ON_SUCCESS: "checkpoint-on-success",
|
|
26616
|
+
REBASE_ON_SUCCESS: "rebase-on-success"
|
|
24669
26617
|
};
|
|
24670
26618
|
var CHANGE_SET_SIDE = {
|
|
24671
26619
|
BEFORE: "before",
|
|
@@ -24675,6 +26623,9 @@ function getDispatchHistoryPolicy(action) {
|
|
|
24675
26623
|
if (action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE) {
|
|
24676
26624
|
return DISPATCH_HISTORY_POLICY.CHECKPOINT_ON_SUCCESS;
|
|
24677
26625
|
}
|
|
26626
|
+
if (action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_REBASE) {
|
|
26627
|
+
return DISPATCH_HISTORY_POLICY.REBASE_ON_SUCCESS;
|
|
26628
|
+
}
|
|
24678
26629
|
if (action.kind === DISPATCH_ACTION_KIND.SIR_SYNC || action.kind === DISPATCH_ACTION_KIND.CALENDAR_RECONCILE || action.kind === DISPATCH_ACTION_KIND.ACTIVITY_LOOKAHEAD_SYNC || action.kind === DISPATCH_ACTION_KIND.BASELINE_APPLY || action.kind === DISPATCH_ACTION_KIND.PONDERATOR_CRITERION_SET || action.kind === DISPATCH_ACTION_KIND.STATUS_CRITERIA_SET || action.kind === DISPATCH_ACTION_KIND.ACTIVITY_CLIPBOARD_CLEAR || action.kind === DISPATCH_ACTION_KIND.ACTIVITY_CLIPBOARD_IMPORT || action.kind === DISPATCH_ACTION_KIND.ACTIVITY_COPY || action.kind === DISPATCH_ACTION_KIND.SELECTION_TOGGLE || action.kind === DISPATCH_ACTION_KIND.SELECTION_REPLACE || action.kind === DISPATCH_ACTION_KIND.VISIBILITY_SET || action.kind === DISPATCH_ACTION_KIND.FILTER_SET || action.kind === DISPATCH_ACTION_KIND.SORT_SET || action.kind === DISPATCH_ACTION_KIND.VIEW_ATTRIBUTES_SET || action.kind === DISPATCH_ACTION_KIND.GROUP_SET || action.kind === DISPATCH_ACTION_KIND.WBS_DEPTH_SET) {
|
|
24679
26630
|
return DISPATCH_HISTORY_POLICY.SKIP;
|
|
24680
26631
|
}
|
|
@@ -25090,7 +27041,7 @@ function collectResequencedParents(changes, parentOf) {
|
|
|
25090
27041
|
if (change.kind === ENTITY_CHANGE_KIND.DELETED) {
|
|
25091
27042
|
const deletedFields = change.fields;
|
|
25092
27043
|
if (deletedFields && "parentId" in deletedFields) {
|
|
25093
|
-
touched.add(
|
|
27044
|
+
touched.add(parentKeyOf4(deletedFields.parentId?.before));
|
|
25094
27045
|
}
|
|
25095
27046
|
continue;
|
|
25096
27047
|
}
|
|
@@ -25102,12 +27053,12 @@ function collectResequencedParents(changes, parentOf) {
|
|
|
25102
27053
|
if (!movedPositionally) continue;
|
|
25103
27054
|
addParentOf(activityId);
|
|
25104
27055
|
if ("parentId" in fields) {
|
|
25105
|
-
touched.add(
|
|
27056
|
+
touched.add(parentKeyOf4(fields.parentId?.before));
|
|
25106
27057
|
}
|
|
25107
27058
|
}
|
|
25108
27059
|
return touched;
|
|
25109
27060
|
}
|
|
25110
|
-
function
|
|
27061
|
+
function parentKeyOf4(parentId) {
|
|
25111
27062
|
if (parentId === null || parentId === void 0) return ROOT_PARENT_ID;
|
|
25112
27063
|
return String(parentId);
|
|
25113
27064
|
}
|
|
@@ -25151,154 +27102,6 @@ function buildStateSnapshot(activities, links, calendars, baseCalendars = []) {
|
|
|
25151
27102
|
};
|
|
25152
27103
|
}
|
|
25153
27104
|
|
|
25154
|
-
// src/boundary/save/field-policy.ts
|
|
25155
|
-
var SAVE_FIELD_POLICY = {
|
|
25156
|
-
TRACKED: "tracked",
|
|
25157
|
-
IGNORED: "ignored"
|
|
25158
|
-
};
|
|
25159
|
-
|
|
25160
|
-
// src/boundary/save/activity-changes.ts
|
|
25161
|
-
var ACTIVITY_SAVE_FIELD_POLICY = {
|
|
25162
|
-
id: SAVE_FIELD_POLICY.IGNORED,
|
|
25163
|
-
proplannerId: SAVE_FIELD_POLICY.IGNORED,
|
|
25164
|
-
parentId: SAVE_FIELD_POLICY.TRACKED,
|
|
25165
|
-
name: SAVE_FIELD_POLICY.TRACKED,
|
|
25166
|
-
description: SAVE_FIELD_POLICY.TRACKED,
|
|
25167
|
-
type: SAVE_FIELD_POLICY.TRACKED,
|
|
25168
|
-
startDate: SAVE_FIELD_POLICY.TRACKED,
|
|
25169
|
-
endDate: SAVE_FIELD_POLICY.TRACKED,
|
|
25170
|
-
durationHours: SAVE_FIELD_POLICY.TRACKED,
|
|
25171
|
-
constraintType: SAVE_FIELD_POLICY.TRACKED,
|
|
25172
|
-
constraintDate: SAVE_FIELD_POLICY.TRACKED,
|
|
25173
|
-
autoScheduling: SAVE_FIELD_POLICY.TRACKED,
|
|
25174
|
-
calendarId: SAVE_FIELD_POLICY.TRACKED,
|
|
25175
|
-
assignedDefaultCalendar: SAVE_FIELD_POLICY.IGNORED,
|
|
25176
|
-
dateOrigin: SAVE_FIELD_POLICY.TRACKED,
|
|
25177
|
-
progress: SAVE_FIELD_POLICY.TRACKED,
|
|
25178
|
-
cost: SAVE_FIELD_POLICY.TRACKED,
|
|
25179
|
-
usedCost: SAVE_FIELD_POLICY.TRACKED,
|
|
25180
|
-
realCost: SAVE_FIELD_POLICY.TRACKED,
|
|
25181
|
-
workHours: SAVE_FIELD_POLICY.TRACKED,
|
|
25182
|
-
realWorkHours: SAVE_FIELD_POLICY.TRACKED,
|
|
25183
|
-
ponderator: SAVE_FIELD_POLICY.TRACKED,
|
|
25184
|
-
hasCustomPonderator: SAVE_FIELD_POLICY.TRACKED,
|
|
25185
|
-
correlativeId: SAVE_FIELD_POLICY.TRACKED,
|
|
25186
|
-
uniqueCorrelativeId: SAVE_FIELD_POLICY.TRACKED,
|
|
25187
|
-
customId: SAVE_FIELD_POLICY.TRACKED,
|
|
25188
|
-
subcontractId: SAVE_FIELD_POLICY.TRACKED,
|
|
25189
|
-
isLookahead: SAVE_FIELD_POLICY.TRACKED,
|
|
25190
|
-
hasLookaheadTasks: SAVE_FIELD_POLICY.IGNORED,
|
|
25191
|
-
newActivityIds: SAVE_FIELD_POLICY.TRACKED,
|
|
25192
|
-
hasNewActivities: SAVE_FIELD_POLICY.TRACKED,
|
|
25193
|
-
isNewActivity: SAVE_FIELD_POLICY.IGNORED,
|
|
25194
|
-
pendingRequestIds: SAVE_FIELD_POLICY.IGNORED,
|
|
25195
|
-
responsableIds: SAVE_FIELD_POLICY.TRACKED,
|
|
25196
|
-
tagIds: SAVE_FIELD_POLICY.TRACKED,
|
|
25197
|
-
baselinePoints: SAVE_FIELD_POLICY.IGNORED,
|
|
25198
|
-
baselineSnapshot: SAVE_FIELD_POLICY.IGNORED,
|
|
25199
|
-
recursiveDurationHours: SAVE_FIELD_POLICY.IGNORED,
|
|
25200
|
-
isCritical: SAVE_FIELD_POLICY.IGNORED,
|
|
25201
|
-
freeSlackHours: SAVE_FIELD_POLICY.IGNORED,
|
|
25202
|
-
calendarDuration: SAVE_FIELD_POLICY.IGNORED,
|
|
25203
|
-
baselineVarianceDays: SAVE_FIELD_POLICY.IGNORED,
|
|
25204
|
-
projectBaselineVarianceDays: SAVE_FIELD_POLICY.IGNORED,
|
|
25205
|
-
primaryBaselineVarianceDays: SAVE_FIELD_POLICY.IGNORED,
|
|
25206
|
-
expectedProgress: SAVE_FIELD_POLICY.IGNORED,
|
|
25207
|
-
expectedProgressBaseline: SAVE_FIELD_POLICY.IGNORED,
|
|
25208
|
-
status: SAVE_FIELD_POLICY.IGNORED,
|
|
25209
|
-
criticalPath: SAVE_FIELD_POLICY.IGNORED,
|
|
25210
|
-
p6: SAVE_FIELD_POLICY.IGNORED
|
|
25211
|
-
};
|
|
25212
|
-
var PERSISTABLE_ACTIVITY_FIELDS = Object.keys(ACTIVITY_SAVE_FIELD_POLICY).filter((field) => {
|
|
25213
|
-
return ACTIVITY_SAVE_FIELD_POLICY[field] === SAVE_FIELD_POLICY.TRACKED;
|
|
25214
|
-
});
|
|
25215
|
-
var DATE_FIELDS2 = /* @__PURE__ */ new Set([
|
|
25216
|
-
"startDate",
|
|
25217
|
-
"endDate",
|
|
25218
|
-
"constraintDate",
|
|
25219
|
-
"dateOrigin"
|
|
25220
|
-
]);
|
|
25221
|
-
var ID_ARRAY_FIELDS = /* @__PURE__ */ new Set([
|
|
25222
|
-
"newActivityIds",
|
|
25223
|
-
"responsableIds",
|
|
25224
|
-
"tagIds"
|
|
25225
|
-
]);
|
|
25226
|
-
function projectPersistableActivity(activity) {
|
|
25227
|
-
const entries = PERSISTABLE_ACTIVITY_FIELDS.map(
|
|
25228
|
-
(field) => [field, snapshotFieldValue(field, activity[field])]
|
|
25229
|
-
);
|
|
25230
|
-
return Object.fromEntries(entries);
|
|
25231
|
-
}
|
|
25232
|
-
function snapshotFieldValue(field, value) {
|
|
25233
|
-
if (DATE_FIELDS2.has(field)) {
|
|
25234
|
-
return value instanceof Date ? new Date(value) : null;
|
|
25235
|
-
}
|
|
25236
|
-
if (ID_ARRAY_FIELDS.has(field)) {
|
|
25237
|
-
return Array.isArray(value) ? [...value] : [];
|
|
25238
|
-
}
|
|
25239
|
-
return value;
|
|
25240
|
-
}
|
|
25241
|
-
function activityChangedSince(current, saved) {
|
|
25242
|
-
return changedPersistableActivityFields(current, saved).length > 0;
|
|
25243
|
-
}
|
|
25244
|
-
function changedPersistableActivityFields(current, saved) {
|
|
25245
|
-
return PERSISTABLE_ACTIVITY_FIELDS.filter(
|
|
25246
|
-
(field) => !fieldEquals(field, current[field], saved[field])
|
|
25247
|
-
);
|
|
25248
|
-
}
|
|
25249
|
-
function fieldEquals(field, currentValue, savedValue) {
|
|
25250
|
-
if (DATE_FIELDS2.has(field)) return dateEquals(currentValue, savedValue);
|
|
25251
|
-
if (ID_ARRAY_FIELDS.has(field))
|
|
25252
|
-
return idArrayEquals(currentValue, savedValue);
|
|
25253
|
-
return (currentValue ?? null) === (savedValue ?? null);
|
|
25254
|
-
}
|
|
25255
|
-
function dateEquals(current, saved) {
|
|
25256
|
-
const currentTime = current instanceof Date ? current.getTime() : null;
|
|
25257
|
-
const savedTime = saved instanceof Date ? saved.getTime() : null;
|
|
25258
|
-
return currentTime === savedTime;
|
|
25259
|
-
}
|
|
25260
|
-
function idArrayEquals(current, saved) {
|
|
25261
|
-
const currentIds = new Set(Array.isArray(current) ? current.map(String) : []);
|
|
25262
|
-
const savedIds = new Set(Array.isArray(saved) ? saved.map(String) : []);
|
|
25263
|
-
if (currentIds.size !== savedIds.size) return false;
|
|
25264
|
-
for (const currentId of currentIds) {
|
|
25265
|
-
if (!savedIds.has(currentId)) return false;
|
|
25266
|
-
}
|
|
25267
|
-
return true;
|
|
25268
|
-
}
|
|
25269
|
-
|
|
25270
|
-
// src/boundary/save/link-changes.ts
|
|
25271
|
-
var LINK_SAVE_FIELD_POLICY = {
|
|
25272
|
-
id: SAVE_FIELD_POLICY.IGNORED,
|
|
25273
|
-
source: SAVE_FIELD_POLICY.TRACKED,
|
|
25274
|
-
target: SAVE_FIELD_POLICY.TRACKED,
|
|
25275
|
-
type: SAVE_FIELD_POLICY.TRACKED,
|
|
25276
|
-
lag: SAVE_FIELD_POLICY.TRACKED,
|
|
25277
|
-
freeSlackHours: SAVE_FIELD_POLICY.IGNORED,
|
|
25278
|
-
isCritical: SAVE_FIELD_POLICY.IGNORED,
|
|
25279
|
-
ganttId: SAVE_FIELD_POLICY.IGNORED,
|
|
25280
|
-
sectorId: SAVE_FIELD_POLICY.IGNORED,
|
|
25281
|
-
proplannerId: SAVE_FIELD_POLICY.IGNORED
|
|
25282
|
-
};
|
|
25283
|
-
var TRACKED_LINK_FIELDS = Object.keys(LINK_SAVE_FIELD_POLICY).filter((field) => {
|
|
25284
|
-
return LINK_SAVE_FIELD_POLICY[field] === SAVE_FIELD_POLICY.TRACKED;
|
|
25285
|
-
});
|
|
25286
|
-
function linkChangedSince(baseline, current) {
|
|
25287
|
-
return TRACKED_LINK_FIELDS.some(
|
|
25288
|
-
(field) => baseline[field] !== current[field]
|
|
25289
|
-
);
|
|
25290
|
-
}
|
|
25291
|
-
function checkNoUpdatedLinks(baseline, current) {
|
|
25292
|
-
const baselineById = new Map(
|
|
25293
|
-
baseline.map((link) => [String(link.id), link])
|
|
25294
|
-
);
|
|
25295
|
-
return current.filter((link) => {
|
|
25296
|
-
const base = baselineById.get(String(link.id));
|
|
25297
|
-
if (!base) return false;
|
|
25298
|
-
return linkChangedSince(base, link);
|
|
25299
|
-
});
|
|
25300
|
-
}
|
|
25301
|
-
|
|
25302
27105
|
// src/previsualization/canonical-digest.ts
|
|
25303
27106
|
function normalized(value) {
|
|
25304
27107
|
if (value === void 0) return ["undefined"];
|
|
@@ -25749,6 +27552,30 @@ var SaveTracker = class _SaveTracker {
|
|
|
25749
27552
|
hasCheckpointLinkId(linkId) {
|
|
25750
27553
|
return this.linksAtLastSave.has(linkId);
|
|
25751
27554
|
}
|
|
27555
|
+
rebaseBaseline(activities, links) {
|
|
27556
|
+
return {
|
|
27557
|
+
activities: activities.map((activity) => {
|
|
27558
|
+
const saved = this.p6ActivitiesAtLastSave.get(String(activity.id));
|
|
27559
|
+
return saved === void 0 ? activity : { ...activity, p6: cloneDomainValue(saved) };
|
|
27560
|
+
}),
|
|
27561
|
+
links,
|
|
27562
|
+
p6Settings: cloneDomainValue(this.p6SettingsAtLastSave),
|
|
27563
|
+
p6ProjectState: cloneDomainValue(this.p6ProjectStateAtLastSave),
|
|
27564
|
+
p6LatestRunLog: cloneDomainValue(this.p6LatestRunLogAtLastSave)
|
|
27565
|
+
};
|
|
27566
|
+
}
|
|
27567
|
+
checkpointActivity(activityId) {
|
|
27568
|
+
return this.activitiesAtLastSave.get(activityId)?.persistable ?? null;
|
|
27569
|
+
}
|
|
27570
|
+
checkpointLink(linkId) {
|
|
27571
|
+
return this.linksAtLastSave.get(linkId) ?? null;
|
|
27572
|
+
}
|
|
27573
|
+
checkpointActivityIds() {
|
|
27574
|
+
return [...this.activitiesAtLastSave.keys()];
|
|
27575
|
+
}
|
|
27576
|
+
checkpointLinkIds() {
|
|
27577
|
+
return [...this.linksAtLastSave.keys()];
|
|
27578
|
+
}
|
|
25752
27579
|
hasUnsavedChanges() {
|
|
25753
27580
|
return this.trackingEnabled && this.initialized && (this.dirtyActivityIds.size > 0 || this.dirtyLinkIds.size > 0 || this.p6TrackingEnabled && (this.dirtyP6ActivityIds.size > 0 || this.p6SettingsIsDirty() || this.p6ProjectStateDirty || this.p6LatestRunLogDirty));
|
|
25754
27581
|
}
|
|
@@ -25868,332 +27695,6 @@ var SaveTracker = class _SaveTracker {
|
|
|
25868
27695
|
}
|
|
25869
27696
|
};
|
|
25870
27697
|
|
|
25871
|
-
// src/boundary/backend/normalize-activity.ts
|
|
25872
|
-
function normalizeActivities(rawActivities, context) {
|
|
25873
|
-
const hiddenIds = [];
|
|
25874
|
-
const activities = rawActivities.map((raw) => {
|
|
25875
|
-
const activity = parseActivity(raw, context);
|
|
25876
|
-
if (raw.should_be_showed === false) hiddenIds.push(activity.id);
|
|
25877
|
-
return activity;
|
|
25878
|
-
});
|
|
25879
|
-
forceRootProjectToRoot(activities);
|
|
25880
|
-
const activityIds = new Set(activities.map((activity) => activity.id));
|
|
25881
|
-
reparentOrphansToRoot(
|
|
25882
|
-
activities,
|
|
25883
|
-
activityIds,
|
|
25884
|
-
context.reporter,
|
|
25885
|
-
context.recordDiagnostic
|
|
25886
|
-
);
|
|
25887
|
-
return { activities, activityIds, hiddenIds };
|
|
25888
|
-
}
|
|
25889
|
-
function parseConstraintLabel(label, reporter, recordDiagnostic, activityId) {
|
|
25890
|
-
if (!label) return null;
|
|
25891
|
-
const normalized2 = CONSTRAINT_LABEL_MAP[label.toUpperCase()];
|
|
25892
|
-
if (normalized2 === void 0) {
|
|
25893
|
-
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.UNKNOWN_CONSTRAINT, activityId);
|
|
25894
|
-
reporter.warn(
|
|
25895
|
-
`[parseFromBackend] unknown constraint label "${label}" \u2014 defaulting to "asap"`
|
|
25896
|
-
);
|
|
25897
|
-
return DEFAULT_CONSTRAINT_TYPE;
|
|
25898
|
-
}
|
|
25899
|
-
return normalized2;
|
|
25900
|
-
}
|
|
25901
|
-
function resolveConstraint(raw, reporter, recordDiagnostic) {
|
|
25902
|
-
const constraintType = parseConstraintLabel(
|
|
25903
|
-
raw.constraint,
|
|
25904
|
-
reporter,
|
|
25905
|
-
recordDiagnostic,
|
|
25906
|
-
String(raw.unique_id)
|
|
25907
|
-
);
|
|
25908
|
-
const constraintTakesDate = constraintType !== CONSTRAINT_TYPE.ASAP && constraintType !== CONSTRAINT_TYPE.ALAP;
|
|
25909
|
-
const constraintDate = constraintTakesDate && raw.constraint_date ? parseBackendDate(raw.constraint_date) : null;
|
|
25910
|
-
return { constraintType, constraintDate };
|
|
25911
|
-
}
|
|
25912
|
-
function resolveCalendarId2(rawCalendarId, knownCalendarIds, defaultCalendarId) {
|
|
25913
|
-
if (rawCalendarId == null) return defaultCalendarId;
|
|
25914
|
-
const coerced = String(rawCalendarId);
|
|
25915
|
-
return knownCalendarIds.has(coerced) ? coerced : defaultCalendarId;
|
|
25916
|
-
}
|
|
25917
|
-
function parseNewActivityIds(raw) {
|
|
25918
|
-
if (raw == null || raw === "") return [];
|
|
25919
|
-
if (Array.isArray(raw)) return raw.map(String);
|
|
25920
|
-
if (typeof raw !== "string") return [];
|
|
25921
|
-
try {
|
|
25922
|
-
const parsed2 = JSON.parse(raw);
|
|
25923
|
-
return Array.isArray(parsed2) ? parsed2.map(String) : [];
|
|
25924
|
-
} catch {
|
|
25925
|
-
return [];
|
|
25926
|
-
}
|
|
25927
|
-
}
|
|
25928
|
-
function hasUsableId(entry) {
|
|
25929
|
-
if (typeof entry !== "object" || entry === null) return false;
|
|
25930
|
-
if (!("id" in entry)) return false;
|
|
25931
|
-
const value = entry.id;
|
|
25932
|
-
return typeof value === "string" || typeof value === "number";
|
|
25933
|
-
}
|
|
25934
|
-
function parseIdList(raw) {
|
|
25935
|
-
if (!Array.isArray(raw)) return [];
|
|
25936
|
-
const ids = [];
|
|
25937
|
-
for (const entry of raw) {
|
|
25938
|
-
if (hasUsableId(entry)) ids.push(String(entry.id));
|
|
25939
|
-
}
|
|
25940
|
-
return ids;
|
|
25941
|
-
}
|
|
25942
|
-
function parseParentId(rawParentId) {
|
|
25943
|
-
return rawParentId === ROOT_PARENT_ID ? null : String(rawParentId);
|
|
25944
|
-
}
|
|
25945
|
-
function normalizePersistedActivityId(rawId) {
|
|
25946
|
-
if (rawId == null) return null;
|
|
25947
|
-
const isValidIdentity = typeof rawId === "number" && (rawId === 0 || Number.isSafeInteger(rawId) && rawId > 0);
|
|
25948
|
-
if (!isValidIdentity) {
|
|
25949
|
-
throw new Error(`invalid activity proplannerId ${String(rawId)}`);
|
|
25950
|
-
}
|
|
25951
|
-
return rawId;
|
|
25952
|
-
}
|
|
25953
|
-
function parseProgress(raw, reporter, recordDiagnostic, activityId) {
|
|
25954
|
-
const progress = Number(raw);
|
|
25955
|
-
if (!Number.isFinite(progress)) {
|
|
25956
|
-
throw new TypeError(`invalid activity progress ${String(raw)}`);
|
|
25957
|
-
}
|
|
25958
|
-
const isBelowNotStarted = progress < NOT_STARTED_PROGRESS;
|
|
25959
|
-
const isAboveCompleted = progress > MAX_TOLERATED_PROGRESS;
|
|
25960
|
-
const isWithinRange = !isBelowNotStarted && !isAboveCompleted;
|
|
25961
|
-
if (isWithinRange) return progress;
|
|
25962
|
-
const clamped = isBelowNotStarted ? NOT_STARTED_PROGRESS : COMPLETED_PROGRESS;
|
|
25963
|
-
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.PROGRESS_OUT_OF_RANGE, activityId);
|
|
25964
|
-
reporter.warn(
|
|
25965
|
-
`[parseFromBackend] activity "${activityId}" progress ${String(raw)} is out of range \u2014 clamping to ${clamped}`
|
|
25966
|
-
);
|
|
25967
|
-
return clamped;
|
|
25968
|
-
}
|
|
25969
|
-
function parseCriticality(raw, reporter) {
|
|
25970
|
-
if (typeof raw.is_critical === "boolean") return raw.is_critical;
|
|
25971
|
-
reporter.error(
|
|
25972
|
-
"[parseFromBackend] invalid activity is_critical \u2014 defaulting to false",
|
|
25973
|
-
new TypeError(
|
|
25974
|
-
`activity "${raw.unique_id}" received ${typeof raw.is_critical}`
|
|
25975
|
-
)
|
|
25976
|
-
);
|
|
25977
|
-
return false;
|
|
25978
|
-
}
|
|
25979
|
-
function parseBaselinePoints(raw, reporter, recordDiagnostic, activityId, isMilestone) {
|
|
25980
|
-
if (!Array.isArray(raw)) return [];
|
|
25981
|
-
return raw.map(
|
|
25982
|
-
(point) => normalizeBaselinePoint(
|
|
25983
|
-
point,
|
|
25984
|
-
reporter,
|
|
25985
|
-
{
|
|
25986
|
-
record: recordDiagnostic,
|
|
25987
|
-
sampleId: activityId
|
|
25988
|
-
},
|
|
25989
|
-
isMilestone
|
|
25990
|
-
)
|
|
25991
|
-
).filter((point) => point !== null);
|
|
25992
|
-
}
|
|
25993
|
-
function parseActivity(raw, context) {
|
|
25994
|
-
const calendarId = resolveCalendarId2(
|
|
25995
|
-
raw.calendarId,
|
|
25996
|
-
context.knownCalendarIds,
|
|
25997
|
-
context.defaultCalendarId
|
|
25998
|
-
);
|
|
25999
|
-
const inheritedDefaultCalendar = raw.calendarId == null || !context.knownCalendarIds.has(String(raw.calendarId));
|
|
26000
|
-
const progress = parseProgress(
|
|
26001
|
-
raw.progress,
|
|
26002
|
-
context.reporter,
|
|
26003
|
-
context.recordDiagnostic,
|
|
26004
|
-
String(raw.unique_id)
|
|
26005
|
-
);
|
|
26006
|
-
const { constraintType, constraintDate } = resolveConstraint(
|
|
26007
|
-
raw,
|
|
26008
|
-
context.reporter,
|
|
26009
|
-
context.recordDiagnostic
|
|
26010
|
-
);
|
|
26011
|
-
const isMilestone = raw.type === ACTIVITY_TYPE.MILESTONE;
|
|
26012
|
-
const startDate = parseBackendDate(raw.start_date);
|
|
26013
|
-
const endDate = isMilestone ? startDate : parseBackendDate(raw.end_date);
|
|
26014
|
-
const durationDays = isMilestone ? 0 : Number(raw.duration);
|
|
26015
|
-
const isCritical = parseCriticality(raw, context.reporter);
|
|
26016
|
-
return {
|
|
26017
|
-
id: String(raw.unique_id),
|
|
26018
|
-
proplannerId: normalizePersistedActivityId(raw.id),
|
|
26019
|
-
parentId: parseParentId(raw.parent_id),
|
|
26020
|
-
name: raw.name,
|
|
26021
|
-
description: raw.description ?? "",
|
|
26022
|
-
type: raw.type,
|
|
26023
|
-
startDate,
|
|
26024
|
-
endDate,
|
|
26025
|
-
durationHours: durationToHours(durationDays, context.hoursPerDay),
|
|
26026
|
-
constraintType,
|
|
26027
|
-
constraintDate,
|
|
26028
|
-
autoScheduling: isAutoSchedulingEnabledForProgress(progress),
|
|
26029
|
-
calendarId,
|
|
26030
|
-
assignedDefaultCalendar: calendarId != null && inheritedDefaultCalendar,
|
|
26031
|
-
dateOrigin: raw.date_origin == null ? null : parseBackendDate(raw.date_origin),
|
|
26032
|
-
progress,
|
|
26033
|
-
cost: Number(raw.cost),
|
|
26034
|
-
usedCost: Number(raw.used_cost ?? 0),
|
|
26035
|
-
realCost: Number(raw.real_cost ?? 0),
|
|
26036
|
-
workHours: Number(raw.hhWorkTime),
|
|
26037
|
-
realWorkHours: Number(raw.real_work ?? 0),
|
|
26038
|
-
ponderator: Number(raw.ponderator ?? 0),
|
|
26039
|
-
hasCustomPonderator: raw.hasCustomPonderator,
|
|
26040
|
-
correlativeId: raw.correlative_id,
|
|
26041
|
-
uniqueCorrelativeId: raw.unique_correlative_id,
|
|
26042
|
-
customId: normalizeCustomId(raw),
|
|
26043
|
-
subcontractId: raw.subcontractId,
|
|
26044
|
-
isLookahead: raw.isOnLookahead,
|
|
26045
|
-
hasLookaheadTasks: Array.isArray(raw.tasks) && raw.tasks.length > 0,
|
|
26046
|
-
newActivityIds: parseNewActivityIds(raw.newActivitiesArray),
|
|
26047
|
-
hasNewActivities: raw.hasNewActivities ?? false,
|
|
26048
|
-
...raw.isNewActivity === true ? { isNewActivity: true } : {},
|
|
26049
|
-
pendingRequestIds: parseIdList(raw.pendingRequests),
|
|
26050
|
-
responsableIds: parseIdList(raw.responsables),
|
|
26051
|
-
tagIds: parseIdList(raw.tags),
|
|
26052
|
-
baselinePoints: parseBaselinePoints(
|
|
26053
|
-
raw.baseline_points,
|
|
26054
|
-
context.reporter,
|
|
26055
|
-
context.recordDiagnostic,
|
|
26056
|
-
String(raw.unique_id),
|
|
26057
|
-
isMilestone
|
|
26058
|
-
),
|
|
26059
|
-
baselineSnapshot: null,
|
|
26060
|
-
baselineVarianceDays: null,
|
|
26061
|
-
projectBaselineVarianceDays: null,
|
|
26062
|
-
primaryBaselineVarianceDays: null,
|
|
26063
|
-
recursiveDurationHours: durationToHours(
|
|
26064
|
-
Number(raw.sumOfDurationRecursively ?? 0),
|
|
26065
|
-
context.hoursPerDay
|
|
26066
|
-
),
|
|
26067
|
-
calendarDuration: computeCalendarDuration({
|
|
26068
|
-
type: raw.type,
|
|
26069
|
-
startDate,
|
|
26070
|
-
endDate
|
|
26071
|
-
}),
|
|
26072
|
-
isCritical,
|
|
26073
|
-
freeSlackHours: null,
|
|
26074
|
-
expectedProgress: null,
|
|
26075
|
-
expectedProgressBaseline: null,
|
|
26076
|
-
status: null,
|
|
26077
|
-
criticalPath: null
|
|
26078
|
-
};
|
|
26079
|
-
}
|
|
26080
|
-
function normalizeCustomId(raw) {
|
|
26081
|
-
return raw.has_childs ? null : raw.custom_id;
|
|
26082
|
-
}
|
|
26083
|
-
function forceRootProjectToRoot(activities) {
|
|
26084
|
-
const rootProject = activities.find(
|
|
26085
|
-
(activity) => activity.correlativeId === 0 && activity.type === ACTIVITY_TYPE.PROJECT
|
|
26086
|
-
);
|
|
26087
|
-
if (rootProject) rootProject.parentId = null;
|
|
26088
|
-
}
|
|
26089
|
-
function reparentOrphansToRoot(activities, ids, reporter, recordDiagnostic) {
|
|
26090
|
-
for (const activity of activities) {
|
|
26091
|
-
if (activity.parentId === null) continue;
|
|
26092
|
-
if (!ids.has(activity.parentId)) {
|
|
26093
|
-
reporter.warn(
|
|
26094
|
-
`[parseFromBackend] activity "${activity.id}" references unknown parent "${activity.parentId}" \u2014 re-parenting to root (null)`
|
|
26095
|
-
);
|
|
26096
|
-
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.ORPHAN_ACTIVITY, activity.id);
|
|
26097
|
-
activity.parentId = null;
|
|
26098
|
-
}
|
|
26099
|
-
}
|
|
26100
|
-
}
|
|
26101
|
-
|
|
26102
|
-
// src/boundary/backend/normalize-link.ts
|
|
26103
|
-
function resolveLinkType(rawType) {
|
|
26104
|
-
if (typeof rawType !== "string") return null;
|
|
26105
|
-
const fromLabel = parseLinkTypeCode(rawType);
|
|
26106
|
-
if (fromLabel) return fromLabel;
|
|
26107
|
-
if (isLinkType(rawType)) return rawType;
|
|
26108
|
-
return null;
|
|
26109
|
-
}
|
|
26110
|
-
function normalizePersistedLinkId(rawId) {
|
|
26111
|
-
if (rawId == null) return void 0;
|
|
26112
|
-
const isValidIdentity = typeof rawId === "number" && (rawId === 0 || Number.isSafeInteger(rawId) && rawId > 0);
|
|
26113
|
-
if (!isValidIdentity) {
|
|
26114
|
-
throw new Error(`invalid link proplannerId ${String(rawId)}`);
|
|
26115
|
-
}
|
|
26116
|
-
return rawId;
|
|
26117
|
-
}
|
|
26118
|
-
function normalizeLinks(rawLinks, context) {
|
|
26119
|
-
const parsedLinks = [];
|
|
26120
|
-
for (const rawLink of rawLinks) {
|
|
26121
|
-
try {
|
|
26122
|
-
parsedLinks.push(
|
|
26123
|
-
parseLink(rawLink, context.knownActivityIds, context.hoursPerDay)
|
|
26124
|
-
);
|
|
26125
|
-
} catch (parseError2) {
|
|
26126
|
-
context.recordDiagnostic(
|
|
26127
|
-
LOAD_DIAGNOSTIC_CODE.INVALID_LINK,
|
|
26128
|
-
String(rawLink.unique_id ?? rawLink.id)
|
|
26129
|
-
);
|
|
26130
|
-
context.reporter.warn(
|
|
26131
|
-
"[parseFromBackend] dropping invalid link",
|
|
26132
|
-
parseError2
|
|
26133
|
-
);
|
|
26134
|
-
}
|
|
26135
|
-
}
|
|
26136
|
-
return dedupeLinksBySignature(parsedLinks, context.recordDiagnostic);
|
|
26137
|
-
}
|
|
26138
|
-
function parseLink(link, activityIds, hoursPerDay) {
|
|
26139
|
-
const id = String(link.unique_id ?? link.id);
|
|
26140
|
-
const source = String(link.source_id ?? link.source ?? "");
|
|
26141
|
-
const target = String(link.target_id ?? link.target ?? "");
|
|
26142
|
-
if (!activityIds.has(source)) {
|
|
26143
|
-
throw new Error(
|
|
26144
|
-
`[parseFromBackend] link "${id}" source "${source}" does not exist`
|
|
26145
|
-
);
|
|
26146
|
-
}
|
|
26147
|
-
if (!activityIds.has(target)) {
|
|
26148
|
-
throw new Error(
|
|
26149
|
-
`[parseFromBackend] link "${id}" target "${target}" does not exist`
|
|
26150
|
-
);
|
|
26151
|
-
}
|
|
26152
|
-
const type = resolveLinkType(link.type);
|
|
26153
|
-
if (type === null) {
|
|
26154
|
-
throw new Error(
|
|
26155
|
-
`[parseFromBackend] link "${id}" has invalid type "${String(link.type)}"`
|
|
26156
|
-
);
|
|
26157
|
-
}
|
|
26158
|
-
const ganttIdRaw = link.ganttId;
|
|
26159
|
-
const sectorIdRaw = link.sectorId;
|
|
26160
|
-
const proplannerId = normalizePersistedLinkId(link.id);
|
|
26161
|
-
return {
|
|
26162
|
-
id,
|
|
26163
|
-
source,
|
|
26164
|
-
target,
|
|
26165
|
-
type,
|
|
26166
|
-
lag: lagDaysToHours(Number(link.lag ?? 0), hoursPerDay),
|
|
26167
|
-
freeSlackHours: null,
|
|
26168
|
-
isCritical: false,
|
|
26169
|
-
...typeof ganttIdRaw === "number" ? { ganttId: ganttIdRaw } : {},
|
|
26170
|
-
...typeof sectorIdRaw === "number" ? { sectorId: sectorIdRaw } : {},
|
|
26171
|
-
...proplannerId === void 0 ? {} : { proplannerId }
|
|
26172
|
-
};
|
|
26173
|
-
}
|
|
26174
|
-
function dedupeLinksBySignature(links, recordDiagnostic) {
|
|
26175
|
-
const bySignature = /* @__PURE__ */ new Map();
|
|
26176
|
-
for (const link of links) {
|
|
26177
|
-
const signature = `${link.source}-${link.target}-${link.type}`;
|
|
26178
|
-
const existing = bySignature.get(signature);
|
|
26179
|
-
if (!existing) {
|
|
26180
|
-
bySignature.set(signature, link);
|
|
26181
|
-
continue;
|
|
26182
|
-
}
|
|
26183
|
-
const incomingWins = linkIdAsNumber(link) > linkIdAsNumber(existing);
|
|
26184
|
-
const discarded = incomingWins ? existing : link;
|
|
26185
|
-
recordDiagnostic(LOAD_DIAGNOSTIC_CODE.DUPLICATE_LINK, discarded.id);
|
|
26186
|
-
if (incomingWins) {
|
|
26187
|
-
bySignature.set(signature, link);
|
|
26188
|
-
}
|
|
26189
|
-
}
|
|
26190
|
-
return [...bySignature.values()];
|
|
26191
|
-
}
|
|
26192
|
-
function linkIdAsNumber(link) {
|
|
26193
|
-
const numeric = Number(link.id);
|
|
26194
|
-
return Number.isFinite(numeric) ? numeric : -Infinity;
|
|
26195
|
-
}
|
|
26196
|
-
|
|
26197
27698
|
// src/boundary/backend/normalize-sector.ts
|
|
26198
27699
|
function parseActivityCreter(raw) {
|
|
26199
27700
|
if (typeof raw !== "string") return DEFAULT_ACTIVITY_CRETER;
|
|
@@ -29245,8 +30746,8 @@ function normalizeLatestConstraintDate(input) {
|
|
|
29245
30746
|
});
|
|
29246
30747
|
}
|
|
29247
30748
|
function isMidnightFinishConstraint(constraintType, constraintDate) {
|
|
29248
|
-
const
|
|
29249
|
-
return
|
|
30749
|
+
const isMidnight2 = constraintDate.getUTCHours() === 0 && constraintDate.getUTCMinutes() === 0 && constraintDate.getUTCSeconds() === 0 && constraintDate.getUTCMilliseconds() === 0;
|
|
30750
|
+
return isMidnight2 && (constraintType === "fnlt" || constraintType === "mfo");
|
|
29250
30751
|
}
|
|
29251
30752
|
function constrainLatestFinish(activity, latestFinish, adapter) {
|
|
29252
30753
|
const bounds = computeConstraintBounds(activity, adapter);
|
|
@@ -32025,8 +33526,36 @@ function emptyChangeSet2(source) {
|
|
|
32025
33526
|
trackingEvents: []
|
|
32026
33527
|
};
|
|
32027
33528
|
}
|
|
33529
|
+
function resolvePersistenceAcknowledgement(action, result, saveTracker) {
|
|
33530
|
+
if (action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE) {
|
|
33531
|
+
return {
|
|
33532
|
+
checkpoint: action.checkpoint,
|
|
33533
|
+
historyBoundary: action.checkpoint?.historyBoundary,
|
|
33534
|
+
persistedActivities: action.activities,
|
|
33535
|
+
persistedLinks: action.links,
|
|
33536
|
+
clearsHistory: false
|
|
33537
|
+
};
|
|
33538
|
+
}
|
|
33539
|
+
if (action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_REBASE) {
|
|
33540
|
+
const rebaseCheckpoint = result.__rebaseCheckpoint;
|
|
33541
|
+
if (rebaseCheckpoint === void 0) {
|
|
33542
|
+
throw new Error("rebase-on-success requires a rebase checkpoint");
|
|
33543
|
+
}
|
|
33544
|
+
return {
|
|
33545
|
+
checkpoint: saveTracker.rebaseBaseline(
|
|
33546
|
+
rebaseCheckpoint.activities,
|
|
33547
|
+
rebaseCheckpoint.links
|
|
33548
|
+
),
|
|
33549
|
+
historyBoundary: void 0,
|
|
33550
|
+
persistedActivities: void 0,
|
|
33551
|
+
persistedLinks: void 0,
|
|
33552
|
+
clearsHistory: true
|
|
33553
|
+
};
|
|
33554
|
+
}
|
|
33555
|
+
throw new Error("checkpoint-on-success requires persistence acknowledge");
|
|
33556
|
+
}
|
|
32028
33557
|
function acknowledgesPersistence(action) {
|
|
32029
|
-
return action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE || action.kind === DISPATCH_ACTION_KIND.ACTIVITY_LOOKAHEAD_SYNC && action.acknowledgePersistedLookahead === true;
|
|
33558
|
+
return action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_ACKNOWLEDGE || action.kind === DISPATCH_ACTION_KIND.PERSISTENCE_REBASE || action.kind === DISPATCH_ACTION_KIND.ACTIVITY_LOOKAHEAD_SYNC && action.acknowledgePersistedLookahead === true;
|
|
32030
33559
|
}
|
|
32031
33560
|
function changesRequireFilterProjectionRebuild(changes) {
|
|
32032
33561
|
for (const change of changes.activities) {
|
|
@@ -33671,6 +35200,7 @@ var ScheduleCore = class _ScheduleCore {
|
|
|
33671
35200
|
linkIdGen: this.coreRuntime.linkIdGenerator,
|
|
33672
35201
|
isActivityIdReserved: (activityId) => this._saveTracker.hasCheckpointActivityId(activityId),
|
|
33673
35202
|
isLinkIdReserved: (linkId) => this._saveTracker.hasCheckpointLinkId(linkId),
|
|
35203
|
+
persistenceCheckpoint: this._saveTracker,
|
|
33674
35204
|
uidGen: this.coreRuntime.uniqueCorrelativeIdGenerator,
|
|
33675
35205
|
customIdTracker: this.coreRuntime.customIdTracker,
|
|
33676
35206
|
reporter: this.coreRuntime.reporter,
|
|
@@ -33760,32 +35290,37 @@ var ScheduleCore = class _ScheduleCore {
|
|
|
33760
35290
|
);
|
|
33761
35291
|
}
|
|
33762
35292
|
if (result.ok) {
|
|
33763
|
-
if (historyPolicy === DISPATCH_HISTORY_POLICY.CHECKPOINT_ON_SUCCESS) {
|
|
33764
|
-
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
|
|
33768
|
-
|
|
35293
|
+
if (historyPolicy === DISPATCH_HISTORY_POLICY.CHECKPOINT_ON_SUCCESS || historyPolicy === DISPATCH_HISTORY_POLICY.REBASE_ON_SUCCESS) {
|
|
35294
|
+
const acknowledgement = resolvePersistenceAcknowledgement(
|
|
35295
|
+
action,
|
|
35296
|
+
result,
|
|
35297
|
+
this._saveTracker
|
|
35298
|
+
);
|
|
33769
35299
|
const currentActivities = this.coreRuntime.state.getAllActivities();
|
|
33770
35300
|
const currentLinks = this.coreRuntime.state.getAllLinks();
|
|
33771
|
-
const checkpoint =
|
|
33772
|
-
const boundary = checkpoint?.historyBoundary ?? this._undo.capturePersistenceBoundary();
|
|
35301
|
+
const checkpoint = acknowledgement.checkpoint;
|
|
33773
35302
|
this._saveTracker.acknowledge(
|
|
33774
35303
|
checkpoint?.activities ?? currentActivities,
|
|
33775
35304
|
checkpoint?.links ?? currentLinks,
|
|
33776
|
-
|
|
33777
|
-
|
|
35305
|
+
acknowledgement.persistedActivities,
|
|
35306
|
+
acknowledgement.persistedLinks,
|
|
33778
35307
|
currentActivities,
|
|
33779
35308
|
currentLinks,
|
|
33780
35309
|
checkpoint?.p6Settings ?? this.coreRuntime.state.dataDateSettings(),
|
|
33781
35310
|
checkpoint?.p6ProjectState === void 0 ? this.coreRuntime.state.dataDateProjectState() : checkpoint.p6ProjectState,
|
|
33782
35311
|
checkpoint?.p6LatestRunLog === void 0 ? this.coreRuntime.state.dataDateLatestRunLog() : checkpoint.p6LatestRunLog
|
|
33783
35312
|
);
|
|
33784
|
-
|
|
33785
|
-
|
|
33786
|
-
|
|
33787
|
-
|
|
33788
|
-
|
|
35313
|
+
if (acknowledgement.clearsHistory) {
|
|
35314
|
+
this._undo.clear();
|
|
35315
|
+
} else {
|
|
35316
|
+
this._undo.discardThrough(
|
|
35317
|
+
acknowledgement.historyBoundary ?? this._undo.capturePersistenceBoundary()
|
|
35318
|
+
);
|
|
35319
|
+
this._undo.applyPersistedIdentities(
|
|
35320
|
+
acknowledgement.persistedActivities ?? [],
|
|
35321
|
+
acknowledgement.persistedLinks ?? []
|
|
35322
|
+
);
|
|
35323
|
+
}
|
|
33789
35324
|
} else if (action.kind === DISPATCH_ACTION_KIND.ACTIVITY_LOOKAHEAD_SYNC && action.acknowledgePersistedLookahead === true) {
|
|
33790
35325
|
this._saveTracker.acknowledgePersistedLookahead(
|
|
33791
35326
|
result.changes.activities,
|
|
@@ -34349,6 +35884,7 @@ function toPublicDispatchResult(result) {
|
|
|
34349
35884
|
const {
|
|
34350
35885
|
__beforeSnap: droppedSnapshots,
|
|
34351
35886
|
__beforeLinks: droppedLinks,
|
|
35887
|
+
__rebaseCheckpoint: droppedRebaseCheckpoint,
|
|
34352
35888
|
...publicResult
|
|
34353
35889
|
} = result;
|
|
34354
35890
|
return publicResult;
|
|
@@ -35230,6 +36766,8 @@ exports.NOT_STARTED_PROGRESS = NOT_STARTED_PROGRESS;
|
|
|
35230
36766
|
exports.ORDER_DIRECTION = ORDER_DIRECTION;
|
|
35231
36767
|
exports.ORDER_MODE = ORDER_MODE;
|
|
35232
36768
|
exports.PASTE_PLACEMENT = PASTE_PLACEMENT;
|
|
36769
|
+
exports.PERSISTENCE_REBASE_CONFLICT_KIND = PERSISTENCE_REBASE_CONFLICT_KIND;
|
|
36770
|
+
exports.PERSISTENCE_REBASE_ENTITY = PERSISTENCE_REBASE_ENTITY;
|
|
35233
36771
|
exports.REJECTION_REASON = REJECTION_REASON;
|
|
35234
36772
|
exports.ROOT_PARENT_ID = ROOT_PARENT_ID;
|
|
35235
36773
|
exports.SCHEDULE_CORE_STATUS = SCHEDULE_CORE_STATUS;
|