[Canonical page](<https://docs.dhtmlx.com/planning-engine/wasm/how-to/split-assignment-work/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Split assignment work

Hand one task's work to several people, for example one developer for the first two days and another for the rest, without changing the task's dates.

Use the `offset` and `duration` fields of [`AssignmentData`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/assignment-data.md>) to do this. Each assignment books only its own part of the task, called a slice. For how slices affect load, see [Resources and assignments](<https://docs.dhtmlx.com/planning-engine/wasm/resources-and-assignment/resources-and-assignments.md#assignment-slices>).

## Steps

1. Book each person on their part of the task. Both fields are worked time in the task's [`duration_unit`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#timeunit>), counted from the task start:

   - `offset`: where the slice starts. Absent or `null` means the task start.
   - `duration`: how much work the slice books. Absent or `null` means up to the task end.

   ```json
   {
     "assignments": [
       { "id": "build:anna", "task": "build", "resource": "anna", "value": 1, "duration": 2 },
       { "id": "build:ben", "task": "build", "resource": "ben", "value": 1, "offset": 2 }
     ]
   }
   ```

   Anna works the first 2 days of `build`. Ben starts after 2 days of work and finishes the task.

2. Pass the assignments to [`solve(dataJson, …)`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) as usual. Then check the booked load with [`loadProfile`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-load-profile.md>):

   ```js
   const options = { from: "2026-08-10", to: "2026-08-15", basis: "day" };
   const profile = unwrap(project.loadProfile(JSON.stringify(options)));
   ```

3. To move the handover in a live project, send an [`assignments.update`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md#assignmentupdate>) entry for each slice in a [`SchedulingChanges`](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) payload. Pass it to [`apply`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-apply.md>), then call [`solve(undefined, "{}")`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>):

   ```json
   {
     "baselineId": "...",
     "assignments": {
       "update": [
         { "id": "build:anna", "duration": 3 },
         { "id": "build:ben", "offset": 3 }
       ]
     }
   }
   ```

4. To book the whole task again, set the field to `null` in the update entry. `"offset": null` goes back to the task start, and `"duration": null` goes up to the task end. A field you leave out stays as it is.

The full example splits the 4-day `build` task between Anna and Ben, prints their daily load, then moves the handover one day later:

```js
const { JsonProject } = require("./pkg-node/plen_wasm.js");

function unwrap(text) {
  const response = JSON.parse(text);
  if (!response.ok) {
    const { kind, code, message, path } = response.error;
    throw new Error(`${kind} ${code ?? ""}: ${message} (at ${path})`);
  }
  return response.result;
}

function printLoad(project) {
  // Daily load from Monday 2026-08-10 to Friday 2026-08-14.
  const options = { from: "2026-08-10", to: "2026-08-15", basis: "day" };
  const profile = unwrap(project.loadProfile(JSON.stringify(options)));
  for (const r of profile.resources) {
    console.log(`  ${r.resource}: ${JSON.stringify(r.buckets.map((b) => b.load))}`);
  }
}

const data = {
  // build: 4 working days, Monday to Thursday.
  tasks: [{ id: "build", start_date: "2026-08-10 08:00", duration: 4 }],
  resources: [{ id: "anna" }, { id: "ben" }],
  assignments: [
    // Anna works the first 2 days of build.
    { id: "build:anna", task: "build", resource: "anna", value: 1, duration: 2 },
    // Ben starts after 2 days of work and finishes the task.
    { id: "build:ben", task: "build", resource: "ben", value: 1, offset: 2 },
  ],
};

const project = new JsonProject("{}");
try {
  const first = unwrap(project.solve(JSON.stringify(data), "{}"));
  console.log("handover after day 2");
  printLoad(project);

  // Move the handover one day later: Anna works 3 days, Ben the last one.
  const changes = {
    baselineId: first.baselineId,
    assignments: {
      update: [
        { id: "build:anna", duration: 3 },
        { id: "build:ben", offset: 3 },
      ],
    },
  };
  const report = unwrap(project.apply(JSON.stringify(changes)));
  if (report.rejected.length > 0) {
    throw new Error(`rejected: ${JSON.stringify(report.rejected)}`);
  }
  const result = unwrap(project.solve(undefined, "{}"));
  console.log(`handover after day 3, moved tasks: ${result.updates.length}`);
  printLoad(project);
} finally {
  project.free();
}
```

`loadProfile` needs a solved project. After `apply` it returns a `project` error with code `no-baseline`, so call `solve(undefined, "{}")` before you read the load again.

## Things to check

- Slices never move tasks. A slice edit leaves every task date as it was, but it changes the `baselineId`. A [change set](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/scheduling-changes.md>) that still carries the old token returns a `project` error with code `stale-baseline`.
- The engine clips the slice to the task's work when it books load. A slice that runs past the task end books only up to the end. An `offset` at or past the task's duration books nothing. If the task gets shorter later, the slice follows it.
- Non-working time and leveling pauses don't count as worked time. A [pause](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/common.md#pause>) or a `leveling_delay` moves the slice together with the work.
- Two slices of the same resource on the same task add up where they overlap.
- Strict validation rejects an `offset` below `0` or not finite, and a `duration` of `0` or less or not finite. [`solve(dataJson, …)`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) returns a `validation` error envelope with the path:

  ```json
  {
    "ok": false,
    "error": { "kind": "validation", "code": null, "message": "assignment offset must be a finite value >= 0, got -1", "path": "assignments[0].offset" }
  }
  ```

  In [`apply`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-apply.md>), the same values show up as rejected operations in `rejected`, at paths like `assignments.update[0]`, and set `needsResync`. See [Recover from rejected changes](<https://docs.dhtmlx.com/planning-engine/wasm/how-to/recover-from-rejected-changes.md>).
- A project created with [`new JsonProject('{"validation":"repair"}')`](<https://docs.dhtmlx.com/planning-engine/wasm/api/json-project.md#creating>) clears an invalid value and adds a `repaired` [note](<https://docs.dhtmlx.com/planning-engine/wasm/api/types/schedule-result.md#note>), so the assignment books the whole task. See [Accept messy input](<https://docs.dhtmlx.com/planning-engine/wasm/how-to/accept-messy-input.md>).
- An assignment on an unscheduled task keeps its slice and books it again when the task is scheduled.

## Result

Run the example with `node`. It prints each person's load for Monday to Friday, before and after the handover moves:

```text
handover after day 2
  anna: [1,1,0,0,0]
  ben: [0,0,1,1,0]
handover after day 3, moved tasks: 0
  anna: [1,1,1,0,0]
  ben: [0,0,0,1,0]
```

The load moves from Ben to Anna on Wednesday. `build` keeps its dates, so [`solve`](<https://docs.dhtmlx.com/planning-engine/wasm/api/project-solve.md>) without data reports no moved tasks.
