@outbuild-company/schedule-core 1.9.3 → 1.10.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/index.cjs CHANGED
@@ -338,8 +338,13 @@ var FIELD_REGISTRY = {
338
338
  },
339
339
  expectedProgressBaseline: {
340
340
  valueKind: "number",
341
+ equalityPrecision: 2,
341
342
  extract: (activity) => activity.expectedProgressBaseline
342
343
  },
344
+ expectedProgress: {
345
+ valueKind: "number",
346
+ extract: (activity) => activity.expectedProgress
347
+ },
343
348
  baselineDurationDays: {
344
349
  valueKind: "number",
345
350
  extract: (activity) => activity.baselineSnapshot?.durationDays ?? null
@@ -422,6 +427,9 @@ function toTargetDate(value) {
422
427
  }
423
428
  return new Date(Number.NaN);
424
429
  }
430
+ function utcDayMs(date2) {
431
+ return Date.UTC(date2.getUTCFullYear(), date2.getUTCMonth(), date2.getUTCDate());
432
+ }
425
433
  function matchString(value, target, operator) {
426
434
  const isAbsent = value === null;
427
435
  if (operator === "includes") {
@@ -434,24 +442,35 @@ function matchString(value, target, operator) {
434
442
  if (operator === "isNot") return isAbsent || String(value) !== target;
435
443
  return false;
436
444
  }
437
- function matchNumber(value, target, operator) {
445
+ function matchNumber(value, target, operator, equalityPrecision) {
438
446
  if (value === null || typeof value !== "number") {
439
447
  return operator === "notEquals";
440
448
  }
441
- if (operator === "equals") return value === target;
442
- if (operator === "notEquals") return value !== target;
449
+ const equalityValue = equalityPrecision === void 0 ? value : Number(value.toFixed(equalityPrecision));
450
+ if (operator === "equals") return equalityValue === target;
451
+ if (operator === "notEquals") return equalityValue !== target;
443
452
  if (operator === "greaterThan") return value > target;
444
453
  if (operator === "lessThan") return value < target;
445
454
  if (operator === "greaterOrEqual") return value >= target;
446
455
  if (operator === "lessOrEqual") return value <= target;
447
456
  return false;
448
457
  }
458
+ function matchNumberBetween(value, minimum, maximum) {
459
+ return typeof value === "number" && value >= minimum && value <= maximum;
460
+ }
449
461
  function matchDate(value, target, operator) {
450
462
  if (!(value instanceof Date)) return false;
451
- if (operator === "after") return value.getTime() > target.getTime();
452
- if (operator === "before") return value.getTime() < target.getTime();
463
+ const valueDay = utcDayMs(value);
464
+ const targetDay = utcDayMs(target);
465
+ if (operator === "after") return valueDay > targetDay;
466
+ if (operator === "before") return valueDay < targetDay;
453
467
  return false;
454
468
  }
469
+ function matchDateBetween(value, minimum, maximum) {
470
+ if (!(value instanceof Date)) return false;
471
+ const day = utcDayMs(value);
472
+ return day >= utcDayMs(minimum) && day <= utcDayMs(maximum);
473
+ }
455
474
  function matchIdArray(value, allowed, operator) {
456
475
  const ids = Array.isArray(value) ? value : [];
457
476
  const intersects = ids.some((id) => allowed.has(String(id)));
@@ -474,11 +493,33 @@ function matchCriterion(activity, criterion, context) {
474
493
  if (descriptor === null) return false;
475
494
  const value = descriptor.extract(activity, context);
476
495
  const { operator } = criterion;
496
+ if (operator === "between") {
497
+ if (descriptor.valueKind === "number") {
498
+ return matchNumberBetween(
499
+ value,
500
+ Number(criterion.value),
501
+ Number(criterion.valueTo)
502
+ );
503
+ }
504
+ if (descriptor.valueKind === "date") {
505
+ return matchDateBetween(
506
+ value,
507
+ toTargetDate(criterion.value),
508
+ toTargetDate(criterion.valueTo)
509
+ );
510
+ }
511
+ return false;
512
+ }
477
513
  if (descriptor.valueKind === "string") {
478
514
  return matchString(value, String(criterion.value), operator);
479
515
  }
480
516
  if (descriptor.valueKind === "number") {
481
- return matchNumber(value, Number(criterion.value), operator);
517
+ return matchNumber(
518
+ value,
519
+ Number(criterion.value),
520
+ operator,
521
+ descriptor.equalityPrecision
522
+ );
482
523
  }
483
524
  if (descriptor.valueKind === "date") {
484
525
  return matchDate(value, toTargetDate(criterion.value), operator);
@@ -500,10 +541,10 @@ function matchesCriteria(activity, filter, context) {
500
541
  );
501
542
  }
502
543
  function overlapsRange(activity, range) {
503
- const taskStart = activity.startDate.getTime();
504
- const taskEnd = activity.endDate.getTime();
505
- const windowStart = range.start.getTime();
506
- const windowEnd = range.end.getTime();
544
+ const taskStart = utcDayMs(activity.startDate);
545
+ const taskEnd = utcDayMs(activity.endDate);
546
+ const windowStart = utcDayMs(range.start);
547
+ const windowEnd = utcDayMs(range.end);
507
548
  const fullyInside = taskStart >= windowStart && taskEnd <= windowEnd;
508
549
  const startsInside = taskStart >= windowStart && taskStart <= windowEnd;
509
550
  const windowStartsInside = windowStart >= taskStart && windowStart <= taskEnd;
@@ -653,9 +694,10 @@ var OPERATORS_BY_KIND = {
653
694
  "greaterThan",
654
695
  "lessThan",
655
696
  "greaterOrEqual",
656
- "lessOrEqual"
697
+ "lessOrEqual",
698
+ "between"
657
699
  ]),
658
- date: /* @__PURE__ */ new Set(["after", "before"]),
700
+ date: /* @__PURE__ */ new Set(["after", "before", "between"]),
659
701
  "id-array": /* @__PURE__ */ new Set(["someOf", "notSomeOf"]),
660
702
  enum: /* @__PURE__ */ new Set(["someOf", "notSomeOf"]),
661
703
  boolean: /* @__PURE__ */ new Set(["someOf", "notSomeOf"]),
@@ -686,6 +728,9 @@ function normalizeCriterion(criterion) {
686
728
  if (!OPERATORS_BY_KIND[descriptor.valueKind].has(criterion.operator)) {
687
729
  return { ok: false, reason: "unknown_operator" };
688
730
  }
731
+ if (criterion.operator === "between") {
732
+ return normalizeBetweenCriterion(criterion, descriptor.valueKind);
733
+ }
689
734
  if (descriptor.valueKind === "number") {
690
735
  const value = normalizeNumberValue(criterion.value);
691
736
  return value === null ? { ok: false, reason: "invalid_value" } : {
@@ -726,6 +771,31 @@ function normalizeCriterion(criterion) {
726
771
  }
727
772
  return { ok: true, criterion };
728
773
  }
774
+ function normalizeBetweenCriterion(criterion, valueKind) {
775
+ if (valueKind === "number") {
776
+ const value = normalizeNumberValue(criterion.value);
777
+ const valueTo = normalizeNumberValue(criterion.valueTo);
778
+ if (value === null || valueTo === null || value > valueTo) {
779
+ return { ok: false, reason: "invalid_value" };
780
+ }
781
+ return {
782
+ ok: true,
783
+ criterion: { ...criterion, value, valueTo }
784
+ };
785
+ }
786
+ if (valueKind === "date") {
787
+ const value = normalizeDateValue(criterion.value);
788
+ const valueTo = normalizeDateValue(criterion.valueTo);
789
+ if (value === null || valueTo === null || value.getTime() > valueTo.getTime()) {
790
+ return { ok: false, reason: "invalid_value" };
791
+ }
792
+ return {
793
+ ok: true,
794
+ criterion: { ...criterion, value, valueTo }
795
+ };
796
+ }
797
+ return { ok: false, reason: "unknown_operator" };
798
+ }
729
799
  function validateValueForKind(criterion, valueKind) {
730
800
  const { value } = criterion;
731
801
  if (valueKind === "boolean") {
@@ -1623,13 +1693,19 @@ function resolveExpansionAnchors(originalLink, adapter) {
1623
1693
  userLag: originalLink.lag ?? 0,
1624
1694
  sourceEndpointIsStart,
1625
1695
  targetLag: targetEndpointIsFinish ? -endpointSpanHours(target, targetBounds, adapter) : 0,
1626
- summarySourceId: originalLink.type === LINK_TYPE.START_TO_START && sourceBounds ? originalLink.source : void 0
1696
+ summarySourceId: originalLink.type === LINK_TYPE.START_TO_START && sourceBounds ? originalLink.source : void 0,
1697
+ finishAnchoredSummaryTargetId: targetEndpointIsFinish && targetBounds ? originalLink.target : void 0
1627
1698
  };
1628
1699
  }
1629
1700
  function pushOffsetPreservingLinks(expanded, sourceIds, leafIds, originalLink, adapter, virtualIdCounter, linksByActivity) {
1630
1701
  const anchors = resolveExpansionAnchors(originalLink, adapter);
1631
1702
  const { source, sourceBounds, targetStart, userLag } = anchors;
1632
- const { sourceEndpointIsStart, targetLag, summarySourceId } = anchors;
1703
+ const {
1704
+ sourceEndpointIsStart,
1705
+ targetLag,
1706
+ summarySourceId,
1707
+ finishAnchoredSummaryTargetId
1708
+ } = anchors;
1633
1709
  let counter = virtualIdCounter;
1634
1710
  for (const srcId of sourceIds) {
1635
1711
  const sourceLag = sourceEndpointIsStart ? sourceLagForLeaf(srcId, source, sourceBounds, adapter) : 0;
@@ -1652,7 +1728,7 @@ function pushOffsetPreservingLinks(expanded, sourceIds, leafIds, originalLink, a
1652
1728
  targetLag,
1653
1729
  userLag + offTarget,
1654
1730
  summarySourceId,
1655
- offTarget !== 0 && targetLag === 0 ? originalLink.target : void 0
1731
+ finishAnchoredSummaryTargetId ?? (offTarget !== 0 && targetLag === 0 ? originalLink.target : void 0)
1656
1732
  )
1657
1733
  );
1658
1734
  counter += 1;
@@ -2100,7 +2176,7 @@ async function asapPass(orderedIds, links, adapter, options, isCurrent, plans) {
2100
2176
  if (!maxStart || result.successorStart > maxStart) {
2101
2177
  maxStart = result.successorStart;
2102
2178
  drivingLinkId = String(link.id);
2103
- drivingOffsetIsStale = link._summaryTargetId !== void 0 && !predecessorMovedThisRun;
2179
+ drivingOffsetIsStale = link._summaryTargetId !== void 0 && !predecessorMovedThisRun && (link._targetLag === 0 || options.preventSummaryTargetPullback === true);
2104
2180
  }
2105
2181
  }
2106
2182
  const staleOffsetWouldPullBack = maxStart !== null && drivingOffsetIsStale && maxStart < activity.startDate;
@@ -9963,7 +10039,11 @@ async function runPostMutation(deps, args) {
9963
10039
  if (passes.has(DERIVED_PASS.AUTOSCHEDULE)) {
9964
10040
  await refreshMutatedParentBounds(deps.adapter, args);
9965
10041
  }
9966
- const schedule = passes.has(DERIVED_PASS.AUTOSCHEDULE) ? await runAutoschedule(deps, args.autoscheduleFrom) : emptySchedule();
10042
+ const schedule = passes.has(DERIVED_PASS.AUTOSCHEDULE) ? await runAutoschedule(
10043
+ deps,
10044
+ args.autoscheduleFrom,
10045
+ args.preventSummaryTargetPullback
10046
+ ) : emptySchedule();
9967
10047
  await rollUpParentBounds(
9968
10048
  deps.adapter,
9969
10049
  args,
@@ -10034,8 +10114,11 @@ function collectFieldProgressInvalidations(adapter, beforeById, dirtySeeds, affe
10034
10114
  }
10035
10115
  }
10036
10116
  }
10037
- async function runAutoschedule(deps, autoscheduleFrom) {
10117
+ async function runAutoschedule(deps, autoscheduleFrom, preventSummaryTargetPullback = false) {
10038
10118
  const options = autoscheduleFrom === "roots" ? {} : { triggerId: autoscheduleFrom };
10119
+ if (preventSummaryTargetPullback) {
10120
+ options.preventSummaryTargetPullback = true;
10121
+ }
10039
10122
  const result = await deps.scheduler.schedule(options);
10040
10123
  const outcome = captureScheduledRows(deps.adapter, result.updatedIds);
10041
10124
  applyResults(deps.adapter, result);
@@ -11504,14 +11587,21 @@ async function createActivityCore(action, deps, opts = {}) {
11504
11587
  recomputeUsedCostCascadeForActivity(newId, adapter);
11505
11588
  recomputeCostCascadeForActivity(newId, adapter);
11506
11589
  }
11507
- recomputeAndFoldCorrelatives(
11590
+ const correlativeShifts = recomputeCorrelatives(
11508
11591
  adapter,
11509
11592
  newId,
11510
- beforeSnap,
11511
11593
  initialTouched,
11512
11594
  opts
11513
11595
  );
11514
- return { ok: true, newId, parent, newActivity, beforeSnap, initialTouched };
11596
+ return {
11597
+ ok: true,
11598
+ newId,
11599
+ parent,
11600
+ newActivity,
11601
+ beforeSnap,
11602
+ initialTouched,
11603
+ correlativeShifts
11604
+ };
11515
11605
  }
11516
11606
  function validateCreateTarget(action, adapter) {
11517
11607
  const isRoot = isRootParent(action.parentId);
@@ -11606,20 +11696,16 @@ function applyParentMutations(action, newId, parent, adapter, parentWasLeaf) {
11606
11696
  );
11607
11697
  }
11608
11698
  }
11609
- function recomputeAndFoldCorrelatives(adapter, newId, beforeSnap, initialTouched, opts) {
11610
- if (opts.skipCorrelativeRecompute) return;
11611
- const correlativeShifts = recomputeCorrelativeIds(adapter);
11612
- if (opts.skipBeforeSnap) return;
11613
- foldCorrelativeShifts(
11614
- adapter,
11615
- correlativeShifts,
11616
- /* @__PURE__ */ new Set([String(newId)]),
11617
- beforeSnap,
11618
- initialTouched
11699
+ function recomputeCorrelatives(adapter, newId, initialTouched, opts) {
11700
+ if (opts.skipCorrelativeRecompute) return [];
11701
+ const shifts = recomputeCorrelativeIds(adapter).filter(
11702
+ (shift) => shift.activityId !== String(newId)
11619
11703
  );
11704
+ for (const shift of shifts) initialTouched.add(shift.activityId);
11705
+ return shifts;
11620
11706
  }
11621
11707
  async function buildCreateChangeSet(adapter, action, coreResult, scheduledIds, hoursPerDay) {
11622
- const { newId, newActivity, beforeSnap, initialTouched } = coreResult;
11708
+ const { newId, newActivity, beforeSnap, initialTouched, correlativeShifts } = coreResult;
11623
11709
  const trackingEvent = {
11624
11710
  name: DISPATCH_TRACK_EVENT.ACTIVITY_CREATION,
11625
11711
  properties: {
@@ -11636,6 +11722,7 @@ async function buildCreateChangeSet(adapter, action, coreResult, scheduledIds, h
11636
11722
  beforeSnap,
11637
11723
  touchedIds: initialTouched,
11638
11724
  scheduledIds,
11725
+ correlativeShifts,
11639
11726
  trackingEvents: [trackingEvent]});
11640
11727
  }
11641
11728
  async function dispatchActivityCreate(action, options, deps) {
@@ -11664,8 +11751,9 @@ async function dispatchActivityCreate(action, options, deps) {
11664
11751
  },
11665
11752
  {
11666
11753
  action,
11667
- autoscheduleFrom: "roots",
11754
+ autoscheduleFrom: newId,
11668
11755
  recomputeParentsFrom: [newId],
11756
+ preventSummaryTargetPullback: true,
11669
11757
  now: deps.now,
11670
11758
  options
11671
11759
  }
@@ -21352,6 +21440,11 @@ var ScheduleCore = class _ScheduleCore {
21352
21440
  this.assertReady();
21353
21441
  return this.coreRuntime.state.hiddenIds().map(String);
21354
21442
  }
21443
+ getVisibleActivityIds() {
21444
+ this.assertReady();
21445
+ const hiddenIds = new Set(this.coreRuntime.state.hiddenIds().map(String));
21446
+ return this.coreRuntime.state.getAllIds().map(String).filter((activityId) => !hiddenIds.has(activityId));
21447
+ }
21355
21448
  getActiveFilter() {
21356
21449
  this.assertReady();
21357
21450
  return cloneDomainValue(this.coreRuntime.state.getActiveFilter());