# Detect and Handle Conflicts

## Content

Scheduler can detect overlapping events and events that intersect configured non-working time. Detection is informational by default. Cancel the `conflictChecked` event when the application must reject a conflicting operation.

## Reject conflicting changes

`conflictChecked` runs after a valid Create or Update candidate is calculated and before it is committed. It also runs for mouse Move and Resize operations.

```ts
scheduler.conflictChecked.addHandler((_sender, args) => {
  const conflicts = args.result?.conflicts ?? [];
  if (conflicts.length > 0) {
    showConflictWarning(conflicts);
    args.cancel = true;
  }
});
```

Leave `args.cancel` unchanged when conflicts should only be displayed, logged, or sent to a review workflow. Server-side validation remains authoritative.

| Need | Use |
| ---- | --- |
| Read conflicts from a programmatic request | Inspect `result.conflicts`. |
| Reject a pending request | Set `args.cancel = true` in `conflictChecked`. |
| Stop before conflict detection | Set `args.cancel = true` in `itemChanging`. |
| Persist an accepted request | Handle `itemChanged`. |

## Read conflict results

`args.result.conflicts` contains zero or more conflict records. A result can contain both types.

| Type | Meaning | Detail property |
| ---- | ------- | --------------- |
| `SchedulerConflictType.TimeOverlap` | The candidate overlaps another loaded event. | `conflictingItems` |
| `SchedulerConflictType.NonWorkingTime` | The candidate intersects configured non-working time. | `conflictingTimeRanges` |

Use `conflict.type` rather than array order when applying different business rules.

```ts
scheduler.conflictChecked.addHandler((_sender, args) => {
  for (const conflict of args.result?.conflicts ?? []) {
    if (conflict.type === wjScheduler.SchedulerConflictType.TimeOverlap) {
      notifyOverlap(conflict.conflictingItems);
    }
    if (conflict.type === wjScheduler.SchedulerConflictType.NonWorkingTime) {
      notifyUnavailable(conflict.conflictingTimeRanges);
    }
  }
});
```

The result is available only after the operation reaches conflict detection. It can be absent when validation fails or an earlier cancelable event stops the request.

## Understand matching rules

* Event ranges use `[start, end)`, so adjacent events do not conflict.
* With resources, two events conflict only when every resource field they both set has the same value.
* Empty or unknown resource values are ignored. An empty resource data source does not participate in matching.
* When resources are configured but neither event has a valid resource value, they do not produce a `TimeOverlap` conflict.
* Working-time conflicts are global and are not filtered by resource.
* Event overlap checks include only events currently loaded by Scheduler.

For example, with `roomId` as the matching resource dimension:

| Event A | Event B | Time overlap conflict |
| ------- | ------- | --------------------- |
| 09:00-10:00, Room A | 10:00-11:00, Room A | No; ranges are adjacent |
| 09:00-10:00, Room A | 09:30-10:30, Room A | Yes |
| 09:00-10:00, Room A | 09:30-10:30, Room B | No; resource values differ |

With multiple resource fields, every field that both events set must match. For example, an overlap matches only when both `teamId` and `regionId` match; a different value in either field prevents a `TimeOverlap` conflict.

| Resource configuration | Event A | Event B | Time-overlap result |
| ---------------------- | ------- | ------- | ------------------- |
| No resources configured | No assignment | No assignment | Time overlap is reported. |
| Resources configured | No valid assignment | No valid assignment | No `TimeOverlap` is reported. |
| Resources configured | `team=A`, `region=East` | `team=A`, `region=East` | Time overlap is reported. |
| Resources configured | `team=A`, `region=East` | `team=A`, `region=West` | No `TimeOverlap`; a resource value differs. |

## Inspect conflicts from an update result

When application code calls an update method, inspect the returned result after the operation reaches validation and conflict detection.

```ts
const event = scheduler.getEvents(undefined, undefined, [
  { fieldExpr: 'id', value: 1 },
])[0];

if (event) {
  const result = scheduler.update(event, {
    start: new Date(2026, 7, 24, 9, 30),
    end: new Date(2026, 7, 24, 10, 30),
  });
  const conflicts = result.conflicts ?? [];
}
```

The item passed to `update` must be the original object reference from the current Scheduler collection. Do not pass a copy, a rendered event instance, or an item from another collection. Use the event handler when the application must cancel the operation; use the returned result to display or log a programmatic request.

The returned result reports the outcome of the programmatic request; it does not replace cancellation in `conflictChecked`. A request canceled by `itemChanging` may not reach conflict detection and may therefore have no conflict result.

## Style conflicting events

Scheduler applies `wj-scheduler-item-conflict` to an event with one or more conflicts.

```css
.wj-scheduler-item-conflict {
  outline: 2px solid #dc2626;
  outline-offset: -2px;
}
```

Custom item templates can read `SchedulerItemTemplateContext.conflicts` and render details based on each conflict's type. Keep conflict presentation separate from the source item data.