# Dime.Scheduler
> Documentation
This file contains all documentation content in a single document following the llmstxt.org standard.
## About
import { FaInfoCircle } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "About", link: "https://app.dimescheduler.com/about"}]} />
The about page gathers everything you need to identify, support, or audit a Dime.Scheduler tenant in one place. Open it from the user menu in the top-right of the application, or go directly to [app.dimescheduler.com/about](https://app.dimescheduler.com/about).
## What you'll find
- **Tenant identification** - the unique tenant ID and environment name, useful when contacting support or correlating logs.
- **License information** - the license type, edition, and the modules currently activated for the tenant. For the full breakdown of editions, modules, and thresholds, see the [license](license.md) page.
- **Version information** - the build of Dime.Scheduler currently running on the tenant, helpful when triaging compatibility questions with the [Business Central](../integrations/bc/intro.mdx) or [NAV](../integrations/nav/intro.md) connectors.
- **Legal & policy links** - the terms of service, the privacy policy, and other resources required for compliance reviews.
## When to use it
- **Opening a support ticket** - copy the tenant ID and version from this page so the support team can pinpoint your environment.
- **Validating an upgrade** - after a release, verify the version number matches what you expected.
- **License audits** - confirm the active edition and modules without leaving the app.
---
## API keys
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "API keys", link: "https://app.dimescheduler.com/keys"}]} />
An API key is a long-lived secret that proves a *process* (not a person) has permission to call the Dime.Scheduler API. They are the simplest of several authentication options the API supports, which is exactly why they fit the cases where simplicity matters most: a back-office connector that wakes up every hour, a CI job that pushes setup changes, a script that pulls a daily report.
For anything user-facing - a web app where individual users sign in, an interactive tool - OAuth is the better choice. API keys cannot represent a specific person; they represent the integration as a whole. If multiple humans need different permissions or you need to know *who* did something, reach for the [authentication options](setup.md) on the user side instead.
## Creating a key
Global administrators can create a new API key from the API key setup page:
1. Open the [API keys page](https://app.dimescheduler.com/keys) from the admin menu.
2. Click **New** and give the key a recognisable name (the calling system, the script, the integration).
3. Pick an expiration date. Short-lived keys are safer; rotate them on a schedule that fits your security policy.
4. Click **Generate**. The full key is displayed **once**.
:::warning
The full key value is only shown the moment it is generated. Copy it into your secret store immediately - there is no way to retrieve it again afterwards. If the key is lost, revoke it and issue a new one.
:::
## Using a key
Send the key in the `X-API-KEY` header of every request - pass the raw key value, with no `Bearer` prefix, no quotes, no whitespace:
```http
X-API-KEY:
```
```sh
curl --header "X-API-KEY: YOUR_API_KEY" https://api.dimescheduler.com/...
```
The same header is used by the [public API](../develop/api.md), the [MCP server](../integrations/mcp/configuration.md), and every supported [SDK](../develop/sdk/about.md) (.NET, JavaScript, Python).
## Keeping keys safe
A leaked API key gives the holder everything the integration can do, for as long as the key lives. The safer you can make that surface, the better:
- **Store keys in a secret manager** (Azure Key Vault, AWS Secrets Manager, environment variables in a protected CI runner) - never commit them to source control.
- **Use one key per integration.** Rotation and revocation become surgical: when a script is decommissioned, revoke just its key, not everyone else's.
- **Set an expiration.** Even production keys should expire; an expiring key forces rotation discipline.
- **Revoke immediately on suspected leak** - delete the key from the API keys page and issue a replacement.
---
## Application Settings
Dime.Scheduler configures behavior in layers. Understanding how those layers interact is key to setting up the application so it stays both flexible and predictable.
The configuration cascades. Administrators set the default behavior of the application. Planners can then deviate from those defaults by customizing their own user profiles and components (layouts). This split of responsibilities may look complex at first, but it is what lets the system stay flexible without becoming unpredictable.
The [highest level of configuration](administration/setup.md) is the administrator's responsibility. Unlike the static configuration, this global configuration affects how Dime.Scheduler's planning capabilities actually work. Turning on the "Apply requested dates" switch, for example, has a profound effect on the planning board.
The next two levels belong to the planner. Users manage their own profiles and layouts. Some buttons and switches override the higher levels (such as the "unit of distance" flag or the "ignore calendars" mode on the planning board or appointment), while others simply extend the application's behavior (like the planning board's start date or snap interval).
The rule of thumb across these layers is straightforward:
:::info
The highest level of a given configuration item is applied unless a lower level overrides it.
:::
The top two levels are easy to grasp because there are only so many setups to do. Profiles and layouts, on the other hand, have no limits and can produce very different planning views.
This section covers the static and global configuration. For user-level configuration, [continue here](user/profiles-layouts.md).
import DocCardList from '@theme/DocCardList';
---
## Connectors
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Connectors", link: "https://app.dimescheduler.com/connectors"}]} />
A connector is how Dime.Scheduler talks back to the system that owns the work. When a planner schedules a task on the board, that change has to flow back to the system the job came from - Business Central, NAV, a Power Automate flow, whatever the source is - or the rest of the business never finds out the work was planned. A connector is the configured link that makes that round-trip happen.
One tenant can have many connectors. Most do: a single Dime.Scheduler instance often serves multiple companies inside the same ERP, multiple regional installs of the same ERP, or a mix of an ERP and a webhook-based system. Each gets its own connector entry, each with its own credentials, each scoped to a specific source.
## What identifies a connector
Beyond housekeeping (a name and description so you can tell them apart), the connector form asks for two kinds of information: **where to send data** and **how to authenticate** when you get there. Everything else flows from those two questions.
### Where to send data
Three fields decide where:
- **Type** is the back-office system family the connector targets:
- Microsoft Dynamics 365 Business Central
- Microsoft Dynamics NAV
- Webhook
- **Web Service URI** is the endpoint Dime.Scheduler posts to.
- **API Type** picks the protocol within the family. Some types support more than one:
| Back-office system | Supported types | Comment |
| ------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------- |
| **Microsoft Dynamics 365 Business Central** | SOAP web servicesAPI pages | See the [BC web service URL](/integrations/bc/install/ds-to-bc#where-to-reach-bc). |
| **Microsoft Dynamics NAV** | SOAP web services | See the [NAV web service URL](/integrations/nav/install/ds-to-nav#web-service-url). |
| **Webhook** | REST | |
The **Enabled** flag is the on/off switch. When off, the connector is registered but ignored - useful for staging a new connector before going live, or temporarily silencing a source during maintenance.
:::info[Bi-directional message flow]
What you configure here only covers the flow **from** Dime.Scheduler **to** the back-office system. The opposite direction (getting data *into* Dime.Scheduler) is set up in that system itself. The supported integrations Dime Software manages on the customer's behalf:
- [Webhook](/integrations/webhook/intro)
- [Power Automate](/integrations/powerplatform)
- [Microsoft Dynamics NAV](/integrations/nav/install/ds-to-nav)
- [Microsoft Dynamics 365 Business Central](/integrations/bc/install/ds-to-bc)
:::
### How records get matched back
When several back-office systems share one tenant, every record needs to say where it came from - otherwise Dime.Scheduler has no way to tell which connector should receive an update. The **source app** is that identifier: a unique tag stamped on every record at the moment it arrives, that travels with the record forever afterwards.
Each connector declares the source app it owns. When the planner makes a change, Dime.Scheduler reads the source app off the record and fires every connector that has a matching source app. Run the simulation below to see this match-and-dispatch step in action:
### Limiting the connector's scope
The **Crud type** lets you restrict a connector to specific operations - create only, update only, delete only, or any combination. Use it when one source needs different downstream behaviour per operation (a Power Automate flow that only fires on create, a separate notification connector that only fires on delete).
## Authentication
The back office decides what authentication it accepts; Dime.Scheduler supports the common shapes.
### Basic authentication
Just a **login** and **password**.
### Windows authentication (NTLM, Kerberos)
Just a **login** and **password**. Not supported by the REST API.
### MS Entra ID
Needs three values:
- **Tenant ID**
- **Client ID**
- **Client Secret**
### OAuth2
Any OAuth2-capable identity provider works. The supported grant type is client-credentials, in two flavours:
- **Client secret basic** - credentials go in the HTTP Basic Authentication header.
- **Client secret post** - credentials go in the request body.
Both need the same details:
- **Authorization Server** - the endpoint Dime.Scheduler calls first to exchange credentials for a token.
- **Client ID** - the app registration in the OAuth2 provider.
- **Client Secret** - that app's secret value.
- **Scope** - optional, space-delimited values Dime.Scheduler needs to post back to the web service URL.
### Picking an authentication method
OAuth2 and MS Entra ID (which is OAuth2 underneath) are the industry standard and what you should reach for first. With OAuth2, **client secret basic** is the safer of the two flavours - prefer it unless the back office only accepts the post variant.
Basic Authentication is the fastest way to get a test integration working. Windows / NTLM is fine on-prem when Active Directory is set up properly on the NAV/BC server. Both are older methods the industry has moved away from, so use them only when OAuth2 or MS Entra ID isn't an option.
### Reference: what each connector type supports
Authentication methods:
| Connector | Basic Auth | Windows Auth | NTLM | MS Entra ID | OAuth2: Basic Auth | OAuth2: Body Auth |
| ----------------------------------------------------------- | ---------- | ------------ | ---- | ----------- | ------------------ | ----------------- |
| [NAV](/integrations/nav/install/ds-to-nav) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| [Business Central](/integrations/bc/install/ds-to-bc) on-prem | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| [Business Central](/integrations/bc/install/ds-to-bc) cloud | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
| [Webhook](/integrations/webhook/intro) | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
Data formats:
| Connector | SOAP | JSON |
| ----------------------------------------------------------- | ---- | ---- |
| [Business Central](/integrations/bc/install/ds-to-bc) on-prem | ✅ | ✅ |
| [Business Central](/integrations/bc/install/ds-to-bc) cloud | ✅ | ✅ |
| [NAV](/integrations/nav/install/ds-to-nav) | ✅ | ❌ |
| [Webhook](/integrations/webhook/intro) | ❌ | ✅ |
REST/JSON is preferred where it is available; it is the modern default.
## Back-office-specific connector setup
For the system-specific details:
- [Business Central](/integrations/bc/install/ds-to-bc)
- [NAV](/integrations/nav/install/ds-to-nav)
- [Power Automate](/integrations/powerplatform/)
- [Webhook](/integrations/webhook/authentication)
---
## Configuring task details
The task details component shows the properties of your jobs, tasks and appointments. This guide walks you through configuring it: which fields appear, how they are grouped, and in what order.
## Pane contents setup
### Navigation
Configure the task details component in the "Pane Layouts" settings view:

The view has two distinct sections:
- The list of supported fields
- The order and grouping of the fields shown in the task details grid
The tree on the left-hand side is static and cannot be modified. When Dime.Scheduler is installed for the first time, the tree on the right-hand side already contains a few fields. For example, the first group shows essential information like the job and task numbers:

You are free to change these values. Everything except the number of groups can be customized:
- The name of the groups
- The sequence of the groups
- Which fields are selected and which groups they belong to
- The sequence of the fields inside a group
### Adding fields
Start by adding some fields. We'll use the empty "Group 14" to show information about the appointments and their underlying tasks.
- In the "Fields" tree, expand the "Appointments" node. Click the arrow, or select the node and press the right arrow key.
- Navigate to the "EndDate" item.
- Click the record and hold it while you drag it over Group 14.
- When you reach the right group, release the mouse to drop the field.
Here it is in action:

Do the same for the start date. This time the group already contains an item, so you need to decide where the new field goes. For the start date, it's best to place it before the end date. The drag and drop functionality gives you a visual cue that shows exactly where the record will land.
Drop it here to place it above the end date:

Drop it here to place it below the end date:

As planned, drop it above the end date in the first position:

You can keep adding fields, but let's finish this section with a job field and a task field. We'll choose the two most commonly populated fields: Task No. and Job No. The fields may show different captions; we'll cover that later, or you can jump straight to the corresponding [guide on customizing columns](administration/guides/customizing-columns.md).
Group 14 should now contain 4 fields:
- Start date (Appointment)
- End date (Appointment)
- Task No. (Task)
- Job No. (Job)
### Removing fields
Remove a field from the tree on the right-hand side in one of two ways:
- Select the record and click the "Delete" button.
- Select the field and press the "Delete" keyboard key.
Like every other action in this view, changes are saved instantly and automatically.
If you use the default data that Dime.Scheduler provides, Group 1 already contains a Job No. field. Remove it from Group 1:

### Group sequence
Just as you can reorder the fields within a group, you can reorder the groups themselves. The method is the same: drag and drop. Move Group 14 near the top:

## Localization
The default naming convention doesn't tell you much about a group. Change the group captions in the localization settings view:

In the translations grid, filter the context group by "TARGETPANEGROUP":

This returns a list of 20 records, one for each group in the tree in the pane layouts settings view.
Now give the groups meaningful names. Select group 14 in the grid and add a caption. Be sure to select the correct language:

## Validate
### Validate localization
Go back to the pane layouts settings view. "Group 14" now shows its new name:

### Validate task details
In the planning view, make sure the profile includes a task details component. The renamed group appears here as well:

### Validate task details data
Create a manual appointment on the planning board and select it. Only the start and end date are populated:

This is expected, because the appointment has no task or job.
The same holds for open tasks: there's no data for the appointment fields, so they are skipped:

For an appointment created from the open task grid, all data is available:

## Conclusion
This guide covered the setup of task details: adding fields to groups, changing the order of fields and groups, and localizing the groups. There's more you can do. Fields can also be localized and formatted (for dates and numbers). To learn more, check out the [guide on customizing columns](administration/guides/customizing-columns.md).
You've already seen this in action with the Job No. field:

---
## Creating a new user
Adding a user to Dime.Scheduler takes only a few details. Before you start, decide three things:
- Which login experience you want to give this user
- Their e-mail address
- Their name
These match the required fields in the grid of the user administration view.
## What information is stored
Dime.Scheduler keeps very little personal data about a user:
- Name
- E-mail
- Time zone
- Language
Beyond this master data, plenty of information can be associated with a user. It falls into two categories:
- Data-driven security entities such as groups, filter values, categories and time markers
- User-generated content like layouts and profiles
## Adding a user
You add a user from the grid in the user administration view. Click the 'Add' button, then fill out the empty required cells in the new row. The information you need to enter depends on the user type.
### Adding a forms user
A forms user is the simplest type. The account is not attached to any external system and is managed entirely by Dime.Scheduler. Because Dime.Scheduler is also an identity service, these credentials can be used by other applications.
Since this user type stands on its own, you only need to fill out the required fields: the e-mail and the language. Dime.Scheduler generates a password automatically, which you can pass on to the user. This is a temporary password, so the user is prompted to reset it on first login.
The language and time zone fields are optional. If you leave them blank, the user is prompted to set a value when logging in.
### Adding a Microsoft Entra ID user
Microsoft Entra ID (formerly known as Azure Active Directory) is, alongside the likes of Facebook and Google, one of the most widely adopted external identity services. It lets you reuse existing user accounts that are managed centrally, and it is built and secured by Microsoft.
The creation process is the same as for a forms user: enter a name and an e-mail address. The one requirement is that the e-mail address must be connected to a Microsoft account and Microsoft Entra ID. Users can then log in from any device, anywhere in the world, and the service can be configured extensively to fit the needs of any organization.
## Read more
- [What is Microsoft Entra ID](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis)
- [Logging in for the first time](user/guides/logging-in-for-the-first-time.md)
---
## Setting up calendars for the planning board
Calendars define when resources are available to work. This guide walks you through a full calendar setup for the planning board. By the end you will:
- Create a hierarchy of calendars
- Assign resources to calendars
- Apply calendars on the planning board
Calendars also drive the Gantt chart, but to keep this guide focused we stay on the planning board. The behavior is the same in both views, and once configured the integration is seamless.
The example here is deliberately simple and does not mirror a real-world setup, but it shows how calendars optimize resource planning. The feature is straightforward, yet because it plugs into the core of the application it is powerful, so it is worth taking the time to learn.
## Permissions
Before you start, confirm your user has the right permissions. Most administration and settings views require an augmented [authorization level](/administration/user/roles#protected-features):
- To reach the settings modules, you need a role that contains the '**Settings Module**' user action.
- The '**Administration Module**' user action grants access to the views in the administration area.
An administrator can assign a role to you, but you have to log out and back in to see the changes.
## Creating calendars
### Calendars navigation
With the right permissions, open the 'Calendars' view from the sidebar navigation menu:
.
Alternatively, you can navigate to the relative URI `Settings/Calendars`.
### Create a calendar hierarchy
The calendar setup screen is divided into two areas:
- The tree list with calendars on the left
- The selected calendar's details, business hours and overrides
The main panel reflects whichever item is selected in the tree. Here is how a default calendar looks in the setup screen [^1]:

By the end of this guide, you will have a setup like this:
```cmd
.
+-- Base calendar 2019
| +-- Business hours
| +-- 08:00 - 12:00
| +-- 13:00 - 15:00
| +-- 15:15 - 17:30
| +-- Holidays
| +-- December 25th
| +-- Modified schedule
| +-- December 24th (partially working)
| +-- Every Tuesday between December 16th and December 22th:
| +-- 08:00 - 12:00
| +-- 12:30 - 19:00
| +-- Every Friday
| +-- 09:00 - 14:00
| +-- Child calendars
| +-- Workers 2019
| +-- Holidays
| +-- Week of the July 15th
| +-- Clerks 2019
| +-- Holidays
| +-- December 24th
+-- Default calendar
| +-- Business hours
| +-- 08:00 - 16:00
```
#### Add a calendar
You will add three calendars: one base calendar used by two other calendars. Nested calendars let you manage common schedules across teams and keep data duplication to a minimum. They are not required, but they are recommended when you have many resources to manage.
To add a calendar, click the "Add" button. A new line appears and the center panel focuses on it. Start with the base calendar: give it a code (a unique value that can serve as a reference in back-office systems) and a descriptive name.
These form fields are not saved automatically. The red circling sync icon in the tree signals pending changes. Click it to save the calendar's master data: the form fields (code, name, default, etc.) and the first tab in the panel below (the settings tab). Refresh the page and you will see the record has been persisted.
#### Setting the business hours
Next, change the business hours, which default to 08:00 - 16:00. Go to the corresponding tab, which holds a grid where you add, update and remove business hours.
Business hours are sets of working hours that, together, make up a working day. Most people don't work eight hours straight; they take some kind of lunch break. To update an existing set, double click the cell (either start or end) to reveal a selection list:

You can create as many sets as you need:

The one caveat: business hours must not overlap. For example, 08:00 - 12:00 and 11:30 - 13:30 is an invalid setup and will cause problems in the system.
Changes are persisted immediately, so there is no need to synchronize the calendar manually.
#### Adding a day override
Now add some holidays to the base calendar that apply to everyone in the organization. Holidays like Christmas belong in a base calendar. Go to the "Day overrides" tab, which is empty by default. This grid works together with the date picker in the area above the tab panel.
In the date picker, navigate to December and select the 25th. The selected day is marked in a different color. Press the "Add" button in the day overrides grid, and a new record is appended with the date pre-populated:

This transaction is persisted as a full non-working day. Edit the cells in the grid to change the name and/or the date:

Now add another day override on Christmas Eve, when people may leave the office at 15:00. This differs from Christmas because people still work, just not the whole day, so it takes an extra step. Add a record in the day overrides grid, keep it selected, and click the "Edit" button. A window opens where you indicate whether this is a working or a non-working day. If you choose the working time option, specify the working hours for this day. These hours can be anything and override the default business hours for this day only.

As before, date conflicts cause problems, so don't create two day overrides on the same day. Click 'OK' to save the changes. There is no need to synchronize the calendar; changes are saved automatically.
#### Adding a week override
A week override is a range of week days for a limited time range. Long holidays or temporary altered work schedules are good cases for a week override. Like a day override, select a start date in the date picker and click "Add" in the week overrides grid. The overridden week is visualized in the date picker:

One difference from the other grids: this transaction is not saved right away. You must configure at least one day in the "Edit" dialog to save the override. After that, changes save automatically. This prevents empty overrides, which would cause confusion and undesirable results.

Alongside the two familiar options - working and non-working hours - a third option exists: the default time. This inherits the calendar's settings for that day during the overridden week. In this example, if you set Monday to use the default settings, Monday 16 December is a normal business day. Tuesday 17 December differs because the week override sets different working hours for Tuesdays between 16 December and 22 December.
You can always click a colored cell in the date picker to see the details of that override:
```text
Working hours for Dec 16, 2019:
15:15-17:30
13:00-15:00
08:00-12:00
Based on: override "[Without name]: 2019-12-16 - 2019-12-22" in calendar "Base calendar 2019"
----
Working hours for Dec 17, 2019:
08:00-12:00
12:30-19:00
Based on: override "[Without name]: 2019-12-16 - 2019-12-22" in calendar "Base calendar 2019"
```
#### Adding a week day
The last override option is the simplest: week day overrides are perpetual overrides for a given week day. For example, every Friday afternoon people leave the office at 15:00 instead of 16:00. As with day overrides, you specify whether the day is working or non-working. Unlike the other two override types, week days are not visualized in the date picker, but they are applied in the recalculation algorithms.

#### Calendar inheritance
So far you have a base calendar for 2019 with a few overrides. This calendar is usable on its own, but now create two more specialized calendars: one for workers and one for clerks. Assume all workers take time off in July to escape the heat, while clerks get extra time off at the end of the year.
Add two new calendars: 'Workers 2019' and 'Clerks 2019'. Besides the code and the name, select a parent calendar: the base calendar 2019. Save the calendars. Changes to all calendars are saved in one transaction, so there is no need to process them one by one. The calendar tree should now look like this:

Both calendars inherit the settings from the base calendar. In other words, unless you override something at a lower level (the workers or clerks calendar), the settings from the higher level (the base calendar) apply. Here, all workers and clerks work less on Christmas Eve and get Christmas day off. Settings from a higher level - the parent calendars - are not shown in the date picker of the inherited child calendars, so keep the hierarchy in the tree in mind.
Navigate to Christmas Eve for the clerks calendar, which the parent calendar marks as a shorter working day. Override this day and mark it as non-working (no action is needed, since this is the default value). Then select the workers calendar, add a week override in July 2019, and flag all days as non-working.
You are almost ready to see the two calendars in action. Before you head to the planning board, assign these calendars to some resources.
## Assigning resources to calendars
### Resource calendars navigation
A separate setup screen handles the assignment of calendars to resources (or vice versa). Open the resource calendar assignment screen from the sidebar navigation menu:
.
Alternatively, you can navigate to the relative URI `Settings/ResourceCalendars`.
### Calendar assignment
By default, all resources use the default calendar. Administrators can assign a calendar to a resource for a particular time range.
The process is straightforward: choose a resource, pick a calendar, and set when and how long the calendar applies.

Read a line in the grid as: 'this calendar applies to this resource between this date and that date'.
A resource is therefore not limited to one calendar. Because each assignment is limited in time, you can build detailed, flexible work schedules. Think of everyone who works in shifts.
For this guide, locate two resources that appear close to each other on your planning board. That makes the different calendars easier to spot later.
One final step remains before you can use the calendars in the planning board.
## Configuring the application
### Application navigation
If you can access the administration module, open the application setup:

Alternatively, you can navigate to the relative URI `Administration/Application`.
### Configuring calendars for the planning board
In this view, open the "Planning" tab. Three fields matter here:
- Visualize resource calendars
- Resource zone color
- Switch on calendar mode
The first two control how calendars are visualized in the planning board. The third decides whether calendars factor into appointment durations.
For this guide, enable both flags and pick a bold color from the color picker.

Now open the planning view.
## Working with the planning board
### Visualizing calendars
Go to the planning board and navigate to the resources you configured in the resource calendars setup. Use the date picker to jump to the 2019 Christmas period. If all went well, the two resources show quite different shaded time slots. That is the calendars at work.

The shaded areas represent each resource's unavailable times, leaving the empty areas as working times.
In this example, Wylma Charlet is clearly a clerk: she doesn't work on Christmas Eve. Willy McReynolds does work, so he is a worker. Further down the list, you will find resources on the default calendar: Burch Mougel follows the default calendar's schedule.
### Working with appointments
Our setup has calendar mode switched on. Without it, nothing in this or the next section would apply: unavailable times can still be shown, but they are not enforced.
Switch to the day view and locate Christmas Eve on the planning board:

Select the 11:00 time slot, right click, and press the "New" button. The editor opens. Do not disable the calendar mode button; it is enabled by default, so keep it that way.

Nothing notable has happened yet, but that changes when you drag this appointment slightly to the right:

This is the result after you drop the appointment:

Notice how the total duration differs from the working time. That is because of the 30 minute break between 12:30 and 13:00.
The task takes one hour to complete, so keeping the appointment at one hour would be wrong, since the resource is unavailable for half an hour. Dime.Scheduler looks at the calendars that apply and extends the appointment's duration until the working time is complete.
Do the same for the other resource and you'll see a more dramatic change:

The first available time slot is two days away, so the appointment's duration is extended until the task can be completed. Here the appointment lasts 45 hours for a task that takes only one hour. This kind of planning is extreme, but it makes the point.
### Resolving the leading resource
Looking at the image above, you may wonder what happens when an appointment has two or more resources assigned and those resources have different schedules.
We call this resource calendar constraint resolution, and you see it the moment you add a second resource to an appointment. Use SHIFT + drag to add a resource. Before you drop the appointment, the resolution window appears:

This window lets you choose the 'leading resource': the resource used as the source for calculating the appointment's duration. In other words, this resource's calendars (if any) are applied.
The recalculation is correct for the leading resource, but not necessarily for the others. This constraint resolution window appears every time you modify the appointment, giving you the chance to pick the most relevant resource.
If that gets in the way, you can switch off calendar mode at three levels: globally, per planning board, or per appointment. For the appointment level, there are three ways to toggle calendar mode:
- Via the constraint resolution window:

- Via the context menu:

- Via the editor:

While calendar mode is off, the appointment ignores calendars and behaves as if none exist. The moment you switch it back on, you are asked to choose a leading resource.

The settings on appointments are (temporarily) ignored when calendar mode is off on the planning board [^2].
When calendar mode is off entirely, the buttons are not even available. As explained in [appointment recalculation](user/features/appointment-recalculation.md#configuration), this is a cascading setting that allows for maximum flexibility.
## Summary
You built a calendar hierarchy in which common settings and holidays are inherited by specialized calendars that reflect how the organization works. You then assigned resources to those calendars, configured the application, and created appointments to see the effect of calendars on the planning board.
## Read more
- [Planning board](user/components/planningboard/index.md)
- [Calendar-based appointment duration recalculation](user/features/appointment-recalculation.md)
- [Calendars](administration/settings/calendars.md)
- [Role-based access control](user/authentication/roles.md)
- [Roles](/administration/user/roles)
[^1]: There must always be a default calendar. Changes that would leave the setup without a default calendar are rejected until this constraint is met.
[^2]: This can lead to interesting situations when the same appointment appears on multiple planning boards in your user profile and one of them has calendar mode switched off.
---
## Customizing columns
This guide walks you through customizing two columns so you can tailor the look and feel of the grids. You will change the display text and formatting of a free date field and a free decimal field.
## Prerequisites
You need to be an administrator to access the setup views. Specifically, you need the "Settings module" user action assigned to your account.
## Create a profile and layout
This step is optional, but a dedicated profile and layout make the impact of each change easy to see, so we will prepare one first.
Start from the standard profile. Make the open tasks component a little wider so you can see a good number of columns, and remove any other components from the profile if needed.
Something like this works well:

Save the profile. Go to the "Profile" menu and click "Save as". In the modal window that appears, enter a descriptive name (for example "Customize Columns Guide"), mark it as the default profile, and save.
You now have access to the state buttons in the open tasks grid, which is exactly what you need. Navigate to one of the columns in the grid and open its menu. Open the columns submenu and add the columns "Task Free Date 1" and "Task Free Decimal 1". These fields accept any kind of data; they are not bound to any specific field in the back office, so it is up to you to give each column a meaning.

Once the columns are added, move them to the left. Locate them in the grid, drag the column headers to the left, and make them a little wider:

Save the layout. Click the icon in the open tasks grid. In the modal window that appears, enter a descriptive name (for example "Customize Columns Guide"), mark it as the default layout, and save.
That is all for the planning view. Now it is time to configure these columns.
## Locate the columns
Go to `Settings -> Localization` to open the localization settings view.

As the name suggests, this view handles the localization of the application. The first tab lets you provide custom captions for many fields and columns, which is what you need here.
The grid on the left displays every field that can be customized. Add a filter to the "Field name" column for fields that contain the text "Free", and add a filter to the "Source table" column for fields that contain the text "Task". Somewhere in the middle of the grid you should find the records "FreeDecimal1" and "FreeDate1".

Start with the decimal field.
## Configure the decimal field
In this section you will do two things: change the column caption (which is "Free Decimal 1" in English) and change how decimal values are displayed.
### Decimal formatting
The supported decimal formats are documented in the [localization settings](administration/settings/localization.md#decimal). Suppose you want 4-digit precision, so the value 1.5 is displayed as 1.5000. The format to enter is "0.0000".
In the translations grid, make sure the "Free Decimal 1" field is selected. Double-click the record and enter 0.0000.

Save the changes.
### Decimal caption
The grid on the right, called "Captions", is contextual: it shows the captions for whichever field is selected in the fields grid. With the "Free Decimal 1" field still selected, click the "Add" button in the "Captions" grid:

Now do two things: select the language and enter the corresponding caption. Stick to English and give it a name:

Save the changes.
## Configure the date field
In this section you will do two things: change the column caption (which is "Free Date 1" in English) and change how date values are displayed. The steps are the same as the previous section.
### Date formatting
The supported date formats are documented in the [localization settings](administration/settings/localization.md#date-and-time). Suppose you want to show the full representation of the date, so the value 2020/03/15 is displayed as "March 15 2020 10:10". The format to enter is "F j Y g:i".
In the translations grid, make sure the "Free Date 1" field is selected. Double-click the record and enter "F j Y g:i".

Save the changes.
### Date caption
The grid on the right, called "Captions", is contextual: it shows the captions for whichever field is selected in the fields grid. With the "Free Date 1" field still selected, click the "Add" button in the "Captions" grid:

Now do two things: select the language and enter the corresponding caption. Stick to English and give it a name:

Save the changes.
## Validate changes
Navigate back to the planning view. Notice how the column names and formatting have changed:

From now on, every reference to this field uses the caption rather than the default translation. The pane contents setup is one example:

The task details window also uses the new formatting and captions:

## Read more
- [Localization settings](administration/settings/localization.md)
---
## Implementation checklist
import ImplementationChecklist from '@site/src/components/ImplementationChecklist';
This is the end-to-end path for standing up a new Dime.Scheduler implementation on
Business Central or NAV. Work through it top to bottom and tick boxes as you go.
:::info[Your progress is saved]
The boxes you tick are stored in this browser, so you can close the tab and pick up
where you left off. Treat the list as a solid default, not gospel: every customer has
quirks, so add, skip, or adapt steps where your environment needs it.
:::
## How an implementation flows
Every back-office integration follows the same three phases:
## The checklist
## When something breaks
Sync issues are the most common hiccup during an implementation. Start here:
- [Troubleshooting hub](/troubleshooting)
- [Business Central troubleshooting](/integrations/bc/troubleshooting) and [transaction errors](/integrations/bc/transaction-errors)
- [NAV troubleshooting](/integrations/nav/troubleshooting)
Still stuck? [Contact support](https://www.dimescheduler.com/support).
---
## Lock appointments for users
This guide shows you how to lock appointments so that some users, or all of them, can no longer edit them. The authorization mechanism in Dime.Scheduler is data-driven, which lets you build fine-grained security workflows around your customer's business processes. As with most of Dime.Scheduler, the back office drives the configuration and you control the locking from there.
## Authorizing users in Dime.Scheduler
This guide builds on data-driven security. For the full picture, see [data-driven security](user/authentication/datadrivensecurity.md). In short, you can lock appointments and resources using filter groups or visual indicators:
- With security on **filter groups**, you segregate roles and responsibilities. For example, users can be configured to only see resources and tasks from a limited number of companies, departments, regions, etc.
- With security on visual indicators such as **categories** and **time markers**, you implement a data and workflow-driven authorization based on the floating values of categories and time markers. For example, users can be barred from modifying the data when an appointment is in a certain state.
Filter groups restrict what resources and tasks a user can see, while indicators limit what a user can do with (planned) tasks.
When you combine security on filter groups, categories and/or time markers, the most restrictive setting wins: every condition must be met before the user can modify the appointment.
:::info
This data-driven authorization mechanism doesn't necessarily mean users won't see the appointments on the planning board. While users *will* see all appointments for the resources they see on the planning board, they won't be able to edit or delete appointments that are outside of their security permissions.
:::
### Filter group authorization
Filter groups restrict what resources and tasks a user can see.
Once you have set up the filter groups in the back office and sent them to Dime.Scheduler, apply the data-driven security as follows:
1. In Dime.Scheduler, go to the [Users administration page](administration/user/users.md#user-assignments).
2. Select the user you want to secure.
3. In the bottom tab panel, select the **filter group tab**.
4. Select the values that apply to that user.
From now on, this user will only **see** resources and tasks that match the selected filter values.
### Indicator authorization
Authorizing users through indicators works the same way. Indicators limit what a user can do with (planned) tasks.
Once you have set up the categories and time markers in the back office and sent them to Dime.Scheduler, apply the data-driven security as follows:
1. In Dime.Scheduler, go to the [Users administration page](administration/user/users.md#user-assignments).
2. Select the user you want to secure.
3. Navigate to the pane at the bottom.
4. Select the categories and time markers that apply to that user.
:::tip
When you send a job or task from the back-office system to Dime.Scheduler, you can specify default indicators. These values are applied when the user drops the open task to the planning board.
:::
From now on, this user will only be able to **modify** appointments that match the selected indicators.
## Locking an appointment from the back office
You can lock tasks and appointments in two main ways: programmatically or data-driven.
### Locking programmatically
You can also lock appointments directly. Use the web service function [`mboc_updateAppointmentLocked`](/develop/api/appointment) to lock or unlock a single appointment. To lock or unlock every appointment linked to a given task, use [`mboc_updateTaskLocked`](/develop/api/task#set-lock).
Call this low-level function directly, or reach for one of the platform-dependent wrappers below.
#### Dynamics
In NAV/BC, you can use the function `UpdateLocked` or `UpdateLockedFromId` in `codeunit 2087643 "DS Update Appointment Status"`.
#### SDK / CLI
The [SDK](/develop/sdk) and [CLI](/develop/cli) can lock appointments and tasks too. For example, the `tasklocked` command locks or unlocks any task in your Dime.Scheduler instance:
```text
dimescheduler tasklocked --help
██████╗ ██╗███╗ ███╗███████╗ ███████╗ ██████╗██╗ ██╗███████╗██████╗ ██╗ ██╗██╗ ███████╗██████╗
██╔══██╗██║████╗ ████║██╔════╝ ██╔════╝██╔════╝██║ ██║██╔════╝██╔══██╗██║ ██║██║ ██╔════╝██╔══██╗
██║ ██║██║██╔████╔██║█████╗ ███████╗██║ ███████║█████╗ ██║ ██║██║ ██║██║ █████╗ ██████╔╝
██║ ██║██║██║╚██╔╝██║██╔══╝ ╚════██║██║ ██╔══██║██╔══╝ ██║ ██║██║ ██║██║ ██╔══╝ ██╔══██╗
██████╔╝██║██║ ╚═╝ ██║███████╗██╗███████║╚██████╗██║ ██║███████╗██████╔╝╚██████╔╝███████╗███████╗██║ ██║
╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝
Dime.Scheduler 1.0.0
Copyright (C) 2022 Dime Software
--sourceapp The source app.
--sourcetype The source type.
--jobno The job's unique identifier.
--taskno The task's unique identifier.
--locked True to lock the task.
--sentfrombackoffice True to mark the appointment is sent from the back office.
-u, --URI Required. The base URI of Dime.Scheduler.
-c, --user Required. The Dime.Scheduler user's email address.
-p, --password Required. The Dime.Scheduler user's password.
-a, --append (Group: action) Append the record to Dime.Scheduler.
-d, --delete (Group: action) Remove the record from Dime.Scheduler.
--help Display this help screen.
--version Display version information.
```
### Locking with data
When you update an appointment from the back-office application to Dime.Scheduler, you can change its indicators. Every change is reflected immediately, which makes the data-driven mechanism reevaluate. This makes it easy to create an indicator such as 'Locked' and assign it to a limited group of authorized users, so the appointment won't accidentally be updated without permission.
---
## Administration Guides
import DocCardList from '@theme/DocCardList';
These guides cover the configuration work an administrator does after Dime.Scheduler is
installed. Each one is self-contained, so you can jump straight to the task in front of you.
If you are standing up a brand new environment, start with the
[implementation checklist](/administration/guides/implementation-checklist). It is the
end-to-end path from a fresh install to a working planning board, and it links out to the
rest of these guides at the point where each becomes relevant.
Otherwise, pick by what you are trying to change:
- **Access and security.** [Creating a new user](/administration/guides/create-new-user)
covers a single account. [Setting up a secure environment](/administration/guides/setting-up-security)
is the wider picture of data-driven security and roles, and
[locking appointments](/administration/guides/lock-appointments) restricts what individual
planners are allowed to touch.
- **Planning data.** [Setting up calendars](/administration/guides/calendars) defines the
working hours the planning board draws against.
- **What planners see.** [Customizing columns](/administration/guides/customizing-columns)
and [configuring task details](/administration/guides/configuring-taskdetails) change how
data is labelled and grouped in the grids and the task details pane.
---
## Setting up a secure environment
This guide walks you through locking down a Dime.Scheduler environment by combining two authorization mechanisms:
- Data-driven security
- Role-based access control
Each controls a different side of access:
- Data-driven security rules define what data users get to see.
- Role-based access control rules define which actions users can execute.
To set up role-based access control, you create roles, assign user actions to them, and assign those roles to users and user groups. To set up data-driven authorization, you work with a data set that includes (resource and task) filter values, categories and time markers.
There is no direct link between the two mechanisms, so you can set up each one independently. Used together, though, they are far more powerful than either alone. This guide covers each topic on its own.
## Setting up the roles
This is the easier of the two concepts to understand and set up. It rests on two notions: "Roles" and "User Actions".
A role is a collection of user actions that you assign to users and user groups. A user action describes a protected activity in Dime.Scheduler. For example, to create an appointment, a user needs access to the "Create appointment" user action. You can create as many roles as you want and assign as many roles as you want to users and groups. A user can lack the "Create appointment" user action in the "Read-only" role but still have it through another role.
Now let's set this up.

Create two roles with the following user actions:
- Limited planner
- Drag and drop
- Drag and drop appointment
- Edit
- New
- Details
- Scheduler module
- Read-only
- Scheduler module
Next, assign these roles to two new users. Go to the user administration view:

Create two new Forms users:

> Tip: you can use a fake or temporary mail service like [getnada.com](https://getnada.com)
Select the read-only planner and assign the newly created "Read-only" role:

Select the limited planner and assign the newly created "Limited planner" role:

Now check the results. If you were already logged in, log out and log in again. The limited planner has access to a few buttons in the planning board's context menu:

The read-only planner doesn't even have access to this context menu.
If a role is too restrictive, you have two options:
- Expand the scope of the role by adding user actions.
- Assign one or more roles to the user (or group(s)).
At this point, both users still see the same data. Role-based access control has no influence on that, it governs only the protected actions in Dime.Scheduler.
## Setting up the data-driven security roles
Now that you've controlled what users can do, turn to which data they can manipulate. This is the realm of data-driven security, where filter values, categories and time markers from resources and tasks are matched against those of the users and user groups.
> This section assumes the filter values, categories and time markers are already set up.
Data-driven security is all about matching and comparing data between users and entities like resources and tasks. This guide focuses on resources, but the same approach applies to tasks.
> To make the matches easy to spot, we highlight them in different colors.
### User-driven security rules
While you're still in the user administration setup, assign some filter values, categories and time markers. This environment has two filter groups, each with two filter values. Assign two entirely different filter values to the users:

There is no overlap between the filter values. Do the same for categories and time markers:

Refresh the planning view for both users, and you'll notice that some data is already blocked:

The limited planner can still create appointments, but now has only a limited choice of categories:

Data-driven security is already doing its job. Now take it one step further with resource filter values.
### Resource-driven security rules
Secure two resources that share the same filter values as the user. Head to the resource settings view.

Select two random resources and assign the filter values:

Refresh the planning board, and you'll see the changes immediately:

Both users get a different data set: resources are filtered by the user's filter values.
## Summary
Role-based access control is standard practice in many software products. Dime.Scheduler goes a step further by letting administrators define their own roles instead of relying on static, predefined ones. A role has little meaning on its own: administrators decide what it contains, not the people at Dime. Role-based access control is configured in the application, but it tends to stay static because security requirements rarely change. Usually you set it up once at the start and leave it untouched for a long time.
Data-driven security, on the other hand, can be applied very dynamically. It's ideal for defining requirements on tasks, resources and planners. Use it to divide tasks and resources by region, skill, customer and more. It's powerful and it flows throughout the entire application: users can only see what data-driven security allows them to see.
In this example, you narrowed the planning capabilities of two users to a very specific set. Read-only planners can only see a filtered data set (thanks to the assigned filter values and categories) and can't modify anything. Limited planners can do more, but only up to a point: they can act only on the tasks and resources that match their filter values and categories.
This is a deliberately simple example. In practice, it grows quickly once a customer grasps the power behind these concepts. Just as resources and tasks can be anything, categories, time markers and filter values can be used to divide them along any dimension you need.
## Read more
- [Data-driven security](user/authentication/datadrivensecurity.md)
- [Role-based access control](user/authentication/roles.md)
---
## Administering Dime.Scheduler
import { FaCog, FaUsers, FaSlidersH, FaExchangeAlt, FaServer, FaBoxes } from 'react-icons/fa';
import { LandingHero, CardGrid, Card, PathwayList, Pathway, Note, CTA } from '@site/src/components/LandingKit';
}
featured
/>
}
/>
}
/>
}
/>
}
/>
}
/>
Dime.Scheduler sits between your back-office system (BC, NAV, CRM, custom) and your dispatchers. **Connectors** define how data moves in and out; **resources, calendars, and templates** define what dispatchers see; **users, roles, and groups** define who can do what. Most administration tasks fall into one of those three buckets.
Setting up a new tenant? Start with the [implementation checklist](/administration/guides/implementation-checklist) for the end-to-end path. Everything else is in the sidebar on the left.
---
## License
Every Dime.Scheduler instance runs on a license: a unique value that defines which features the customer can use and the thresholds that apply to them. You can check the current status at any time on the about page inside the app.
## What is being licensed
Every license type ships with all features out of the box and free of charge, except for a handful of optional modules. What differs between license types are the thresholds. For example, the Enterprise subscription lets you work with hundreds of resources, while the Starters license caps you at a limited number. The same applies to the number of location-tracked resources and Exchange calendars.
Three modules can be added to the standard license at extra cost:
- Exchange connector
- Basic map
- Advanced map
Because third-party vendors are involved, these modules carry different licensing conditions. The details fall outside the scope of this documentation, so reach out to our sales team for more information.
## Remediating license violations
Dime.Scheduler continuously checks whether the application still meets its license conditions. If an administrator adds a 101st resource while the license caps you at 100, for instance, your ERP partner may reach out, at Dime Software's request, to invite you to upgrade to a higher edition.
To check the current license status, open the [about page](https://app.dimescheduler.com/about). To resolve a violation, see the [resource setup](administration/settings/resources.md) docs. The fix is straightforward: bring the number of resources back below the threshold, or upgrade to a higher edition.
- For the plannable resources on the planning board, you can temporarily check the `Do not show` field for a selection of resources.
- For the synchronization with Exchange, you can temporarily turn off the `Exchange Integration Enabled` field.
- For the basic and advanced map, you can temporarily turn off the `GPS Tracking Enabled` field.
---
## Actions
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Actions", link: "https://app.dimescheduler.com/actions"}]} />
An action URI is a shortcut that lets a planner trigger something *outside* Dime.Scheduler from inside the context of a task, an appointment, or a job. Send an email, open the originating record in Business Central, kick off a Power Automate flow, launch a custom workflow in a third-party system: anything that can be addressed by a URL is fair game.
Without action URIs, the workflow loop is broken. A planner spots that an appointment needs a follow-up email, switches to Outlook, retypes the customer's address, then comes back. With action URIs, the same loop happens in one click - the URI carries the appointment's data along with it, so the destination system already knows which record to open or what to prefill.

## How an action URI works
You define a URL template - either as a POST or a GET - with placeholders for the values you want to pass along. When the planner triggers the action from the right-click menu, Dime.Scheduler substitutes those placeholders with the values of the record they triggered it on, and either posts the payload (for POST URIs) or opens the resulting URL in a new tab (for GET URIs).
GET URIs are the workhorse: most back-office systems expose deep-link URLs that open a specific record, so a GET action URI is usually enough. POST URIs are for the cases where you need to push a structured payload, typically into a workflow engine that expects an inbound webhook.
## What scopes an action URI to a record
Two things decide whether a given action URI shows up on a given record:
- **The entity type** - planned task, unplanned task, map pin, new appointment. An action URI declared for the open task list never shows up on the planning board, and vice versa.
- **The source app and source type** of the job behind the record. A "Create absence in BC Germany" action only makes sense for records that came from BC Germany; a "Show in CRM" action only makes sense for jobs whose source type is `service order`. The action URI form has source-app and source-type filters precisely so a tenant connected to multiple back offices doesn't end up with a dozen irrelevant items in every menu.

The **description** column is what the planner sees in the menu. Keep it short and verb-led ("Create absence", "Open in BC", "Email customer") - this is the entire UX of the feature.
## Constructing the URL
Most back-office systems make this easy. Business Central, for example, [documents its web-client URL format](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-web-client-urls) - find the URL that opens the record you want, then drop in placeholders where the record-specific values go.
The placeholders you can use depend on the entity type the action URI is bound to:
### Planned tasks (appointments)
Available from the planning board and the planned task list.
| Placeholder | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------- |
| `#SOURCEAPP#` | The identifier of the system that owns this record. Set on the job level. For example, BC GERMANY, BC USA |
| `#SOURCETYPE#` | The type of record. For example, service order, production order. |
| `#START#` | The start date of the appointment. |
| `#END#` | The end date of the appointment. |
| `#RESOURCE#` | The resource assigned to this appointment. |
| `#APPOINTMENTID#`| The appointment's identifier. |
| `#DATABASEACTION#`| The action type: `A` (Add), `E` (Edit), `D` (Delete). |
### Unplanned tasks
Available from the open task list, the map, and the route sequence grid.
| Placeholder | Description |
| -------------- | --------------------------------------------------------------------------------------------------------- |
| `#SOURCEAPP#` | The identifier of the system that owns this record. Set on the job level. For example, BC GERMANY, BC USA |
| `#SOURCETYPE#` | The type of record. For example, service order, production order. |
| `#JOBNO#` | The external identifier of the job. |
| `#TASKNO#` | The external identifier of the task. |
## Pushing action URIs from the back office
You do not have to maintain action URIs by hand in Dime.Scheduler - the back-office connector can push them too. The example below registers an action from Business Central that opens BC's "create new absence" page when the planner triggers it on a planning-board appointment.
```AL
procedure SendActionURLNew()
var
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DSWebServiceManagement: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DSWebServiceManagement.InitParameters();
DSWebServiceManagement.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebServiceManagement.AddParameter('SourceType', "Source Type");
DSWebServiceManagement.AddParameter('UrlType', '0');
DSWebServiceManagement.AddParameter('Url', DimeDSDimeSchedulerMgt.GenerateActionUrl("Action Report No.", 'E'));
DSWebServiceManagement.AddParameter('UrlDesc', "New absence");
DSWebServiceManagement.AddParameter('DefaultUrl', DSWebServiceManagement.HandleBool(false));
DSWebServiceManagement.CallDimeSchedulerWS('mboc_upsertActionUrl');
end;
```
The codeunit produces a templated URL that BC's URL parser understands:
```text
https://mybc/?company=CRONUS%20BE
&report=2087625
&filter=DSApp.Source Type:#SOURCETYPE#
&filter=DSApp.Database Action:#DATABASEACTION#
&filter=DSApp.Start:#START#
&filter=DSApp.End:#END#
&filter=DSApp.Resource Report Filter:#RESOURCE#
```
When the planner triggers this from an appointment, Dime.Scheduler substitutes each placeholder with the appointment's actual values and opens the URL.
## Reference
### Scopes
The **Type** column on the action URI grid sets the scope. The four options:
- Map
- New appointments
- Existing appointments
- Open tasks
### Shortcut keys
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
---
## Appointment Templates
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
import ReactPlayer from 'react-player'
} items={[{title: "Appointment Templates", link: "https://app.dimescheduler.com/appointmenttemplates"}]} />
The standard appointment card in Dime.Scheduler has the obvious fields - start, end, resource, description - and that is enough for a lot of scheduling work. It is not enough the moment a customer needs the technician to record a serial number, a meter reading, a "did the customer sign off" checkbox. Appointment templates are how you add those fields without writing code.
A template adds custom tabs and fields to the appointment card; whatever the user fills in flows back to the back-office application alongside the standard appointment data. You can define multiple templates and bind each one to a different kind of task, so the card adapts to the work at hand instead of becoming a wall of irrelevant fields.
## How templates, sections, and fields fit together
The model has three layers:
- A **field** is the smallest unit - a name, a data type, a default value. Fields live in their own grid and can be reused.
- A **section** groups fields into a single tab on the appointment card.
- A **template** groups sections, and is what gets assigned to a task type.
The same field can appear in more than one section, and the same section can appear in more than one template - that reuse is what keeps your data model consistent across the business.
```cmd
.
+-- Appointment Template 1
| +-- Section 1
| +-- Text field 1
| +-- Number field 1
| +-- Section 2
| +-- Text field 2
| +-- Date field 1
| +-- Yes/No field 1
+-- Appointment Template 2
| +-- Section 3
| +-- Text field 1
| +-- Yes/No field 1
| +-- Section 4
| +-- Text field 3
| +-- Date & time field 1
```
## Building a template from scratch
The whole setup happens on a single page (`Settings → Appointment Templates`) with six grids that mirror the three-layer model:

- The **top three grids** define the entities themselves: templates, sections, fields.
- The **fourth grid** (below fields) is for the options list of `Select` fields.
- The **two assignment grids** are contextual to whatever you have selected: they show which fields belong to the selected section, and which sections belong to the selected template.
The workflow is bottom-up: define the fields first, group them into sections, then assemble sections into templates.
### Defining fields
The fields grid is where you create the actual data points the user will fill in.

| Field | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Code | The name with which the field is stored. This code is also sent to the back-office application as the identifier of the field. |
| Display Name | The label or caption of the field on the appointment card |
| Type | Specify the type of data the user should enter in the field. The options are: Text field NumberBoolean (shows a Yes/No or True/False check box)DateDate & TimeSelect (the user can select from a predefined list of values, see also Field Options) |
| Mandatory | Specify whether this field is required. If a field has a default value, then this requirement will automatically be fulfilled. |
| Default | Enter the default value for the field. The user can always modify this value afterwards in the appointment card. |
For `Select` fields, the options grid below the fields grid is where you list what the user can pick from:
| Field | Description |
| ------- | --------------------------------------------------------------------------------------------------------------- |
| Code | The code with which the option is stored in the database and which is also sent to the back-office application. |
| Name | The label or caption of the option shown to the user |
| Default | You can specify one of the options to be the default value for the Field. |
### Defining sections and templates
Sections and templates have nearly identical fields - both just need a code and a name. The template grid has one extra column:
| Field | Description |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Code | The identifier of the template. This code is used - by the back-office application - to define which template to use for a task. |
| Name | The name is only used for managing templates and allows you to give a more elaborate description than with the code. |
| Default | If a task is sent by the back-office application without a template code, then the template marked as default is automatically applied for that task. That is of course only when one of the templates is marked as default. If a task is sent by the back-office application with a template code then that template is applied and the default is overridden. |
### Wiring fields into sections, sections into templates
The two assignment grids work the same way on both layers:
- Select the parent (a section, or a template) on the left.
- Click the **+** icon next to the child you want to add (a field, or a section). The child appears in the assignment grid on the right.
- Reorder with the up/down arrows on the assignment grid; the order is what the user sees on the card.
- Remove with the **Delete** button on the assignment grid (this unlinks the child, it does not delete the field or section itself).
## Read more
- [Appointment templates](user/features/appointment-templates.md)
---
## Audit log
When a setting changes in your tenant, somebody made it change. The audit log is how you find out who, what, and when - whether you are tracing a misconfiguration back to its source, satisfying a compliance review, or just trying to understand why a calendar suddenly looks different than it did last week.
Every create, update, and delete action across the administration pages is recorded with a timestamp, the category of thing that was touched, the action type, the user who made the change, and a descriptive message. Nothing happens "silently" in the administration area - if a setting moves, it leaves a trail here.
## Finding the entry you need
The grid grows quickly, so the page leans on three ways to narrow it down:
- **Filter by category** to scope to a single area (calendars, users, connectors, and so on).
- **Search messages** for free-text matches when you know roughly what changed but not where it lives.
- **Narrow by date range** when you remember when, not what.
When you need to take an audit entry outside of Dime.Scheduler - to send to an auditor, to attach to a ticket, to archive - use the **Export** button to produce an Excel (`.xlsx`) file of the current view.
## Data retention
Audit log data is kept for **90 days**, after which records are cleaned up automatically. Before removal, a CSV of the deleted records is sent to the email address configured in the **Audit Cleanup Email** field of the [application setup](/administration/setup). If you need a longer audit history, exporting the CSV (either through the dump or via the export button) is how you get it.
## Reference: tracked categories
Create, update, and delete actions are tracked across the following entities:
| Category | Create | Update | Delete |
|---|:---:|:---:|:---:|
| Action URLs | x | x | x |
| API keys | x | | x |
| Application setup | x | x | |
| Appointment fields | x | x | x |
| Appointment field options | x | x | x |
| Appointment sections | x | x | x |
| Appointment section fields | x | x | x |
| Appointment templates | x | x | x |
| Appointment template sections | x | x | x |
| Calendars | x | x | x |
| Calendar availability | x | x | x |
| Calendar days | x | x | x |
| Calendar day availability | x | x | x |
| Calendar weeks | x | x | x |
| Captions | x | x | x |
| Connectors | x | x | x |
| Data reset | | | x |
| Digests | x | | |
| Exchange rules | | x | x |
| Fields | x | x | x |
| Field template content | x | x | x |
| Languages | x | x | x |
| Links | x | x | x |
| Pane content | x | x | x |
| Pane groups | | x | x |
| Resources | x | x | x |
| Resource types | x | x | x |
| Roles | x | x | x |
| Role user actions | x | | x |
| Transactions | | x | |
| Users | x | x | x |
| User categories | x | | x |
| User filter values | x | | x |
| User groups | x | x | x |
| User group categories | x | | x |
| User group filter values | x | | x |
| User group profiles | x | x | x |
| User group roles | x | | x |
| User group time markers | x | | x |
| User profiles | x | x | x |
| User roles | x | | x |
| User time markers | x | | x |
---
## Calendars
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Calendars", link: "https://app.dimescheduler.com/calendars"}]} />
A calendar describes when work can happen: regular business hours, weekends, holidays, the occasional shorter Friday. Calendars are how Dime.Scheduler knows the difference between a normal Tuesday and Christmas Day, between a 9-to-5 office and a 24/7 crew.
Without calendars, the planning board treats every hour the same. Planners can drop appointments on public holidays without warning, overtime hides instead of standing out, and [Gantt chart](user/components/gantt.md) durations are off because the system has no way to know which days actually count as working days. The calendar setup page is where you give Dime.Scheduler that ground truth - once per tenant for the default working pattern, and as many extra calendars as you need for teams or resources that work differently.

When the calendar visualisation is enabled in [Administration > Application > Planning](/administration/setup/planning), Dime.Scheduler shades non-working time on the planning board so planners stay inside business hours by default. The same calendars drive Gantt charts, where non-working time is rendered as gaps and task duration is recalculated to skip them.
## How a calendar is built
Every calendar starts from the same baseline: a set of **business hours** (the time slots that count as working time, for example 08:00-12:00 and 13:00-17:00) and a definition of which days are **weekends** (Saturday and Sunday by default). That baseline is what applies to every date unless something more specific overrides it.
On top of that baseline you can add four kinds of overrides, each more specific than the last:
- **Week days** recur every week, like "Fridays we leave at 16:00."
- **Week overrides** apply a non-standard week within a date range, like "Last week of June, Saturday is a working day."
- **Day overrides** target a single date, like "December 25th, closed."
- **Day overrides** can also flip the opposite direction: a working day that would normally be closed (a Saturday open-house, for example).
You will usually start by defining the company baseline once, then layer overrides as the exceptions come up. Most setups stabilise quickly; the calendar is something you touch when the holiday list rolls over, not a daily concern.
## How conflicts resolve
The interesting question is what happens when two rules apply to the same date. Day overrides win over week overrides, week overrides win over week days, and the baseline (business hours + weekends) loses to all three. In short: the more specific the override, the higher its priority.
To make this concrete, take a calendar with:
| Description | Value |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Business hours | 08:00-12:00 and 13:00-18:00 |
| Weekend | Saturday and Sunday |
| Day overrides | December 25th and January 1st |
| Week overrides | Between June 28th and July 5th: Monday until Friday: 08:00-12:00 and 13:00-18:00Saturday: 08:00-12:00 and 13:00-15:00Sunday: non-working |
| Week days | Fridays are working days between 08:00-12:00 and 13:00-17:00 |
Walk through the conflicts:
- **Friday the 3rd of July.** The week override beats the week day, so employees work 08:00-12:00 and 13:00-18:00, not the early-finish Friday.
- **Saturday the 4th of July.** Normally non-working, but the week override pulls it in: 08:00-12:00 and 13:00-15:00.
- **Friday the 25th of December.** Day override beats everything below it, so nobody works.
- **Wednesday the 1st of January.** Day override marks it non-working. No conflict to resolve.
- **Thursday the 2nd of January.** No overrides apply, so the baseline takes over: 08:00-12:00 and 13:00-17:00.
The hierarchy is unchanged by inheritance (covered next). A child calendar's overrides do not get "promoted" because they came from a parent - the override type alone decides priority.
## Inheriting one calendar from another
Most organisations end up with a small set of calendars that mostly look alike: the company baseline, then a couple of variations for regional offices or specific teams. Rather than maintaining each one from scratch, you can mark a calendar as a child of another and override only what differs.
```cmd
.
+-- Base calendar
| +-- Business hours
| +-- 08:00 - 12:00
| +-- 13:00 - 17:00
| +-- Holidays
| +-- December 25th
| +-- January 1st
| +-- May 1st
| +-- Child calendars
| +-- Region EAST calendar
| +-- Business hours
| +-- 09:00 - 13:00
| +-- 14:00 - 18:00
| +-- Holidays
| +-- June 25th
| +-- March 3rd
+-- Unrelated calendar
| +-- Business hours
| +-- 22:00 - 02:00
| +-- 04:00 - 08:00
| +-- Holidays
| +-- November 11th
| +-- May 30th
| +-- February 29th
```
In this example the "Region EAST" calendar inherits from "Base calendar," so the parent's holidays (December 25th, January 1st, May 1st) apply to Region EAST too. Business hours are not inherited, because the child defines its own. There is no limit on nesting depth, and a child can itself be the parent of another calendar.
## Giving a specific resource its own calendar
Sometimes a single resource needs to follow a different schedule for a period - a part-time contract, a temporary assignment in another region, a sabbatical. Open the resource calendars section, click **Add**, then pick the resource, the alternative calendar, and the date range it applies for (the "until" date is exclusive). You can stack multiple period-specific calendars for the same resource without conflicts.
## Working with the calendar view
The grid in the middle of the screen shows the calendar day by day, with colors explained in the legend on the right. Edits are not saved automatically: after changing any setting, click the sync button. If you navigate away without syncing, your changes are lost.
To create a calendar, click **Add**, give it a code (any internal reference) and a name. Check **default** to make it the company's standard calendar on the planning board.
## Reference
### General settings
| Setting | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Weekends are work days | When checked, weekends will not be marked as non-working time |
| Weekend first day | Determines the first weekend day in your company (by default Saturday) |
| Weekend second day | Determines the second weekend day in your company (by default Sunday) |
| Days per month | This will not affect the actual days per month but is used to convert units of measure in the Gantt charts. E.g. when inserting 1 month for a Task duration a conversion is done to the corresponding number of days inserted in this field. |
| Days per week | This will not affect the actual days per week but is used to convert units of measure in the Gantt charts. E.g. when inserting 1 week for a Task duration a conversion is done to the corresponding number of days inserted in this field. |
| Hours per day | This will not affect the actual hours per day but is used to convert units of measure in the Gantt charts. E.g. when inserting 1 day for a Task duration a conversion is done to the corresponding number of hours inserted in this field. |
### Override types
- **Business hours.** Any number of time slots that count as working time on a normal day.
- **Day override.** A single date (e.g. May 23rd 2019), marked as non-working, working with custom hours, or default.
- **Week day.** A recurring day of the week (e.g. all Mondays), useful for patterns like an early Friday finish.
- **Week override.** A non-standard week within a date range. A week override is only saved when at least one day inside it is non-standard - otherwise it would have no effect.
## Read more
- [Calendar-based appointment duration recalculation](user/features/appointment-recalculation.md)
- [Planning board](user/components/planningboard/index.md)
- [Gantt chart](user/components/gantt.md)
---
## Field Templates
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Field Templates", link: "https://app.dimescheduler.com/fieldtemplates"}]} />
A appointment coming in from the back office arrives with raw data: a job number, a task description, a customer name. What the planner actually wants to see on the appointment is something more useful - a *subject line* and a *body* that summarises what this booking is about at a glance. Field templates are how you compose that view dynamically, by stitching together the raw data fields with literal text into a readable string.
The result is a Dime.Scheduler that speaks the customer's language without touching the back office. The template stays on Dime.Scheduler's side; the back office keeps sending its standard data; the planner reads "Service call - ACME Corp - 14 Main Street" instead of "Task 4729".

## How a template is composed
A template is an ordered list of three kinds of building block:
- **A field** is a placeholder that resolves to a property on the job or task when the template renders. Drop in `JobNo` and the rendered output has the job number; drop in `CustomerName` and you get the customer's name.
- **Free text** is whatever literal text you want around the fields: separators, labels, punctuation. "Service call:", " - ", " for ", and so on.
- **A new line** inserts a line break. Useful in the body field, less so in the subject.
You compose the template by ordering these blocks. Render time fills in the field values and concatenates everything into a single string.

## Building a template
Select the template you want to edit on the left, and it loads on the right.
To add a block, drag one of the three block types from the top-right corner and drop it where you want it in the template - above or below the existing blocks. Then either pick the source field (for a field block) or type the literal text (for a free-text block).

To reorder blocks, drag and drop them, or use the caret buttons. To remove a block, click the trash icon next to it.

## Saving
Edits stay in a *pending* state until you commit them - the **Save** button on the right-hand side is what writes them to the database. Until then, your changes live only in the browser.
:::caution
Changes to field templates do not affect existing appointments. Only new records from the back-office system render with the updated template. If you need an existing appointment's subject or body to update, you typically have to push the appointment through the back office again.
:::
## Planning board tooltip
This page has a second tab, **Tooltip**, where you configure the appointment tooltip shown on the planning board. It uses the same drag-and-drop builder but draws from a richer field library. See [Planning board tooltip](administration/settings/planning-board-tooltip.md) for the full walkthrough.
---
## Filter groups
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Filter groups", link: "https://app.dimescheduler.com/filtergroups"}]} />
A filter group is a category - "Region", "Skill", "Truck type" - with a list of filter values inside it ("North", "South", "Plumbing", "18-wheeler"). You assign those values to resources, tasks, and other entities so the planner can filter the planning view the same way the business actually thinks about its work.
Filter groups used to be a back-office-only concept - the only way to create or rename one was through the API or the source system. That worked for setups where the back office naturally owned the taxonomy. For everything else (a tenant adding a new skill category, a planner needing a quick "VIP customer" tag, a setup that has no back office at all), the round-trip was painful. The filter groups page closes that loop: administrators can now add, rename, and delete groups and their values directly inside Dime.Scheduler.
## How the page is laid out
The screen splits in two: the left column lists the filter groups; the right column shows the values inside whichever group is currently selected. Selecting a group on the left loads its values on the right - everything you do on the right is scoped to the group you have selected.
## Working with groups
- **Add** a group with the **Add group** button at the top of the left column; type the name and confirm with Enter or the green check button.
- **Rename** by hovering over a group, clicking the pencil icon, editing the label, and confirming.
- **Delete** by hovering over a group, clicking the trash icon, and confirming. Deleting a group removes all of its values along with it - there is no recycle bin, so make sure no resources or tasks depend on the values inside before you confirm.
## Working with values
The right column only becomes active once you have selected a group on the left. From there the workflow is the same:
- **Add a value** with the **Add value** button; type and confirm.
- **Rename** with the pencil icon on hover.
- **Delete** with the trash icon on hover.
## Read more
- [Resource filters component](user/components/resourcefilters.md)
---
## Geocoding
Geocoding turns an address into a coordinate so the map knows where to drop a pin. It underpins the [map component](user/components/map.md) and everything that depends on it (route planning, distance calculations, dispatch decisions), so getting it right matters.
Two entities appear on the map and therefore need coordinates:
- [Jobs](administration/settings/jobs.md), via their **Site Address**.
- [Resources](administration/settings/resources.md), via their **Home Address**.
## How Dime.Scheduler reads an address
Each job and resource exposes three address field sets. Dime.Scheduler uses them in descending order of accuracy and stops at the first one that yields a usable result:
1. **Latitude and longitude.** When a coordinate already exists, those values are used directly. No text parsing is needed and the record is considered geocoded.
2. **Separate address fields.** When the coordinate is missing, Dime.Scheduler combines the dedicated street, street number, zip code, city, state, and country fields and asks the geocoding provider to resolve them.
3. **Full address.** When the structured fields are incomplete, Dime.Scheduler falls back to the free-text address field.
For resources, this is the "Home Address" set on the [resources page](administration/settings/resources.md). For jobs, it is the "Site Address" range on the [jobs page](administration/settings/jobs.md).
:::tip
Always populate the dedicated columns when you can. Free-text addresses are notoriously hard to interpret because there is no universal format: in some countries the street number comes before the street name ("1 Rue d'Avignon"), in many others it comes after ("Rue d'Avignon 1"). Structured fields remove that ambiguity.
:::
## Getting geocoding to work end-to-end
Follow these steps the first time you set up an environment, after a bulk data import, or whenever the map shows fewer pins than you expect.
### 1. Make sure addresses arrive in the right columns
In the [resources view](administration/settings/resources.md) and the [jobs view](administration/settings/jobs.md), confirm that addresses populate the structured fields (street, zip, city, country) rather than landing entirely in the free-text address field. The originating back-office system is the master here, so the cleanest fix is usually upstream of Dime.Scheduler.
### 2. Put an ISO country code in the country field
The country field on the job and resource address is sent to the provider as-is, with no mapping in between. Type an [ISO 3166](https://www.iso.org/iso-3166-country-codes.html) code ("US", "BE", "DE") rather than a localized name ("Belgique", "Belgien", "Belgium"). If the value comes from a back-office system, normalize it there before it reaches Dime.Scheduler.
### 3. Trigger geocoding
Three triggers exist, in increasing scope:
- **Per-row geocode button** . On the jobs and resources grids, every row has a small map-pin button. Use it after fixing a single address: it geocodes that one record on demand.
- **Geocode All** in the toolbar. This geocodes every job (or resource) that does not yet have coordinates. It is the same routine the nightly job runs, just on demand.
- **Nightly background job.** Dime.Scheduler runs a scheduled job that picks up every job and resource without coordinates and geocodes them in the background. This is what processes newly imported records overnight without any manual action.
### 4. Refresh and inspect the map
After the geocoder finishes, refresh Dime.Scheduler. Records that resolved successfully now appear on the [map component](user/components/map.md). Records that did not resolve stay coordinate-less and are skipped by the map.
## The blacklist
To avoid retrying the same unresolvable address on every run, Dime.Scheduler maintains a **blacklist** of address signatures it has already tried and failed to resolve.
An entry is added to the blacklist when one of the following happens:
- The address looks too incomplete or implausible to even attempt (for example a free-text field with only a few characters, or a structured address missing too many parts). These are rejected before the provider is called.
- The provider was called but returned no coordinate. The address is recorded so the next nightly run does not retry it.
A blacklist entry stores the address signature together with the country code, so the same street name in two different countries is treated as two distinct entries.
### What this means in practice
- A record on the blacklist is **silently skipped** by the geocoding routines, including the per-row button, the **Geocode All** button, and the nightly job. The record stays without coordinates and stays off the map.
- The blacklist exists per address signature, not per record. Many jobs that share the same incomplete site address will all be skipped by a single entry.
- Fixing the address in the back-office system or directly on the record does not automatically remove the old signature from the blacklist. If the address you just corrected is still not getting pinned on the map, the previous (broken) version of that address may still be blacklisted under a slightly different signature, or the corrected address may resolve to a new entry. In both cases the per-row geocode button is the fastest way to verify what the geocoder actually does with the new input.
## Tips for accurate results
Geocoding is only as good as the input. The rules below cover the failure modes we see most often. Most are easier to enforce in the back-office system than to clean up after the fact.
### Prefer structured fields over free text
Free-text parsing has no universal rules: in some countries the number comes before the street ("1 Rue d'Avignon"), in many others it comes after ("Rue d'Avignon 1"). The provider has to guess. Structured fields remove the guess.
### Keep the address fields strictly address
The street and street number fields should contain a street and a street number, nothing else. Extras that look harmless to a human are noise to the geocoder:
- **No floor, suite, building, or apartment information.** "10 Main Street, Building B, 3rd floor, Suite 301" parses worse than plain "10 Main Street". If the back-office system needs the suite, store it in a separate notes field that Dime.Scheduler does not send to the provider.
- **No company or contact names.** "Acme Corp, 10 Main Street" belongs as two fields: company name on the customer record, "10 Main Street" in the address.
- **No directional hints.** "10 Main Street (next to the petrol station)" cannot be resolved to a coordinate and usually ends up blacklisted.
- **No placeholders.** "TBD", "Unknown", "Same as billing", "-", and empty strings will never resolve.
### Use a real street number, not a range
"10-12 Main Street" forces the provider to pick a side. Use the primary entrance number.
### Avoid PO boxes for site addresses
A PO box has no physical coordinate. The provider will either fail or return the post office, which is not where the work happens.
### Make the postcode match the city
A London postcode with "Manchester" in the city field forces the provider to guess. Most back-office systems can validate postcode/city pairs at entry time.
### Be specific enough to be unambiguous
"Papa John's Pizza, Rome" is ambiguous: the provider is likely to return the Italian capital rather than the small city in Floyd County, Georgia. Include the country, and the postcode when you have it.
### If you must use free text, pick one format and stick to it
Choose one ordering (for example "street number, street, postcode, city, country") and apply it consistently across the dataset. Mixing orderings means you cannot reason about hit rates.
### Fix the master data upstream
Repeated failures usually trace back to bad master data in the back-office system. Cleaning the address at the source benefits every system that consumes it, and prevents the same record from being re-imported broken on the next sync.
## Troubleshooting
| Symptom | Likely cause | What to try |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| A record does not appear on the map after geocoding | The address could not be resolved and the record has no coordinates | Verify the structured fields and the country code, then click the per-row geocode button to retry |
| Many newly imported records are missing from the map | Nightly job has not run yet | Wait for the next nightly run, or click **Geocode All** in the toolbar |
| A record stays uncoordinated even after you corrected the address | The previous version of the address is on the blacklist, or the corrected address is unresolvable too | Use the per-row geocode button to see what happens with the new input. If it still fails, contact support to investigate the blacklist |
| The pin lands in the wrong country or city | Ambiguous free-text address, or a non-ISO country value | Move the data into the structured fields and ensure the country uses an ISO 3166 code |
## Read more
- [Jobs](administration/settings/jobs.md)
- [Resources](administration/settings/resources.md)
- [Map component](user/components/map.md)
- [Localization](administration/settings/localization.md)
---
## Content Management
import { FaSlidersH } from 'react-icons/fa';
This section is where the *substance* of the planning view comes from. A planner can only assign work to resources that exist, see colors that have been defined, plan against calendars that are configured, and use action URIs that an administrator has set up. Every page here defines another piece of that substance - resources, calendars, indicators, templates, captions, and more.
Most of the data here flows in from the back-office application; what you do on these pages is the editing, enriching, and curating on top - the work the back office cannot do for you.
Open this section from the icon in the sidebar menu.
import DocCardList from '@theme/DocCardList';
---
## Indicators
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
import ReactPlayer from 'react-player'
} items={[{title: "Indicators", link: "https://app.dimescheduler.com/indicators"}]} />
A planner stares at a dense planning board for hours. Anything that helps them spot a pattern without reading a single word of text saves real time. Indicators are how Dime.Scheduler makes that possible: small visual signals attached to the planning data, each answering a different question about the work.
This page is where you define them. The back-office application is usually the master for the indicators themselves - it sends them across along with the planning data. What it typically does *not* send is what each one should look like, because that is a visualisation choice rather than a master-data choice. So in practice you create indicators here only when the back office isn't doing it, and you spend most of your time deciding what things look like.
## The four kinds of indicator
Each kind has its own tab at the top of the page, and each answers a different question.
### Categories
A category answers *what kind of work is this*. It is the primary classification of an appointment: order type, work type, status, truck load - whatever the single most useful split is for your planners. An appointment has exactly one category.
Categories are the only kind with an extra column, **Outlook Preset**, which maps the category onto one of the fixed Outlook category colors. That mapping only matters when appointments are synchronised to Exchange, so the color a planner sees in Outlook lines up with the one on the board. Categories are also the unit used for [user assignments](administration/user/users.md#attaching-permissions-groups-and-security-data), which lets you steer who is allowed to plan which type of work.
### Time markers
A time marker answers *what is happening during this appointment*. It is a second, independent signal on the same appointment: a lunch break, standby, travel, a customer meeting inside a longer job. Like the category, an appointment has exactly one.
The distinction between a category and a time marker is worth getting right up front, because both are single-valued and both are colors. Use the category for the classification you sort and report on, and the time marker for the state that changes during the day.
### Pins
A pin answers *what kind of thing is this on the map*. Pins color the markers in the [map component](/user/components/map#marker-colors), typically by skill, vehicle type, or region, so a planner scanning a geography sees the right technician without opening anything.
Pins live on tasks rather than on appointments, and they are the only kind that has no equivalent on the planning board.
### Tags
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases. Not recommended for production-critical workflows yet.
:::
A tag answers *what else do I need to know*. It is the one kind that is not single-valued: a task or an appointment can carry any number of tags at once. That makes tags the place for everything that does not fit in the single category slot - urgent, night shift, call the customer first, waiting on parts - without inventing combined category values like "Urgent - installation".
Tags also differ in that they are *referenced* by a stable code rather than copied onto the record, and they render as icons rather than as a color fill. See [Tags](/user/features/tags) for what planners do with them.
## How each one is rendered
Defining an indicator is half the job. The other half is knowing where it will actually show up, because that is what decides whether a given color choice reads well or gets lost.
| Indicator | Where it appears |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Category | The fill color of the appointment bar on the [planning board](/user/components/planningboard/appointments#category). It is the largest area of color on the board, so it dominates the visual read. |
| Time marker | A [colored horizontal bar](/user/components/planningboard/appointments#time-marker) drawn on the appointment. A thin accent rather than a fill. |
| Pin | The color of the resource marker in the [map](/user/components/map#marker-colors). |
| Tag | A row of small icons at the top of the appointment bar, each in the tag's own color, plus colored chips in the Tags column of the task and route grids. |
There are more visual signals on an appointment than the four you manage here - importance and notifications are driven by the back office rather than by a catalog. For the whole picture, and for advice on choosing a palette that survives a dense board, see [Appointment visualization](/user/setup/visualization).
## Managing the catalog
Every tab works the same way. Click **Add** for a new row, or the pencil icon on an existing row to edit it. The count next to each tab name tells you how large the catalog already is, and the search box filters the list, which starts to matter once a catalog grows past a screenful. Each tab can also export its contents to CSV.

To remove indicators, use the trash icon on a single row, or tick the checkboxes and delete several at once. There is one safety rail on categories, time markers and pins: indicators that are still in use by jobs, tasks, or appointments cannot be removed. Clear the references first (or wait for the back office to clear them), then come back and delete.
Tags are the exception. Deleting a tag does not touch the records that reference its code. Those references stay in the data and simply stop rendering, so recreating the tag under the same code brings them back.
## Setting the color
Put a row in edit mode, then click the color cell to open the picker.

Pick visually with the color area and sliders, or paste a hex or RGBA value directly when the design team has handed you a palette. New rows start out on a default blue, so a catalog that was imported from the back office and never styled will look uniformly blue until someone goes through it.
Which colors to actually pick is a design decision rather than a mechanical one, and it is worth a few minutes before you start clicking: see [choosing colors that survive a real board](/user/setup/visualization#choosing-colors-that-survive-a-real-board).
## The extra fields on tags
Tags carry three fields the other tabs do not, all of which follow from being referenced by code:

| Field | Description |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Code | The stable identifier that tasks and appointments store. Required, and **locked once the tag is created** - a rename must not orphan the records that reference it. Rename the name instead. |
| Icon | The icon rendered on the appointment bar. Pick one from the icon browser, which offers a curated set of suggestions as well as the full catalog. |
| Hidden | Hides the tag from the planner's pickers and from the indicators panel. The tag keeps rendering on records that already carry it; it just cannot be applied by hand any more. Use it to retire a tag, or for tags only the back office is allowed to set. |
:::tip
Codes travel over the API, so choose them the way you would choose an ERP code: short, uppercase, and stable. `NIGHTSHIFT` ages better than `Night shift (new)`.
:::
## Reference
### Shortcut keys
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
## Read more
- [Appointment visualization](user/setup/visualization.md)
- [Indicators panel](user/components/indicators.md)
- [Tags](user/features/tags.md)
- [Assigning users to categories](administration/user/users.md#attaching-permissions-groups-and-security-data)
- [Planning board visual indicators](/user/components/planningboard/appointments#visual-indicators)
---
## Jobs
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Jobs", link: "https://app.dimescheduler.com/jobs"}]} />
A job is the unit of work that everything else hangs off: every task, every appointment traces back to one. Jobs flow into Dime.Scheduler from the back office, so most days an administrator does not have to think about them - until a planner reports an address that won't geocode, a stale order that nobody planned, or a duplicate job nobody can find in the back office anymore. This page is where you sort those situations out.

## Geocoding addresses
The biggest reason to come here is geocoding. A job without coordinates cannot show up on the map, and planners working with map-based dispatching depend on every relevant job being plotted. There are two ways to trigger geocoding:
- The **Geocode All** toolbar button runs across every job that does not yet have coordinates. It is the same routine that runs nightly to pick up new arrivals, so use it when you want results faster than waiting for the next nightly pass.
- Each row has a small geocode button that geocodes just that job. Reach for it when an individual job's address changed and you don't want to wait.
For the routine to produce sensible results, the address needs to be unambiguous. "Papa John's Pizza, Rome" could be the Italian capital or a small city in Floyd County, Georgia - the algorithm picks one and you may not like which. Use international conventions like ISO 3166 country codes; the [localization view](localization.md) maps localized country names to their ISO equivalents to make this easier. Read more about [geocoding](geocoding.md).
## Inspecting a job's tasks
Open a job's row to expand a side panel listing every open task linked to it. From here you can sort, filter, and (covered below) delete tasks one by one - useful when you need to clean up a single task without disturbing the rest of the job.
## Cleaning up jobs and tasks
Stale jobs and tasks accumulate: a back-office system imported an order that was later cancelled, a connector pushed duplicates during a migration, a customer reported the wrong job number. This page lets planners and administrators delete them directly, without having to go back to the source system.
### Deleting jobs
1. Select one or more jobs in the grid (checkboxes, multi-select supported).
2. Click **Delete** in the toolbar.
3. The toolbar switches to a confirmation strip. Optionally tick **Also delete linked appointments** to cascade the delete to every appointment backed by the selected jobs.
4. Confirm.
With the cascade option on, appointments are removed in the background and a notification fires when the cleanup is done.
### Deleting tasks
Tasks are deleted from the per-job side panel:
1. Expand the job's tasks side panel.
2. Select one or more tasks.
3. Click **Delete** and confirm. The same **Also delete linked appointments** option applies.
:::caution
Deleting jobs or tasks is permanent within Dime.Scheduler - the records are gone. If the data originated in a back-office system, however, the next sync may re-import it. For jobs that should stay gone, delete them at the source too.
:::
## Read more
- [Country localization](localization.md)
- [Geocoding](geocoding.md)
---
## Links
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Links", link: "https://app.dimescheduler.com/links"}]} />
A **link** is a shortcut that shows up in the expanded navigation menu inside Dime.Scheduler. Use them to put the resources your planners need every day - a customer portal, an internal SharePoint folder, a runbook in Confluence - one click away from the planning view, instead of three browser tabs over.
Anything addressable by a URL works: websites, web apps, network shares, documents in a DMS like SharePoint.

## Setting them up
The links page is a small grid with three columns:
- **Name** - the text the user sees in the menu.
- **Sequence** - the order in which the link appears.
- **URI** - where it points to.
Sort, filter, add, and remove rows the same way as in every other grid.

## When to use a link vs. an action URI
Links are static. They always point to the same place, no matter where in the application the user is. An action URI is dynamic and *contextual*: it picks up data from the record the planner triggers it on (the appointment's resource, the task's job number, and so on) and substitutes those values into the URL. If the link needs to "know" anything about the record, you want an action URI - if it just needs to take the user somewhere, a link is the simpler choice.
## Reference
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
---
## Localization
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
import ReactPlayer from 'react-player'
} items={[{title: "Localization", link: "https://app.dimescheduler.com/localization"}]} />
Dime.Scheduler ships fully translated, but the vocabulary it ships with is generic on purpose. A field called "Free Text 1" means nothing to a planner; once you rename it "Customer Invoice Reference", the same field suddenly tells the user what to type in it. That gap - between the system's generic terms and the customer's actual business language - is what this page is for.
The localization view does two related jobs: it lets you **override the wording** for any translatable field, group, or section in every language the tenant supports, and it lets you **control formatting** for how dates and numbers are displayed to each locale. Together they are what makes the application feel native rather than translated.
## Captions
Most localization work is captions. Pick a field on the left, and the **Captions** pane on the right shows one row per configured language - fill in the translation for each one. The same field's caption can read differently in English, Dutch, and German, because the underlying data binding is the same in all three.

The **Context** column tells you what kind of thing you are translating. The three most common contexts:
- **DATABASEFIELD** - fields shown in grids and the Task Details panel.
- **TARGETPANEGROUP** - the section groups in the Task Details panel.
- **APPOINTMENTFIELDVALUE** - sections and fields created via [Appointment Templates](administration/settings/appointmenttemplates.md).
## Formatting
Translation gets the words right; formatting gets the *shape* right. A Belgian planner expects `08/05/2018` with the day first, an American expects `5/8/2018`; a Belgian expects `1 234,56`, an American expects `1,234.56`. The **Format** column on the fields grid is where you control this per field.
### Date and time
Format strings combine the codes below. For example, `D - d/m/Y` renders as `Mon - 08/05/2018`.
| Format | Description | Example returned values |
| ------ | ----------------------------------------------------------------------- | ------------------------------------- |
| d | Day of the month, 2 digits with leading zeros | 01 to 31 |
| D | A short textual representation of the day of the week | Mon to Sun |
| j | Day of the month without leading zeros | 1 to 31 |
| l | A full textual representation of the day of the week | Sunday to Saturday |
| N | ISO-8601 numeric representation of the day of the week | 1 (for Monday) through 7 (for Sunday) |
| w | Numeric representation of the day of the week | 0 (for Sunday) to 6 (for Saturday) |
| z | The day of the year (starting from 0) | 0 to 364 (365 in leap years) |
| W | ISO-8601 week number of year, weeks starting on Monday | 01 to 53 |
| F | A full textual representation of a month, such as January or March | January to December |
| m | Numeric representation of a month, with leading zeros | 01 to 12 |
| M | A short textual representation of a month | Jan to Dec |
| n | Numeric representation of a month, without leading zeros | 1 to 12 |
| t | Number of days in the given month | 28 to 31 |
| L | Whether it's a leap year | 1 if it is a leap year, 0 otherwise. |
| Y | A full numeric representation of a year, 4 digits | Examples: 1999 or 2003 |
| y | A two digit representation of a year | Examples: 99 or 03 |
| a | Lowercase Ante meridiem and Post meridiem | am or pm |
| A | Uppercase Ante meridiem and Post meridiem | AM or PM |
| g | 12-hour format of an hour without leading zeros | 1 to 12 |
| G | 24-hour format of an hour without leading zeros | 0 to 23 |
| h | 12-hour format of an hour with leading zeros | 01 to 12 |
| H | 24-hour format of an hour with leading zeros | 00 to 23 |
| i | Minutes, with leading zeros | 00 to 59 |
| s | Seconds, with leading zeros | 00 to 59 |
| O | Difference to Greenwich time (GMT) in hours and minutes | Example: +1030 |
| P | Difference to Greenwich time (GMT) with colon between hours and minutes | Example: -08:00 |
| T | Timezone abbreviation of the machine running the code | Examples: EST, MDT, PDT ... |
### Decimal
Numbers are simpler. Watch one trap, though: when you write a format string, a comma is the *thousand* separator and a dot is the *decimal* separator. The user's locale then swaps those for what they expect at render time, so the same format string renders correctly in both `en-US` and `nl-BE`.
| Format | Description | Example returned values for `123456,789` |
| -------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| 0 | Show only digits, no precision | 123457 |
| 0.00 | Show only digits, 2 precision | en-US: 123456.79 nl-BE: 123456,79 |
| 0.0000 | Show only digits, 4 precision | en-US: 123456.7890nl-BE: 123456,7890 |
| 0,000 | Thousand separator, no precision | en-US: 123,457nl-BE: 123 457 |
| 0,000.00 | Thousand separator, 2 precision | en-US: 123,456.79nl-BE: 123 456,79 |
| 0.#### | Allow maximum 4 decimal places, but do not fill with zeroes at the end | en-US: 123456.789nl-BE: 123456,789 |
| 0.00## | Show at least 2 decimal places, maximum 4, but do not fill with zeroes at the end | en-US: 123456.78nl-BE: 123456,789 |
## Languages
The **Languages** tab is where you pick which languages this tenant actually exposes. Dime.Scheduler supports the full set below out of the box, but most tenants only want a subset visible to their users; this is where you switch them on and off and decide whether they appear in the language selector in the top menu.
A note on locales versus languages: a locale combines a language code with a culture code, so `en-GB` and `en-US` are both "English" but they format dates and numbers differently. If your users span the Atlantic, expose both - they will pick the one that matches their muscle memory.
### Supported languages
| Language name | Code |
| ------------- | ----- |
| Bosnian | bs |
| Croatian | hr |
| Czech | cs |
| Danish | dk |
| Dutch | nl |
| English | en |
| Estonian | et |
| French | fr |
| Finnish | fi |
| German | de |
| Greek | el-CY |
| Hungarian | hu |
| Italian | it |
| Latvian | lv |
| Lithuanian | lt |
| Maltese | mt |
| Norwegian | no |
| Polish | pl |
| Portugese | pt |
| Romanian | ro |
| Russian | ru |
| Slovenian | sl |
| Slovakian | sk |
| Spanish | es |
| Swedish | sv |
| Ukranian | uk |
| Vietnamese | vi |
### Fields
| Field | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| Code | The locale code (e.g. 'en', 'en-GB', etc.) |
| Display Name | The name shown in language selection dialogs. |
| Show in menu bar | Select this field to show the language's icon or flag in the language selection on top of the menu ribbon. |
## Read more
- [Customizing columns guide](administration/guides/customizing-columns.md)
---
## Details
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Details", link: "https://app.dimescheduler.com/details"}]} />
The [Details](user/components/details.md) component is the side panel that surfaces information about whatever the planner has selected on the board: the job behind it, the task, the appointment, all in one place. It is the "tell me everything about this record" panel - and what counts as "everything" depends on the customer.
This page is where you decide that. Different tenants need different things visible on click: a service company wants the customer's address front and center, a manufacturer cares about the BOM, a logistics outfit wants the next stop. The pane layout lets you cherry-pick which fields appear, group them into sections, and order them however the planner's eye works fastest.
## How the page is laid out
The view has two trees side by side. The **fields tree** on the left is the catalog: every field available on the job, task, and appointment entities, grouped by which entity they belong to. The **details tree** on the right is your current layout - the groups and fields that will actually render in the details component.

The output is what the planner sees in the details panel:

## Arranging fields and groups
Everything is drag-and-drop:
- Drag a field from the fields tree onto any group in the details tree to add it.
- Drag a field inside the details tree to reorder it within its group, or to move it to another group.
- Drag a group to reorder groups.
- Select a field and click delete to remove it. Selecting a group and clicking delete removes the group along with every field inside it.

## Group and field labels
The labels users actually see come from the [translations setup](administration/settings/localization.md). When you rename a group or a field's caption in this layout, you are renaming it everywhere it appears - manage those captions in one place to keep terminology consistent across the application.
## Read more
- [Details](user/components/details.md)
- [Translation](administration/settings/localization.md)
---
## Planning board tooltip
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Planning board tooltip", link: "https://app.dimescheduler.com/fieldtemplates"}]} />
When a planner hovers over an appointment on the board, they get a tooltip - a quick read of what this booking is about, without having to open the details panel. This page is where you decide what goes inside that tooltip. The right fields make the planner faster; the wrong fields turn the tooltip into noise.
The tooltip builder lives as a second tab on the [field templates](administration/settings/fieldtemplates.md) page. It uses the same drag-and-drop interface as field templates, but with a richer source: instead of the flat list of database fields, it walks the full appointment object graph (appointment → task → job → category → time marker, and so on), so you can pull values from anywhere in the chain.

## How the page is laid out
Two columns:
- **Left** - the rows that make up the tooltip, in display order.
- **Right** - the **field library**, a searchable tree of every property reachable from an appointment. Each property carries a type badge (`abc` for strings, `123` for numbers, `T/F` for booleans, `date` for dates) so you can tell at a glance what you are picking.
## Building the tooltip
- **Add a field** by dragging it from the library into the left column, or clicking it to append to the bottom.
- **Add a separator** with the **Separator** button in the toolbar. Separators are visual breaks - use them to group related lines into blocks.
- **Edit a label** on each row. The default is the property name (`Job.Customer.Name`); override it with what the planner should see (`Customer`).
- **Reorder** by dragging the grip handle, or with the up/down chevrons. Multi-row drag is supported.
- **Remove** with the trash icon on each row.
## Saving and clearing
The toolbar at the top of the tab has two finishing actions:
- **Save** persists the configuration. Until you save, edits are local to your browser - close the tab and they are gone.
- **Clear** asks for confirmation, then wipes the configuration. You still need to **Save** afterwards for the change to take effect.
## Turning the tooltip on or off
This page decides *what* the tooltip shows. Whether it shows at all is a separate switch: **Enable planning board tooltip** in [Application Setup → Planning → Visualization](/administration/setup/planning#visualization). If the tooltip configured here isn't appearing, that toggle is the first thing to check.
---
## Planning digest
:::info[Status: Preview]
The planning digest is currently in preview. Functionality and behavior may change between releases. Not recommended for production-critical workflows yet.
:::
A field crew that drives in at 7 a.m. wants their day on paper before they leave the depot. A subcontractor who works for you twice a week wants tomorrow's stops emailed by close of business. A regional manager wants a Monday morning view of the week ahead. The planning digest is the simple, recurring way to deliver those views: pick a date range, pick the resources to include, pick who gets the email, and Dime.Scheduler emails a PDF on the schedule you set.
## Configuring a digest
When you set up a digest, you tell Dime.Scheduler four things:
- **Frequency** - how often the email goes out (daily, weekly, or monthly).
- **Date range** - what window of planning to include in the PDF (e.g. "the next 7 days", "tomorrow only").
- **Recipients** - who receives the email. This does not have to be a Dime.Scheduler user, which is what makes this useful for subcontractors and field crews.
- **Resources** - which resources' schedules appear in the PDF.
The system generates and sends the PDF automatically on the configured schedule. Nothing further is required.
---
## Resource capacity
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Resource capacity", link: "https://app.dimescheduler.com/resources"}]} />
Capacity is the answer to one question: *how much work can this resource take on in this period?* Eight hours on a normal Tuesday, six on the Friday they leave early, zero on a public holiday. Capacity is what feeds the [pivot component](user/components/pivot.md), the [Gantt chart](user/components/gantt.md), and the overload indicators on the planning board - and without it, the system has no way to tell the planner that they have just booked twelve hours of work into an eight-hour day.
For most setups, capacity is generated from the [calendars](administration/settings/calendars.md) and rarely touched by hand. This page is where you do touch it: spot-check a resource's numbers when something looks off, override a specific day for a sick leave or a half-day off, or regenerate the whole grid from calendars after a calendar change.

## Reading the matrix
The view renders a matrix: one row per resource, one column per time bucket. The **view mode** controls the bucket size:
- **Day** - one column per day. Weekends always show.
- **Week** - one column per week.
- **Month** - one column per month.
In the week and month views, weekends are hidden by default; toggle the **Weekends** checkbox to bring them back when they matter (a 7-day operation, a weekend service window).
## Navigating the date range
Standard navigation lives in the toolbar:
- **<** / **>** shift the visible range one period back or forward.
- **Today** centres the range on today.
- The date range picker lets you jump to an arbitrary start and end.
The **Search** box on the right filters by resource name or resource number, and pagination at the bottom keeps large rosters from slowing the view down.
## Editing one cell at a time
Click any cell to edit the capacity value for that resource and time bucket. Setting it to `0` removes the entry (which is different from "0 hours of work allowed" - removing the entry means there is no override for that bucket and the system falls back to whatever the calendar says). Edits commit as soon as you leave the cell.
## Regenerating from calendars
When the calendar changes and you want capacity to follow, use the **Generate from calendar** button (the calendar icon in the top-right) instead of editing cells by hand. The dialog asks for:
1. The date range to cover.
2. The resources to regenerate for. The matrix's current set is the default; the search field lets you narrow it.
Then confirm. The generation runs in the background and writes capacity entries from each resource's working pattern, including calendar overrides - the same source of truth used elsewhere in the application.
:::caution
Generating from a calendar **overwrites** capacity entries for the selected resources and date range. Any manual tweaks made in the matrix are lost for the affected cells. Use this when calendars are the source of truth; avoid it after you have done hand-tuning you want to keep.
:::
## Read more
- [Resources](administration/settings/resources.md)
- [Resource types](administration/settings/resource-types.md)
- [Calendars](administration/settings/calendars.md)
---
## Resource types
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Resource types", link: "https://app.dimescheduler.com/resources"}]} />
A resource type is what tells Dime.Scheduler that a Transit van and a 38-ton truck are *different kinds of thing*, even though both are [resources](administration/settings/resources.md). The type carries planning rules: how many of "this kind" can be on a single appointment, whether the planner is allowed to clear them by accident, and so on. Without resource types, every resource looks alike to the system and the rules you actually want to enforce ("only one driver per truck", "always pair a junior with a senior") have nowhere to live.
Resource types are usually created automatically from the back office when resource data syncs over. What you do on this page is enrich those auto-created types - give them a display name, decide whether multiple resources of this type can stack on one appointment, lock down clearing if a stray click would cause real damage.
## Fields
| Field | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name | The name of the resource type. It is better to not change this value if the type was sent from the back office. |
| Display Name | The label or caption of the resource type. |
| Multiple Selection | When checked, the planner can assign more than one resource of this type to the same appointment. The board renders one linked appointment per resource - moving, resizing, or rescheduling one moves them all. Use it when a job needs a crew, not a soloist. |
| Block Resource Clearing | When checked, the planner cannot accidentally clear the selected resources for this type. Use it on roles where a missing resource would silently break planning (the driver of a vehicle, the technician on a service call). |
## Read more
- [Resources](administration/settings/resources.md)
- [Resource capacity](administration/settings/resource-capacity.md)
---
## Resources
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Resources", link: "https://app.dimescheduler.com/resources"}]} />
A resource is anything a planner can assign work to: a person, a vehicle, a piece of equipment, a meeting room. Resources are the master data of planning - every appointment on the planning board exists because a planner put it on a specific resource at a specific time.
This page is where you tell Dime.Scheduler which resources exist, who they are, and what makes each one special. Most of what you configure here is straightforward bookkeeping (names, addresses, contact details), but a handful of columns change how the resource behaves on the planning board, on the map, and against your license.

## Where resources come from
In most setups, the back-office application (your ERP, your CRM, a HR system) is the source of truth for resources. The connector synchronises them into Dime.Scheduler so planners can assign work without you maintaining a parallel list.
You can still add, update, or remove resources directly from this page, but keep one thing in mind: the back office is the master. If you rename a resource here and the back office sends an update tomorrow, the back office wins. Direct edits inside Dime.Scheduler are best treated as quick fixes between syncs, not as the long-term home of resource master data.
The link between the two systems is the **resource number**. It is the identifier that matches a resource in Dime.Scheduler to its counterpart in the back office. It looks like a technical detail, but it is the entire integration in a single field - get it wrong and updates stop flowing.
## What you can configure
The grid has more columns than you typically need to think about. Free dates, free decimals, free text fields are open for you to use however your processes require, and they pair well with proprietary logic enforced in the back office. Other columns capture the resource's address (used by the [map](user/components/map.md#resources)) or its team and department membership.
A handful of columns deserve a closer look because they do not just store data - they change what the resource can do.
### Resource type
The [**resource type**](administration/settings/resource-types.md) groups resources into logical units and unlocks planning rules. For example, a resource type can prevent a planner from assigning more than one resource of that type to the same job - useful when "only one driver per truck" is a real constraint, not a suggestion.
### GPS tracking and Exchange integration
Two flags turn on optional integrations for the resource:
- **GPS tracking** lets the planner follow the resource's live location on the map, provided a system feeds that data into the back office.
- **Exchange integration** synchronises Dime.Scheduler appointments with the resource's Exchange inbox, so assignments show up in the resource's calendar automatically.
Both are licensed features, which leads to the next point.
### How the license affects what you can change
Dime.Scheduler watches the GPS tracking, Exchange integration, and **Do Not Show** flags as you edit. Switching one on triggers a license re-evaluation, and the change is rejected if it would push you past your license threshold. You can still edit the rest of the resource, you just cannot flip that specific flag until you free up capacity (typically by disabling the flag on a different resource first).
The **Do Not Show** field hides a resource from the planning board entirely. No filter or sidebar setting brings it back until you clear the checkbox - which is intentional, because the flag is also how you exclude a decommissioned resource from license counts without deleting its history.
### Pin
By default, resources show a purple pin on the map. The **Pin** column lets you assign a [pin indicator](administration/settings/indicators.md) instead, overriding the default whenever the resource is selected.
## Putting resources on the map
Use the geocode button on a row, or the **Geocode All** button at the top, to resolve the coordinates of a resource's home address. After a refresh, geocoded resources appear on the map. Resources are also geocoded automatically once a day in the background, so the manual button is mostly for the first import or when you fix an address.
Full details are in the [geocoding documentation](administration/settings/geocoding.md).
## Mobile users need a matching email
When a resource will be used by someone logging into the mobile app, populate the resource's **email address** field correctly. The email is what links the user account to the resource: when the user logs in, Dime.Scheduler looks up the resource with the matching email and shows that resource's appointments.
The match has to be exact. If a mobile user reports an empty agenda, this is the first thing to check.
:::gotcha[Mismatched e-mail addresses]
Make sure to:
- Keep email addresses synchronised between users and resources
- Use valid, active email addresses for resources that will be accessed via mobile
- Verify the email address match if a mobile user reports not seeing their assignments
:::
For setting up the user account itself, see the [Users documentation](/administration/users).
## Reference
### Shortcut keys
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
## Read more
- [Resource types](administration/settings/resource-types.md)
- [Resource capacity](administration/settings/resource-capacity.md)
- [License](administration/license.md)
- [Indicators](administration/settings/indicators.md)
- [Map](user/components/map.md#resources)
- [Geocoding](administration/settings/geocoding.md)
- [Users](/administration/users)
- [Mobile App](/user/mobile)
---
## Application Setup - General
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Application Setup", link: "https://app.dimescheduler.com/setup"}, {title: "General", link: "https://app.dimescheduler.com/setup"}]} />
The General tab is the tenant's identity card and its notification preferences in one place: who you are, what time zone the system should assume when a user or resource has not declared one, and where Dime.Scheduler should send the messages that need a human to act on them.
Most of these fields are set once during initial setup and rarely touched again. The two that quietly do the most work in the background are **Default time zone** (it is what catches users and resources that lack a time zone of their own, which would otherwise lead to appointments showing up at the wrong wall-clock time) and **Notification email**, which is where Dime.Scheduler reaches out when something needs your attention.
## Why client-secret reminders matter here
Client secrets configured in the application setup and the Exchange setup all eventually expire. When they do, the integration they back stops working - typically silently, until someone notices that data is no longer flowing.
Dime.Scheduler watches the expiration dates of those secrets and emails the notification address ahead of time so you can renew them on your terms rather than during an outage:
- **Expiration tracking**: secrets across the application setup and Exchange setup are watched continuously.
- **Proactive reminders**: notifications go out ahead of the expiration date, giving you time to renew.
The same reminder mechanism applies to **API keys**, configured through the **API key expiry reminder frequency** field below.
## Fields
| Field | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Company name | The company name. |
| Default time zone | IANA time zone (e.g. `Europe/Brussels`) applied as the tenant default when a user or resource has none configured. |
| Duration format | How the planning totals on a task (required, planned, remaining) should be displayed. Options: `Decimal`, `Hour`, `Day and Decimal`, `Day and Hour`. |
| Notification email | Optional email address that receives system notifications such as setup import results. |
| API key expiry reminder frequency | When and how often administrators are reminded that an API key is about to expire. Options: `Disabled`, `1 month before`, `2 weeks before`, `1 week before`, `3 days before`, `1 day before`. See [API keys](/administration/apikeys) for the underlying expiration mechanism. |
---
## Application Setup - Map
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Application Setup", link: "https://app.dimescheduler.com/setup"}, {title: "Map", link: "https://app.dimescheduler.com/setup"}]} />
The Map tab is where you set the tenant-wide defaults for the [map](/user/components/map): the company address that anchors the view, and the routing preferences that decide how distance and travel time are calculated.
Think of the values on this page as the starting point, not the final word. Each planner can override these four fields on their own profile:
- Calculate route and travel time
- Unit of distance
- Route profile
- Show waypoints
That means there is no need to ship users a "perfect" default - it just needs to be a reasonable one. Power users will adjust their profile; everyone else inherits what you set here.
## Company address
The company address is the pin the map opens to when nothing else is selected. With a **Basic Map** license, a **Verify address** button appears once both fields are filled in. Clicking it geocodes the address and stores the resulting latitude/longitude on the tenant; once verified, the coordinates replace the button.
| Field | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Company address | The primary address. Used as the default location on the map. |
| Company address country | The primary address' country. |
## Routing defaults
| Field | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Calculate route and travel time | Activate automatic route calculation on the map. A route is calculated between all selected tasks and appointments. |
| Unit of distance | Select the unit in which the calculated distance is displayed on the map. Options: `Kilometer`, `Mile`. |
| Route profile | The vehicle profile used for route, distance and travel time calculation. Options: `Car`, `Van`, `Truck`, `Motorcycle`, `Bicycle`, `Pedestrian`. |
| Show waypoints | Show the route selection sequence by displaying a number in the pins on the map. |
---
## Application Setup - Mobile
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Application Setup", link: "https://app.dimescheduler.com/setup"}, {title: "Mobile", link: "https://app.dimescheduler.com/setup"}]} />
The **Mobile** tab covers settings that affect the mobile app.
| Field | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Visibility for locked appointments | Controls how the [mobile app](/user/mobile) treats appointments whose `Locked` flag is set. Options: `Locked` (the appointment is shown but cannot be modified) or `Hidden` (the appointment does not appear in the mobile agenda at all). |
---
## Application Setup - Planning
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Application Setup", link: "https://app.dimescheduler.com/setup"}, {title: "Planning", link: "https://app.dimescheduler.com/setup"}]} />
The **Planning** tab is grouped into four cards: a generic block, **Time Windows**, **Visualization**, and **Calendars**.
## Generic
| Field | Description |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Enable appointment containers | Feature flag to toggle [appointment containers](/user/features/containers). |
| Multi-day capacity distribution mode | Controls how the capacity is calculated for multi-day appointments. Options: `None`, `Constant`, `Even`. To learn what these do, see the [pivot component](/user/components/pivot). |
| Do not count appointment resource | When enabled, the resource that owns an appointment is not counted toward the appointment's resource quantity. Useful when one resource always supervises rather than performs. |
| Disable recalculate planning quantity | When enabled, Dime.Scheduler stops recalculating planned and remaining quantities on the task whenever appointments change. Use this only when a back-office system owns those values. |
## Time Windows
| Field | Description |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Apply requested dates | When activated, appointments are created from the task's `Requested start date` and `Requested end date` fields. See [applying requested dates](#applying-requested-dates). |
| Apply requested times | When activated, appointments are created from the task's `Requested start time` and `Requested end time` fields. See [applying requested times](#applying-requested-times). |
| Respect allowed date constraints | When activated, extra data validation is executed on tasks. See [respect allowed date constraints](#respect-allowed-date-constraints). |
## Visualization
| Field | Description |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Enable planning board tooltip | When activated, hovering over an appointment on the planning board shows a tooltip. The tooltip's content is configured in [Field Templates](/administration/settings/fieldtemplates#planning-board-tooltip). |
| Render category in background | When enabled, all grid-based components displaying appointment or task data render the category color in the row background. |
| Alternate rows | When enabled, all grid-based components in the planning view show alternating row colors. |
## Calendar settings
| Field | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Enable resource calendar visualisation | When enabled, resource availability is visualised in the planning board. See [calendars](#calendars). |
| Resource zone color | Color picker for the background color of non-working times in the planning board. See [calendars](#calendars). |
| Resource calendar recalculation mode | Controls how appointments react when they overlap with a resource's non-working time. Options: `None` (no automatic recalculation), `Extend duration` (extend the appointment to compensate for non-working time), `Update planning quantity` (keep the duration but adjust the planned quantity). |
## Applying requested dates
When you drag and drop a task from the open task list onto the planning board, this setting forces an appointment to be created using the `Requested start date` and `Requested end date` parameters of the task:
- When only the `Requested start date` is populated, the appointment is created on the `Requested start date` with the duration as specified on the task's `Duration` value.
- If both `Requested start date` and `Requested end date` are filled out, an appointment is created with start and end dates equal to the requested start and end dates.
## Applying requested times
The scope of this setting is nearly identical to the [requested dates](#applying-requested-dates) feature but with one different detail: instead of applying dates, this feature applies only hours and minutes; thus it does not affect the date.
## Respect allowed date constraints
When you drag and drop an appointment, this setting disallows the user to place an appointment outside the `Earliest allowed date` or `Latest allowed date`, which is data that can be found on the task.
Appointments that are created manually or backed by tasks that have no requirements set on these fields are not impacted by this restricted planning mode.
## Calendars
[Calendars](/administration/settings/calendars) are used to define common work patterns. They are used in the [Gantt chart](/user/components/gantt) and the [planning board](/user/components/planningboard).
Calendars fulfill two functions in the planning board:
- Showing unavailable working times for each resource
- Recalculating the duration of an appointment to take non-working time into account
These are reflected in the following three configuration items:
- Enable resource calendar visualisation
- Resource zone color
- Resource calendar recalculation mode
The first two are particularly useful for the planner because the user actually _sees_ when resources are available. The recalculation mode is part of the optimization suite and is quite powerful: when an appointment is created or updated, the application can automatically extend the duration to make sure the resource can finish the task according to their calendar. For example, if a resource has a normal nine-to-five job and is scheduled to perform a two-hour task at four o'clock, then the application is smart enough to set the end date to the following day at ten o'clock. The planner can always override this behavior in the planning board or the appointment, which is a good example of the multi-layered configuration mechanism Dime.Scheduler applies:
---
## Application Setup - Preview
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Application Setup", link: "https://app.dimescheduler.com/setup"}, {title: "Preview", link: "https://app.dimescheduler.com/setup"}]} />
The items in this tab are subject to change. They cover experimental flags, deprecated toggles that are kept around for backwards compatibility, and configuration for opt-in capabilities such as route optimization and the AI Chat. For an overview of what each experimental flag does in the planning view itself, see [Experimental features](/user/experimental).
## Solver & optimization
| Field | Description |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Solver API key | API key for the route-optimization solver. Required for any of the optimization features below. |
| Enable route optimization | Adds the **Optimize route** action to the [route sequence](/user/components/routesequence). Requires the **Advanced Map** license. |
| Enable field service solver | Enables the Vehicle Routing Problem (VRP) solver, used by the planning board and the [`optimize_field_service` MCP tool](/integrations/mcp/tools/optimization#optimize-field-service) to assign and sequence appointments across resources. Requires the **Field Service Optimizer** optional module. |
## Planning board
| Field | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Recalculate travel time | _Deprecated._ Original travel-time recalculation switch. New deployments should use the route-sequence flow instead. See [experimental features](/user/experimental). |
| Planning board zoom | _Deprecated._ When switched on, you can zoom in and out on the planning board with `Ctrl` + scroll. Replaced by the new planning board. |
| New planning board | Switches the planning board to the new rendering engine. |
## AI Chat
These settings configure the in-product [AI Chat](/user/features/chat). When the chat is enabled but no API key is configured, the chat panel shows a configuration prompt instead of the conversation UI.
| Field | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------- |
| Show AI chat | Toggles the AI Chat panel in the planning view. |
| Provider | LLM provider to use. Options: `OpenAI`, `Anthropic`, `Azure OpenAI`. |
| Model | Model identifier (e.g. `gpt-4o-mini`, `claude-haiku-4-5-20251001`). Leave blank to use the provider default. |
| API key | The API key for the selected provider. |
| Endpoint | _Azure OpenAI only._ The Azure resource endpoint (e.g. `https://.openai.azure.com`). |
| Deployment | _Azure OpenAI only._ The deployment name configured in Azure. |
---
## Setup packages CLI reference
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
:::info[Status: Preview]
Setup packages are currently in preview. Functionality, file format, and behavior may change between releases. Not recommended for production deployments yet.
:::
`dimescheduler-setup` is the CLI half of the setup-packages story - the thing you call from a terminal or a CI pipeline to validate, compile, and deploy a setup package without a human in the loop. Use it when the configuration lives in source control and a push to `main` should trigger a deploy, or when an operator needs to redeploy a known-good package without opening a desktop app.
This page is the reference for each command, its arguments, options, and exit codes.
## Installation
```bash
npm install -g @dimescheduler/setup
```
```bash
npx @dimescheduler/setup
```
## Commands
### validate
Validate a configuration file against the schema.
```bash
dimescheduler-setup validate [options]
```
**Arguments:**
- ``: Path to .json5 or .json configuration file
**Options:**
- `--json`: Output results as JSON
**Exit codes:**
- `0`: Valid configuration
- `1`: Validation errors
**Example:**
```bash
dimescheduler-setup validate my-profile.json5
```
### compile
Compile JSON5 configuration to API-ready JSON.
```bash
dimescheduler-setup compile [options]
```
**Arguments:**
- ``: Path to .json5 configuration file
**Options:**
- `-o, --output `: Output directory for compiled files
- `--stdout`: Output to stdout instead of file
- `--skip-validation`: Skip schema validation
**Example:**
```bash
# Compile to directory
dimescheduler-setup compile my-profile.json5 -o ./dist
# Compile to stdout
dimescheduler-setup compile my-profile.json5 --stdout
```
### deploy
Deploy configuration to a Dime.Scheduler instance.
```bash
dimescheduler-setup deploy --api-key --env [options]
```
**Arguments:**
- ``: Path to configuration file
**Required options:**
- `--api-key `: API key (or set `DS_API_KEY` environment variable)
- `--env `: Target environment: `production`, `sandbox`, or `test`
**Additional options:**
- `--skip-validation`: Skip schema validation
- `--json`: Output results as JSON
- `--dry-run`: Validate and compile only, don't deploy
- `-y, --yes`: Skip confirmation prompt
**Exit codes:**
- `0`: Success (or cancelled by user)
- `1`: Validation or compilation error
- `2`: Deployment error
**Examples:**
```bash
# Deploy with confirmation
dimescheduler-setup deploy my-profile.json5 --api-key abc123 --env production
# Deploy without confirmation
dimescheduler-setup deploy my-profile.json5 --api-key abc123 --env production -y
# Using environment variable
export DS_API_KEY=abc123
dimescheduler-setup deploy my-profile.json5 --env production
# Dry run (validate and compile only)
dimescheduler-setup deploy my-profile.json5 --api-key abc123 --env sandbox --dry-run
```
## Environment variables
| Variable | Description |
| ------------ | ------------------------------------------------------ |
| `DS_API_KEY` | API key for deployment (alternative to `--api-key`) |
## Read more
- [Setup packages](/administration/setup-packages)
- [The desktop app](/administration/setup-packages/desktop), if you would rather not work from a terminal
- [API keys](/administration/apikeys), which is where `DS_API_KEY` comes from
- [The Dime.Scheduler CLI](/develop/cli), a separate tool for the general-purpose API
---
## Setup packages desktop application
:::info[Status: Preview]
Setup packages are currently in preview. Functionality, file format, and behavior may change between releases. Not recommended for production deployments yet.
:::
Setup packages are JSON5 files at their core, and you can build them by hand if you want to. Most administrators do not want to. The desktop application is the visual alternative: a workspace for creating, managing, and deploying setup packages without opening a text editor.
It is the right tool when you are building a new configuration from scratch, exploring what fields exist, or doing a one-off deployment. For repeat deployments in a CI pipeline, switch to the [CLI](/administration/setup-packages/cli) - the desktop is for humans, the CLI is for scripts.
## Installation
Install via [Scoop](https://scoop.sh/) on Windows:
```powershell
scoop bucket add dimescheduler https://github.com/dime-scheduler/distro
scoop install dimescheduler/setup
```
On macOS, install via [Homebrew](https://brew.sh/):
```bash
brew tap dime-scheduler/distro https://github.com/dime-scheduler/distro
brew install --cask dimescheduler-setup
```
## Pages
### Landing page
Your starting point, with quick access to:
- Create new configuration
- Open existing configuration
- Recent configurations
- Quick-start templates
### Wizard
The wizard walks you through creating a configuration step by step.
#### 1. Document types
Choose profile, layouts, users, configuration, or bundle.
#### 2. Metadata
Set owner and notification email:
- The **owner** email refers to a user that already exists in Dime.Scheduler. This user will be the owner of the new entities.
- The **notification email** receives a report once the import has completed.
#### 3. Profile settings and workspace builder
Configure theme, routing, and planning (for profiles), and a visual layout editor (for profiles).
#### 4. Layouts
Define grid layouts.
#### 5. Users
Manage user accounts (for users/bundles).
#### 6. Configuration
Import and export application configuration settings. Configuration documents contain key/value pairs for system-wide settings. See the [Configuration section](/administration/setup-packages#configuration) for the full list of available keys.
#### 7. Summary and deploy
- Review configuration
- Configure merge strategy (for bundles)
- Save or deploy
### Configurations
Manage saved configurations:
- View and edit existing configurations
- Duplicate configurations
- Delete configurations
- Export as JSON5 or JSON
### Instances
Configure Dime.Scheduler instances for deployment:
- Add production, sandbox, or test instances
- Store API keys securely
- Test connectivity
### Deploy
Bulk-deploy configurations to an instance of choice:
---
## Setup packages
:::info[Status: Preview]
Setup packages are currently in preview. Functionality, file format, and behavior may change between releases. Not recommended for production deployments yet.
:::
Most administration work is point-and-click inside a single tenant: create the users, lay out the profiles, configure the components. That is fine for one tenant, painful for ten. The moment you need to ship the same configuration to a sandbox, replicate a customer setup in a fresh environment, or commit a setup to source control so a change goes through review, the manual approach falls apart.
Setup packages are the answer to that. A setup package is a portable, human-readable JSON5 file that describes a Dime.Scheduler configuration - profiles, layouts, users, application settings - in a way you can version, diff, copy, and deploy. Use them to replicate a tenant in one command, share a configuration between instances, or hold a reusable template under git. No code required.
## What's in a package
The following entities can be packaged:
- **Profiles**: UI structure using columns, rows, stacks, and components
- **Layouts**: column layouts, sorting, filtering, and contextual settings per component
- **Users**: accounts, roles, profile assignments, categories, time markers, filter values
- **Configuration**: system-wide application settings as key/value pairs
## Two ways to use them
- **[Desktop application](/administration/setup-packages/desktop)**: visual workspace, guided wizards, first-time setup
- **[CLI](/administration/setup-packages/cli)**: automation, CI/CD pipelines, version control workflows
Packages flow in **both directions**: import a package from Dime.Scheduler, or build one from scratch and deploy it back. Because the files are human-friendly, they store cleanly in version control and deploy automatically through the CLI.
## Quick start
1. Create a configuration file `my-profile.json5`:
```json5
{
type: "bundle",
name: "Complete Setup Package",
version: "1.0.0",
owner: "admin@example.com",
notificationEmail: "alerts@example.com",
documents: [
{
type: "profile",
payload: {
name: "Service Planning",
code: "SERVICE_PLANNING",
theme: { color: "blue" },
workspace: [
{
type: "column",
width: 30,
content: [{ type: "component", component: "openTasks", title: "Open Tasks" }]
},
{
type: "column",
width: 70,
content: [{ type: "component", component: "planningBoard", title: "Planning Board", id: "scheduler1" }]
}
]
}
}
]
}
```
1. Validate your configuration:
```bash
dimescheduler-setup validate my-profile.json5
```
1. Deploy to your Dime.Scheduler instance:
```bash
dimescheduler-setup deploy my-profile.json5 --api-key YOUR_API_KEY --env production
```
## Document types
| Type | Purpose |
| --------------- | ----------------------------------------------------------------------------------- |
| `profile` | Workspace configuration including planning settings and the workspace tree. |
| `layouts` | Grid and component layouts. |
| `users` | User accounts and permissions. |
| `configuration` | System-wide application settings. |
Every document follows this structure:
```json5
{
type: "profile" | "layouts" | "users" | "configuration",
payload: { ... }
}
```
Each document lives in the `documents` element of the bundle's root node. See [bundles](#bundles).
```json5
{
type: "bundle",
name: "Complete Setup Package",
version: "1.0.0",
owner: "admin@example.com",
notificationEmail: "alerts@example.com",
documents: [
{
type: "profile",
payload: {
name: "Service Planning",
code: "SERVICE_PLANNING",
// ... profile configuration
}
},
{
type: "layouts",
payload: [
// ... layout definitions
]
},
{
type: "users",
payload: [
// ... user definitions
]
},
{
type: "configuration",
payload: [
// ... configuration entries
]
}
],
}
```
## Profile
A profile defines a complete workspace configuration: its theme, planning settings, and UI layout.
### Required fields
| Field | Type | Description |
| ------ | ------ | ----------------------------------------------- |
| `name` | string | Display name (e.g., "Service Planning") |
| `code` | string | Unique identifier in UPPERCASE_WITH_UNDERSCORES |
### Theme
Customize the visual appearance:
```json5
theme: {
color: "blue", // Accent color
scheme: "sltl" // Color scheme
}
```
#### Colors
- `default` (black)
- `primary` (teal)
- `red`
- `green`
- `orange`
- `purple`
- `blue`
#### Schemes
| Code | Description |
| ------ | ------------------------------------ |
| `sltl` | Light sidebar, Light toolbar |
| `sltd` | Light sidebar, Dark toolbar |
| `sdtl` | Dark sidebar, Light toolbar |
| `sdtd` | Dark sidebar, Dark toolbar (default) |
### Sharing
Control who can access the profile:
```json5
shared: {
global: false, // Available to all users
userGroup: "Planners" // Restrict to specific group
}
```
### Route configuration
Configure route calculation behavior:
```json5
route: {
calculateRoutes: true, // Enable automatic route calculation
showSequenceIndicators: true, // Show sequence numbers on routes
unitOfDistance: "km" // "km" or "mi"
}
```
### Planning settings
Configure the planning board behavior:
```json5
planning: {
snapInterval: "1hour", // Task snapping interval
range: {
mode: "week", // Display range
value: 2 // Number of units
},
start: {
mode: "startOfWeek", // Start date mode
value: 0 // Offset in days
},
hours: {
start: 8, // Working day start (0-23)
end: 18 // Working day end (0-24)
}
}
```
#### Snap interval options
`5min` · `10min` · `15min` · `30min` · `1hour` · `2hours` · `4hours` · `12hours` · `1day`
#### Range modes
| Mode | Description |
| ------- | --------------- |
| `day` | Single day view |
| `week` | Week view |
| `month` | Month view |
#### Start modes
| Mode | Description |
| -------------- | ------------------------------------- |
| `today` | Start from today |
| `startOfWeek` | Start from beginning of current week |
| `startOfMonth` | Start from beginning of current month |
## Workspace builder
The workspace defines the UI layout as a tree of containers and components.
### Container types
#### Column (vertical split)
Children are stacked top-to-bottom:
```json5
{
type: "column",
width: 30, // Percentage width (0-100)
content: [ ... ] // Child elements
}
```
#### Row (horizontal split)
Children are placed side-by-side:
```json5
{
type: "row",
height: 50, // Percentage height (0-100)
content: [ ... ] // Child elements
}
```
#### Stack (tabbed container)
Only one child visible at a time, with tabs to switch:
```json5
{
type: "stack",
width: 100, // Optional
height: 50, // Optional
content: [ ... ] // Components or nested containers
}
```
### Components
Leaf nodes that render actual UI components:
```json5
{
type: "component",
component: "planningBoard", // Component identifier
title: "Planning Board", // Tab/header title
id: "scheduler1", // Unique instance ID
layoutRef: "SCHEDULER_LAYOUT" // Optional: reference to layout code
}
```
### Available components
`openTasks` · `planningBoard` · `plannedTasks` · `map` · `indicators` · `resourceFilters` · `details` · `datePicker` · `gantt` · `capacity` · `routeSequence` · `notifications`
### Layout example
A typical two-column layout with sidebar and main content:
```json5
workspace: [
{
type: "column",
width: 25,
content: [
{
type: "stack",
content: [
{ type: "component", component: "openTasks", title: "Open Tasks" },
{ type: "component", component: "plannedTasks", title: "Planned Tasks" }
]
},
{ type: "component", component: "resourceFilters", title: "Filters" }
]
},
{
type: "column",
width: 75,
content: [
{
type: "component",
component: "planningBoard",
title: "Planning Board",
id: "scheduler1",
layoutRef: "SCHEDULER_DEFAULT"
}
]
}
]
```
## Layouts
Layouts control how data grids and components display information. Define them inline within a component, or as standalone documents you can reuse.
### Layout contexts
Each layout targets a specific component type:
`planningBoard` · `openTasks` · `plannedTasks` · `details` · `capacity` · `gantt` · `routeSequence` · `notifications`
### Grid-based component layout properties
```json5
{
context: "planningBoard", // Target component type
code: "SCHEDULER_COMPACT", // Unique identifier
name: "Compact View", // Display name
// Grid configuration
columns: [
{ property: "DisplayName", width: 200 },
{ property: "Category", width: 100 }
],
sorters: [
{ property: "DisplayName", direction: "ASC" }
],
filters: [ ... ],
grouper: {
property: "Category",
direction: "ASC"
},
pageSize: 50,
// Sharing
shared: {
public: true,
userGroup: "Planners"
}
}
```
### Planning board layout properties
Additional properties specific to the planning board (`context: "planningBoard"`):
```json5
{
context: "planningBoard",
code: "SCHEDULER_WEEK",
viewPreset: "week", // day, week, workWeek, month
rowHeight: "2rows", // 1row, 2rows, 3rows, 4rows
resourcesGridWidth: "25%", // Width of resource column
fitToScreen: false, // Fit grid to screen
ignoreCalendars: false // Ignore calendar configurations
}
```
**View presets:** `day` · `week` · `workWeek` · `month`
**Row heights:** `1row` · `2rows` · `3rows` · `4rows`
### Standalone layouts document
Create reusable layouts in a separate document:
```json5
{
type: "layouts",
payload: [
{
context: "planningBoard",
code: "SCHEDULER_COMPACT",
name: "Compact Scheduler",
default: true,
viewPreset: "week",
rowHeight: "1row",
columns: [
{ property: "DisplayName" }
]
},
{
context: "openTasks",
code: "OPEN_TASKS_DEFAULT",
name: "Default Open Tasks",
columns: [
{ property: "TaskNo" },
{ property: "ShortDescription" },
{ property: "Category" }
],
sorters: [
{ property: "TaskNo", direction: "DESC" }
]
}
]
}
```
### Referencing layouts
Reference standalone layouts from components using `layoutRef`:
```json5
{
type: "component",
component: "planningBoard",
title: "Planning Board",
id: "scheduler1",
layoutRef: "SCHEDULER_COMPACT" // Reference by code
}
```
## Users
Define user accounts, their roles, and profile assignments.
### User properties
```json5
{
name: "John Smith", // Display name
email: "john.smith@example.com", // Email
isMSEntraID: true, // MS Entra ID authentication
language: "en", // Language code
timeZone: "Europe/Brussels", // IANA timezone
profiles: [
{ name: "SERVICE_PLANNING", default: true },
{ name: "FIELD_SERVICE" }
],
roles: ["Planner", "Dispatcher"],
timeMarkers: ["Lunch", "Break"],
categories: ["Urgent", "Normal"],
filterValues: ["Region A", "Region B"]
}
```
### Users document
```json5
{
type: "users",
payload: [
{
name: "John Smith",
email: "john.smith@example.com",
language: "en",
timeZone: "Europe/Brussels",
profiles: [
{ name: "SERVICE_PLANNING", default: true }
],
roles: ["Planner"]
},
{
name: "Jane Doe",
email: "jane.doe@example.com",
language: "nl",
timeZone: "Europe/Amsterdam",
profiles: [
{ name: "SERVICE_PLANNING" },
{ name: "FIELD_SERVICE", default: true }
],
roles: ["Dispatcher", "Administrator"]
}
]
}
```
## Configuration
Configuration documents define system-wide application settings as key/value pairs. These settings control application behavior, integrations, and display preferences.
### Configuration document
```json5
{
type: "configuration",
payload: [
{ key: "CompanyName", value: "Acme Corporation" },
{ key: "DurationFormat", value: "HoursMinutes" },
{ key: "DistanceUnit", value: "km" },
{ key: "EnablePlanningBoardTooltip", value: true },
{ key: "ExchangeDefaultShowAs", value: ["Office", "OutOfOffice"] }
]
}
```
### Available configuration keys
| Category | Key | Description |
|----------|-----|-------------|
| **Company** | `CompanyName` | Company name displayed in the application |
| | `CompanyAddress` | Company address for route calculations |
| | `CompanyAddressCountry` | Country of the company address |
| **Display** | `DurationFormat` | Duration format: `Decimal`, `HoursMinutes`, `DaysDecimal`, `DaysHours` |
| | `EnableAppointmentContainers` | Enable appointment containers feature |
| | `EnablePlanningBoardTooltip` | Show tooltips on planning board appointments |
| | `RenderCategoryInBackground` | Render category color in appointment background |
| | `AlternateRows` | Alternate row colors in planning board |
| | `EnableResourceCalendarsVisualisation` | Show resource calendar zones on planning board |
| | `ResourceZonesColor` | Color for resource availability zones (hex format) |
| **Planning** | `ApplyRequestedDates` | Apply requested date constraints when planning |
| | `ApplyRequestedTimes` | Apply requested time constraints when planning |
| | `RespectAllowedDateConstraints` | Respect allowed date constraints from source system |
| | `MultiDayCapacityDistributionMode` | Multi-day capacity distribution: `None`, `Constant`, `Even` |
| | `ResourceCalendarRecalculationMode` | Calendar recalculation: `None`, `ExtendDuration`, `UpdatePlanningQuantity`, `SubtractNonWorkingTime` |
| | `DoNotCountAssignment` | Do not count appointment resource in capacity calculations |
| | `DisableTaskDurationCalculation` | Disable automatic recalculation of planning quantity |
| **Routing** | `RouteReschedulingMode` | Route rescheduling: `DoNothing`, `UpdateTravelTime`, `Optimize` |
| | `CalculateRouteAndTravelTime` | Calculate route and travel time automatically |
| | `DistanceUnit` | Unit of distance: `km`, `mi` |
| | `RouteProfile` | Route profile: `car`, `van`, `truck`, `motorcycle`, `bicycle`, `pedestrian` |
| | `ShowWaypoints` | Show waypoints on map routes |
| **Mobile** | `MobileVisibilityModeForLockedAppointments` | Locked appointment visibility: `Locked`, `Hidden` |
| **AI & Optimization** | `SolverApiKey` | API key for route optimization solver |
| | `EnableRouteModel` | Enable route optimization features |
| | `EnableFieldServiceModel` | Enable field service solver (VRP) |
| | `Chat` | Show AI chat feature in sidebar |
| | `OpenAiApiKey` | OpenAI API key for AI chat features |
| **Exchange Sync** | `ExchangeClientId` | Microsoft Entra ID App Registration client ID |
| | `ExchangeClientSecret` | Microsoft Entra ID App Registration client secret |
| | `ExchangeClientTenant` | Microsoft Entra ID App Registration tenant ID |
| | `ExchangeEnableTwoWaySync` | Enable two-way sync between Exchange and Dime.Scheduler |
| | `ExchangeDeltaStart` | Delta start time for Exchange sync (e.g., -30 days) |
| | `ExchangeDeltaEnd` | Delta end time for Exchange sync (e.g., 30 days) |
| | `ExchangeResourcePreferencesEnabled` | Allow users to manage their Exchange sync preferences |
| | `ExchangeDefaultShowAs` | Busy status filter (array): `Free`, `Tentative`, `Office`, `OutOfOffice`, `WorkingElsewhere`, `Unknown` |
| | `ExchangeDefaultSensitivity` | Sensitivity filter (array): `Normal`, `Personal`, `Private`, `Confidential` |
| | `ExchangeCategoryPreset` | Category preset for Exchange appointments |
| | `ExchangeUseCategory` | Use category for Exchange appointments |
## Bundles
Bundles combine multiple documents into a single, atomic deployment. To deploy profiles, layouts, users, and configuration together, include them all in the `documents` array.
### Bundle structure
```json5
{
type: "bundle",
name: "Complete Setup Package",
version: "1.0.0",
owner: "admin@example.com",
notificationEmail: "alerts@example.com",
documents: [
{
type: "profile",
payload: {
name: "Service Planning",
code: "SERVICE_PLANNING",
// ... profile configuration
}
},
{
type: "layouts",
payload: [
// ... layout definitions
]
},
{
type: "users",
payload: [
// ... user definitions
]
},
{
type: "configuration",
payload: [
// ... configuration entries
]
}
],
mergeStrategy: {
layouts: "append", // append, replace, error
users: "append", // append, replace, error
profiles: "last-wins" // last-wins, first-wins, error
}
}
```
### Merge strategies
Control how duplicate items are handled:
| Strategy | Behavior |
| ------------ | -------------------------------------------------------- |
| `append` | Add new items, keep existing (default for layouts/users) |
| `replace` | Replace all existing items |
| `error` | Fail if duplicates exist |
| `last-wins` | Last definition wins (default for profiles) |
| `first-wins` | First definition wins |
## Examples
### Basic profile
A minimal profile with planning board and open tasks:
```json5
{
type: "profile",
owner: "admin@example.com",
payload: {
name: "Basic Planning",
code: "BASIC_PLANNING",
theme: {
color: "blue",
scheme: "sltl"
},
planning: {
snapInterval: "1hour",
range: { mode: "week" },
hours: { start: 8, end: 17 }
},
workspace: [
{
type: "column",
width: 25,
content: [
{
type: "component",
component: "openTasks",
title: "Open Tasks"
}
]
},
{
type: "column",
width: 75,
content: [
{
type: "component",
component: "planningBoard",
title: "Planning Board",
id: "scheduler1"
}
]
}
]
}
}
```
### Full-featured profile
A complete profile with all components and custom layouts:
```json5
{
type: "profile",
owner: "admin@example.com",
notificationEmail: "alerts@example.com",
payload: {
name: "Service Planning",
code: "SERVICE_PLANNING",
theme: {
color: "primary",
scheme: "sdtd"
},
shared: {
global: false,
userGroup: "Planners"
},
route: {
calculateRoutes: true,
showSequenceIndicators: true,
unitOfDistance: "km"
},
planning: {
snapInterval: "30min",
range: { mode: "week", value: 2 },
start: { mode: "startOfWeek" },
hours: { start: 7, end: 19 }
},
workspace: [
{
type: "column",
width: 20,
content: [
{
type: "stack",
content: [
{ type: "component", component: "openTasks", title: "Open Tasks" },
{ type: "component", component: "plannedTasks", title: "Planned" }
]
},
{ type: "component", component: "resourceFilters", title: "Filters" },
{ type: "component", component: "datePicker", title: "Calendar" }
]
},
{
type: "column",
width: 60,
content: [
{
type: "component",
component: "planningBoard",
title: "Planning Board",
id: "scheduler1",
layoutRef: "SCHEDULER_DETAILED"
}
]
},
{
type: "column",
width: 20,
content: [
{ type: "component", component: "map", title: "Map" },
{ type: "component", component: "details", title: "Details" },
{ type: "component", component: "indicators", title: "Indicators" }
]
}
]
}
}
```
### Complete bundle
A bundle with profile, layouts, and users:
```json5
{
type: "bundle",
name: "Service Department Setup",
version: "1.0.0",
owner: "admin@example.com",
documents: [
// Profile
{
type: "profile",
payload: {
name: "Service Planning",
code: "SERVICE_PLANNING",
theme: { color: "blue" },
workspace: [
{
type: "column",
width: 30,
content: [
{ type: "component", component: "openTasks", title: "Tasks" }
]
},
{
type: "column",
width: 70,
content: [
{
type: "component",
component: "planningBoard",
title: "Board",
id: "scheduler1",
layoutRef: "SCHEDULER_DEFAULT"
}
]
}
]
}
},
// Layouts
{
type: "layouts",
payload: [
{
context: "planningBoard",
code: "SCHEDULER_DEFAULT",
name: "Default Scheduler",
default: true,
viewPreset: "week",
rowHeight: "2rows",
columns: [
{ property: "DisplayName" },
{ property: "Category" }
]
}
]
},
// Users
{
type: "users",
payload: [
{
name: "Service Manager",
email: "manager@example.com",
profiles: [{ name: "SERVICE_PLANNING", default: true }],
roles: ["Administrator", "Planner"]
},
{
name: "Dispatcher",
email: "dispatch@example.com",
profiles: [{ name: "SERVICE_PLANNING", default: true }],
roles: ["Planner"]
}
]
}
],
mergeStrategy: {
layouts: "append",
users: "append",
profiles: "last-wins"
}
}
```
### CI/CD integration
GitHub Actions example:
```yaml
name: Deploy Configuration
on:
push:
branches: [main]
paths:
- 'config/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install CLI
run: npm install -g @dimescheduler/setup
- name: Validate
run: dimescheduler-setup validate config/bundle.json5
- name: Deploy to Sandbox
if: github.ref == 'refs/heads/develop'
run: dimescheduler-setup deploy config/bundle.json5 --env sandbox -y
env:
DS_API_KEY: ${{ secrets.DS_API_KEY_SANDBOX }}
- name: Deploy to Production
if: github.ref == 'refs/heads/main'
run: dimescheduler-setup deploy config/bundle.json5 --env production -y
env:
DS_API_KEY: ${{ secrets.DS_API_KEY_PRODUCTION }}
```
## Import from Dime.Scheduler
### User-generated content
From the user-generated content page in Dime.Scheduler, export a bundle that contains profiles and the default layouts of the components in those layouts.
From there, import the raw JSON file into the setup package builder.
### System
The System page has a button to export all supported non-transactional setup:
- Profiles
- Layouts
- Users
From there, import the raw JSON file into the setup package builder.
---
## Application Setup
import { FaWrench } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Application Setup", link: "https://app.dimescheduler.com/setup"}]} />
The Application Setup is the top of the [configuration cascade](/administration/appsettings). The defaults you pick here apply to every user and resource in the tenant, and they are what every new user inherits before they have customised anything on their own profile. Change a switch in this section and you are not changing one planner's view - you are changing the starting point for the whole organisation.
That makes most of these settings "set once, leave alone": touch them during the initial setup, then revisit only when something fundamental changes (a new license module, a new country, a new mobile rollout). The page is split into five tabs so each concern lives in one place:
- **[General](/administration/setup/general)** - company information and notification preferences.
- **[Planning](/administration/setup/planning)** - defaults that drive how the planning board behaves.
- **[Map](/administration/setup/map)** - map and routing defaults.
- **[Mobile](/administration/setup/mobile)** - settings that affect the mobile app.
- **[Preview](/administration/setup/preview)** - feature flags for experimental and opt-in capabilities.
## Read more
- [Gantt chart component](user/components/gantt.md)
- [Planning board component](user/components/planningboard/index.md)
- [Map component](user/components/map.md)
- [Route sequence component](user/components/routesequence.md)
- [AI Chat](user/features/chat.md)
- [Experimental features](/user/experimental)
---
## System
import { FaStethoscope } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "System", link: "https://app.dimescheduler.com/system"}]} />
The System page is where global administrators manage their tenant. It is available only to users whose role includes the global administrator user action.
## Danger zone
The danger zone lets you remove data from Dime.Scheduler: jobs (along with their dependency data graph, including tasks and appointments), notifications, filters, and resources. Use it with extreme care, because these changes are irreversible.

When you request a removal, a confirmation window appears so you can confirm which modules to delete. After you confirm, Dime.Scheduler schedules a background job and emails the global administrator once the job has finished.
## Activity
This data grid shows tenant activity over the last 14 days, with metrics such as the number of appointments planned, the open tasks, and the number of resources.
---
## Appointments
import { FaPlug } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Appointments", link: "https://app.dimescheduler.com/appointments"}]} />
The [transactions list](/administration/transactions/list) is the integration's view: every call, in order, across all appointments. This page flips that around. It is the *appointment's* view: every appointment in the tenant, with a click-through to the transactions behind it. When a planner says "what is the state of *this* booking with the back office?", this is where you answer them.

The list is sorted by most recent activity first, so the appointments most likely to need attention float to the top.
## Drilling into an appointment
Click the magnifying glass icon on a row to open the appointment detail page. It pulls together everything Dime.Scheduler knows about the booking from the integration side: the appointment's basic info, every connector that has been wired up for it, and the full list of transactions per connector with their statuses.

When the last transaction of a connector failed, that connector's grid surfaces a retry button. Use it for one-off retries when you have just fixed the underlying problem (a back-office permission, a stale credential, a temporary outage) and want to push the appointment through again without waiting for the background retry job.

## Where transactions come from
The **Source** column on each transaction tells you what triggered it:
- **Service** - the regular planning flow. A planner moved or created the appointment on the board.
- **Website** - an administrator (probably you) clicked the retry button.
- **Timer job** - the background retry job picked the appointment up automatically.
For the full picture of how retries are scheduled and when they give up, see the [transactions overview](/administration/transactions).
---
## Errors
import { FaPlug, FaArchive, FaPaperPlane } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Errors", link: "https://app.dimescheduler.com/errors"}]} />
Use this page to find and resolve appointments that failed to sync. It lists every appointment in the faulted state, so an administrator knows exactly what needs attention.

The view is split into three tabs - **Dynamics**, **Exchange**, and **Webhook** - one per connector family. Each tab shows only the failures from that connector type, so you can work through one integration at a time without juggling filters.
## Details
Click the magnifying glass icon to drill into a faulted appointment. The details page summarizes the appointment's info, the connectors assigned to it, and the list of transactions with their statuses.

When the last transaction of a connector did not succeed, you can retry it with the button inside that connector's transactions grid.

## Archive
If a failure is safe to ignore, archive the appointment. Archive every failed appointment at once, or archive a single one with its scoped button.
## Retry
To retry failed appointments straight from the list view, click the button.
---
## Transactions
import { FaPlug } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Transactions", link: "https://app.dimescheduler.com/transactions"}]} />
Every time Dime.Scheduler calls out to a connected system, it logs the call here as a *transaction*. When a planner says "I made that change ten minutes ago and it still hasn't reached BC", this is the page where you find out whether the call went out at all, what came back, and (when needed) where to retry from. Transactions are listed most recent first.

The view splits into three tabs - **Dynamics**, **Exchange**, and **Webhook** - one per connector family. Each transaction stays scoped to the integration it came from, so a stuck Exchange sync never gets buried under BC traffic.
## Where transactions come from
A transaction can be triggered by one of three sources, shown in the **Source** column:
- **Service** - the regular path. A planner moved something on the board; the service fired the transaction.
- **Website** - an administrator clicked **Retry** on a failed transaction from this page or from the appointment detail.
- **Timer job** - the background retry job picked up an appointment that was still in a state worth retrying.
For the full picture of how transactions are evaluated and retried, see the [Transactions overview](/administration/transactions).
---
## Transactions(Transactions)
Every time Dime.Scheduler talks to a connected backoffice system - Business Central, Exchange, or a webhook connection - it records the interaction. This page explains how those transactions work and what "successful" really means, so you can read the [audit trail](/administration/transactions/appointments) inside Dime.Scheduler with confidence.
## What constitutes a transaction
When Dime.Scheduler posts data to a connected system, it marks the transaction as successful. What that means in practice differs per backoffice system, but the rule Dime.Scheduler applies is the same everywhere: a 200 HTTP status code.
In the connector for Business Central, a successful transaction means an item has been posted to the Dime.Scheduler Appointments table, which internal Business Central workflows then pick up. This is a robust pattern that maximizes the resilience of the solution. Other backoffice systems may not have this proxy and process the request immediately. The exact behavior depends on your setup.
:::warning[What makes a transaction successful]
To repeat: all Dime.Scheduler looks at to mark a transaction as successful is a 200 HTTP status code in the response. That does not necessarily mean the new planning information was processed successfully. It only means Dime.Scheduler reached the endpoint and the endpoint responded.
:::
## Processing the planning data in the backoffice system
A decoupled solution - like the staging 'Dime.Scheduler Appointments' table approach in BC - introduces a second layer of complexity. The backoffice can acknowledge that it received the appointment correctly by returning a 200 code, yet still fail to process that appointment internally.
The item may land in the staging table but never make it into the target tables. There are many reasons why. In the image below, for example, the assigned resource is missing a 'Unit of Measure' in Business Central:

The status of this appointment in BC is therefore Error. So even when Dime.Scheduler considers the item sent successfully, the appointment may not have processed correctly in the backoffice system.
:::tip
For help troubleshooting issues like this, see the [Business Central Troubleshooting](/integrations/bc/troubleshooting) guide and the [Business Central Transaction Errors](/integrations/bc/transaction-errors) reference.
:::
To keep planners informed when this happens, Dime.Scheduler can [create notifications for appointments](/integrations/bc/config/safeguards#notify-appointment-errors):

## Summary
Here is how the transaction system works in Dime.Scheduler together with Business Central. Step through the happy path on the USA tenant and then the failure case on the Canadian tenant:
## Automatic retries
Dime.Scheduler's job is done once the backoffice system returns a 200 status code. When any other status code comes back, automatic retry policies may kick in and make a few more attempts to reach the backoffice system. Transient errors like request timeouts (408), throttling (429), bad gateway (502), unavailable services (503), and gateway timeouts (504) are often one-off or temporary, so a second or third attempt might succeed.
A transaction can originate from three sources:
- **Service**: initiated by the user in the regular flow of planning appointments on the planning board.
- **Website**: triggered by the retry button on the transactions page.
- **Timer job**: automatic retries for appointments in a failed status.
As soon as someone changes the planning, Dime.Scheduler calls the connected systems. If that transaction fails, the **automatic retry job** runs in the background and retries every appointment in a state that could still return a successful response. It makes 5 attempts, then suspends further retries on the assumption that a sixth attempt would not change the outcome.
If all attempts fail, or if the administrator changed the connector or the backoffice system itself, they can also **manually retry** from the detail page of the appointment. The transaction source reads 'website' for this action.
---
## Troubleshooting
import AreaTroubleshootingLanding from '@site/src/components/AreaTroubleshootingLanding';
Search for what you're seeing, or browse the reference below. Each symptom links straight to the page that explains the cause and the fix.
## Reference
The sections below are the same answers, listed in full.
### User administration
#### I changed a user role but users are able to do things they aren't supposed to. What has gone wrong?
The change most likely hasn't taken effect for users who were already signed in. Ask them to log out and log back in, then check whether the new role permissions now apply.
### Field templates
#### Where is the setting to change the body text for appointments?
You'll find it in [field templates](administration/settings/fieldtemplates.md).
### Map and geocoding
#### Jobs or resources are missing from the map
The map only plots records that have coordinates. If something is missing, geocoding has not produced a coordinate for it. The most common causes:
- **The address is incomplete or ambiguous.** A street name without a city, a city without a country, or only a postal box rarely resolves cleanly. Encourage users (or the back-office system) to populate the structured fields fully: street, street number, zip, city, country.
- **Free-text addresses are filled in instead of the structured fields.** The structured fields are far more reliable. Where possible, fix the back-office mapping so addresses arrive in the dedicated columns.
- **Non-ISO country values.** The geocoder expects ISO 3166 codes. Map localized country names ("Belgique", "Deutschland") to their codes in the [localization view](administration/settings/localization.md).
- **The nightly job has not run yet.** Newly imported records are picked up by the next nightly geocoding pass. To geocode immediately, use the **Geocode All** toolbar button on the [jobs](administration/settings/jobs.md) or [resources](administration/settings/resources.md) page, or the per-row geocode button on a single record.
#### A record stays off the map even though I fixed the address
Dime.Scheduler maintains a [blacklist](administration/settings/geocoding.md#the-blacklist) of address signatures that previously failed to resolve. The blacklist is what keeps the nightly job from hammering the provider with the same bad input every night, but it can mask a fix:
1. Open the [jobs](administration/settings/jobs.md) or [resources](administration/settings/resources.md) page and click the per-row geocode button on the corrected record. If the new address differs enough from the old one, this often resolves it on the spot.
2. If it still fails, the record may share a signature with an earlier blacklisted entry. There is no in-app screen to inspect the blacklist yet; contact support and we will purge the stale entry. See the [geocoding documentation](administration/settings/geocoding.md) for the full story.
### Localization
#### New label translations are not shown in the planning view?
Translations and global configuration are cached in the browser to keep things fast. Press CTRL + F5 in the planning view to clear the cache and pull in the latest labels. For more detail, see [localization](administration/settings/localization.md).
---
## User Generated Content
import { FaUserCog } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "User Generated Content", link: "https://app.dimescheduler.com/usergeneratedcontent"}]} />
When a user leaves the company, you might think you just delete their account and move on. The problem: that user may have been the *owner* of a layout the whole sales team uses, a profile the dispatchers depend on, or a filter the night shift opens every morning. Delete the account without thinking, and the shared content goes with it - silently breaking views for people who never touched the departing user's stuff.
User-generated content is what Dime.Scheduler calls those owned-but-shared artifacts: profiles, layouts, and filters that a user created and shared. This page is how you find them and transfer ownership somewhere safe before you remove the original owner.
## Spotting the problem before deletion
The users grid in [user administration](administration/user/users.md) has a column in the first position that warns you when a user owns shared content. If that icon is there, you have homework before the delete button is safe to press.

Click the warning to open a dialog that lists every shared profile, layout, and filter the user owns. Reassign them in place: double-click the **Owner** column and pick a new user. Three tabs separate the three artifact types - work through them one tab at a time.

Once the warning icon is gone, the user is safe to remove.
## The standalone view
The user-generated content page is the bird's-eye version of the same workflow. Use it when you want to audit ownership across the tenant - for an audit, before a reorganisation, when a department changes hands - rather than handling one departure at a time.

The layout mirrors the user-administration views: a paged list of users on the left, three contextual tabs on the right (profiles, layouts, filters). The right pane stays disabled until you select a user, then filters to that user's artifacts. The **Clear Filter** button drops the filter and shows everything across all users, which is useful when you want to spot artifacts that *should* be owned by an admin but are sitting on a personal account.
The grids are editable. You can rename an artifact, change its owner, or change its sharing options directly here. What you cannot do is edit the content of the artifact itself - that still happens in the planning view where the user originally built it.

---
## User management
import { FaUserCog } from 'react-icons/fa';
User management is one of the first things to set up when a new tenant comes online, and one of the things you come back to most often: people join, change roles, leave. The pages in this section cover the lifecycle: create the accounts, give them the right permissions, group them when the permissions overlap, and reassign the things they own before they go.
Find them under the icon in the sidebar.
import DocCardList from '@theme/DocCardList';
---
## Roles & User Actions
import { FaUserCog } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Roles", link: "https://app.dimescheduler.com/roles"}]} />
Roles answer the question "what is this user allowed to do?" Without them, every account would either see nothing or see everything - neither of which is what a real organisation wants. A planner needs to plan but probably shouldn't be deleting users; a viewer needs to look but not change; an administrator needs to set things up but probably doesn't want to be on the hook for everyday planning. Roles are how Dime.Scheduler lets you carve up that authority.
A role is a bundle of permissions. A user can be assigned multiple roles, and their effective permission is the union - so you compose access rather than create a separate role for every combination.
## The standard roles
Dime.Scheduler ships with a set of standard roles that cover the common scenarios. Most tenants only ever use these.
- **Administrator.** Full access to the Administration module.
- **User Manager.** Access to the Administration module, but restricted to the user setup pages - cannot touch application settings, indicators, calendars, and so on.
- **Application Manager.** Access to the (Application) Settings module.
- **Personalization.** Can create and manage layouts, filters, and profiles, and share them with other users and groups.
- **Planner.** Full access to the planning view: can create, modify, and delete appointments.
- **Viewer.** Read-only access to the planning view. Cannot change anything.
## Editing roles and assigning user actions
Go to `User Management → Roles`. The page shows two grids side by side: the editable list of roles on the left, and the **user actions** that belong to the selected role on the right.

Selecting a role refreshes the right grid to show that role's permissions. Tick a checkbox to grant a user action, untick it to remove it. The column header has a tick-all/clear-all shortcut.
Changes take effect on the next sign-in. Ask the affected users to log out and back in; on sign-in, Dime.Scheduler applies the updated permissions and stores them in the browser cookie. There is nothing else to do.
## Reference: every user action and what it protects
A user action protects a specific feature: a button, a screen, a capability. When you assign user actions to a role, you are deciding which features that role unlocks.
| User Action | Secured elements |
| ------------------------------- | ---------------------------------------------------------------- |
| Global administrator | User action that allows everything |
| Add application wide URL | |
| Add assignment with drag & drop | |
| Add planning boards | Add planning boards to the profile |
| Bulk edit categories | Update appointment's categories in bulk in planned tasks |
| Bulk edit time markers | Update appointment's time marker in bulk in planned tasks |
| Bypass calendars | Skip recalculation of appointments on planning board |
| Cut | Cut appointments in planning board |
| Copy | Copy appointments in planning board |
| Paste | Paste appointments in planning board |
| Details | Can view the details of an entity (task, job, appointment, etc.) |
| Drag & drop assignment | Can drag and drop assignments in the planning board |
| Drag & drop appointment | Can drag and drop tasks on the planning board |
| Manage profile | Manage profiles |
| Map action URL | Execute action uris in the map |
| Open task action URL | Execute task action uris in the open tasks grid |
| Appointment action URL | Execute appointment action uris in the planning board |
| New | Create manual appointments |
| Edit | Update appointments |
| Remove | Delete appointments |
| Next | Can shift to the next period in the planning board |
| Previous | Can shift to the previous period in the planning board |
| Optimization | Invoke advanced scheduling algorithms (VRP solver) from the planning board |
| Plan project | Plan project in Gantt chart |
| Save | Save layouts |
| Save user defined filters | Save filters |
| Scheduler module | View planning page |
| Settings module | View settings pages |
| User administrator | An administrator that can only manage users |
| Split appointments | Enables the user to split appointments |
### Shortcut keys
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
## Read more
- [Role-based Access Control](user/authentication/roles.md)
---
## User Groups
import { FaUserCog } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "User Groups", link: "https://app.dimescheduler.com/usergroups"}]} />
User groups let you manage settings for collections of users in one place. You assign roles and user profiles to a group, and you configure data-driven security (filter values, categories and time markers) on it, exactly as you would for an individual user.
A user inherits the settings of every group they belong to. When security is assigned on both the user group and the user, the least restrictive permissions apply. A few examples:
- The user group has only the planner role, and the user has only the "Personalization" role. The user ends up with both the planner and personalization roles.
- The user group grants values A and B for filter group 1, and the user is granted value D for the same filter group. The user has permissions for A, B and D, but not C.
- The user group has profile A and the user has profile B. The user can use both profiles A and B.
Managing user groups works the same way as the [user setup](administration/user/users.md).

The user groups grid is the master grid. When you select a user group, every connected grid refreshes to show the entities assigned to that group.
To assign a role, category or anything else, tick the matching checkbox. Just make sure the correct user group is selected first.
## Shortcut keys
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
## Read more
- [User groups](user/authentication/usergroups.md)
- [User management](administration/user/users.md)
---
## Users
import { FaUserCog } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Users", link: "https://app.dimescheduler.com/users"}]} />
A user account is the bridge between a person and Dime.Scheduler: it controls *whether* they can sign in, *what* they see when they do, and *what* they are allowed to change. This page is where you create those accounts, attach the permissions and security data that decide each user's experience, and clean up after people who have moved on.
It is the busiest administration page in most tenants, because user setup is rarely one-and-done. People join, change roles, leave, swap teams, get extra responsibilities. The view is built around that churn: one grid for the users themselves, a row of contextual grids for everything that hangs off whichever user you select.

## The users grid
The central grid lists every registered user. Every other grid on this page is contextual to whichever row you select here.

Each user has these properties:
| Column | Description | Required |
| ------------ | ------------------------------------------------------------------------------------------------------------------------ | -------- |
| Type | The account type. Forms, Microsoft Entra ID, or Windows AD. See [Choosing an account type](#choosing-an-account-type). | ✔️ |
| E-mail | The user's e-mail address. Vital for forms users. | ✔️ |
| Display Name | The display name of the user. | ✔️ |
| Language | The language Dime.Scheduler renders in for this user. | ❌ |
| Time zone | The time zone Dime.Scheduler renders in for this user. | ❌ |
There is a sixth column that doesn't store data - it warns you when the user has shared content (layouts, profiles, filters) that other people depend on. More on that in [Deleting a user](#deleting-a-user).
### Adding a user
Before clicking **Add**, decide what account type you want this person to use - the choice changes which fields you need to fill in. See [Choosing an account type](#choosing-an-account-type). The default and simplest choice is **Forms**, a Dime.Scheduler-managed account.

Type, e-mail, and display name are always required. Language and time zone are optional; leave them blank and the user is prompted to pick them at first login:

Once chosen, the user can change them anytime from the preferences page:


### Deleting a user
Most user records can be deleted directly: select, click **Delete** (or press the delete key), confirm. The complication is shared content.
When a user owns shared layouts, profiles, or filters that other users depend on, the warning icon appears in the first column. Deleting that user without reassigning the content can leave broken references in the views of everyone they shared with. Hover the icon to see what needs reassigning, and use the [User Generated Content](administration/user/generated-content.md) flow to transfer ownership before deleting.
When there is no icon, the user is safe to remove.
## Attaching permissions, groups, and security data
The grids around the users grid are where you give a user their permissions and data-driven security. Each grid is filtered to the user currently selected in the central grid, so the workflow is *select user, tick what applies, move to the next user*.

The example below shows a user with every role assigned:

And one with all roles except one:

Assignment is a single click - tick the checkbox next to whatever you want to grant.
:::gotcha[Assignments need a fresh sign-in]
Changes to a user's assignments only take effect after that user signs out and back in. If a planner just lost a permission they should no longer have, ask them to log out before assuming the change "didn't work".
:::
The contextual grids and what each one controls:
| Grid | What it does |
| ------------- | ----------------------------------------------------------------------------------------------------------- |
| Roles | Assigns [roles](administration/user/roles.md) (Planner, Administrator, Viewer, …) to the user. |
| User Groups | Adds the user to [user groups](administration/user/usergroups.md). |
| User Profiles | Marks which shared profiles are available to the user. |
| Filter values | Restricts the user to specific filter values. Empty means "access to all". |
| Categories | Restricts the user to specific categories. Empty means "access to all". |
| Time Markers | Restricts the user to specific time markers. Empty means "access to all". |
The last three matter most for what a planner can see and do in the planning view. They are how [data-driven security](user/authentication/datadrivensecurity.md) works in practice - "this planner only sees jobs in the Northern region" is enforced by assigning them only the Northern filter value.
## Choosing an account type
Every user account has a *source*: the system that holds the credentials and authenticates the sign-in. Dime.Scheduler supports two:
### Microsoft Entra ID
An account that already exists in Microsoft Entra ID (the service formerly known as Azure Active Directory). The user signs in through the Microsoft sign-in screen with their existing organisational credentials. This is the right choice when the organisation already uses Office 365 / Microsoft 365 and wants Dime.Scheduler to live inside the same identity system - single sign-on, centralised offboarding, the works.
Required fields: e-mail and display name. The e-mail must be a real Entra ID address.
:::partner[Grant consent once, for the whole tenant]
Users can individually grant consent, or the administrator can grant consent on behalf of the entire organization.
For **production**, [grant consent](https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=22e311a5-ec38-4d0c-887b-37f1e60efc54&response_type=code&redirect_uri=https%3A%2F%2Fapp.dimescheduler.com%2Fsignin-microsoft&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&prompt=select_account).
To grant access to users on the **sandbox**, use the [sandbox consent page](https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=c0ad9485-cc69-43ef-8f75-2cf9365abd66&response_type=code&redirect_uri=https%3a%2f%2fsandbox.app.dimescheduler.com%2fsignin-microsoft&scope=https%3a%2f%2fgraph.microsoft.com%2fuser.read&prompt=select_account).
:::
### Dime.Scheduler identity service (Forms)
A forms account is stored in Dime.Scheduler's own database. The user signs in with their e-mail and a password. Pick this when there is no central identity system to integrate with, or when the user does not have an Entra ID account.
A random password is generated on account creation; you can pass it on, and the user also receives an e-mail with a reset link. They will be prompted to change it on first login.
Password policy:
- At least 6 characters
- At least one digit
- At least one lower-case letter
- At least one upper-case letter
- At least one special character
Required fields: e-mail and display name. Language and time zone are optional - if blank, the user picks them on first login.
## Reference
### Shortcut keys
| Shortcut key | Action | Remark |
| --------------------- | --------------------------------------------------------------------- | ------------------------------------ |
| DELETE | Deletes the selected records. Is the equivalent of the delete button. | Only when a delete button is present |
| CTRL + click on group | Collapses the other groups in the grid | Only possible when grouping is set |
## Read more
- [User groups](administration/user/usergroups.md)
- [Roles](administration/user/roles.md)
---
## Add a deep-link action to the planning board
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
The loop this closes: a planner spots that an appointment needs a follow-up, switches to another system, retypes the customer's details, then comes back. An [action URI](/user/features/actionuri) collapses that into one click, carrying the record's data with it so the destination already knows what to open or prefill.
Decide which verb you need first, because it changes the shape of everything else:
- **GET** is the workhorse. Most back-office systems expose deep-link URLs that open a specific record, and Dime.Scheduler opens the resulting URL in a new tab.
- **POST** is for pushing a structured payload, typically into a workflow engine expecting an inbound webhook.
If you are deep-linking into a record, you want GET.
## 1. Know what scopes an action to a record
Two things decide whether an action appears on a given record, and both are common sources of "my action doesn't show up":
- **The entity type.** Planned task, unplanned task, map pin, new appointment. An action declared for the open task list never appears on the planning board, and the reverse is equally true.
- **The source app and source type** of the job behind the record. This is what stops a tenant connected to several back offices from showing a dozen irrelevant menu items on every record.
The available placeholders depend on the entity type you bind to, so this choice comes before you write the URL.
## 2. Build the URL
Find the URL that opens the record in the target system, then substitute placeholders where the record-specific values go. Business Central, for instance, documents its web-client URL format, and most systems have an equivalent.
Which placeholders you can use depends on the entity type you bound the action to in step 1, so check the list for *your* entity type rather than assuming they are shared. [Actions](/administration/settings/actions) has the full set per entity type.
## 3. Write the description carefully
The **description** column is the entire user experience of this feature. It is the only thing a planner ever sees.
Keep it short and verb-led: "Create absence", "Open in BC", "Email customer". A description like "BC Integration Action 2" is a feature nobody uses.
## 4. Test from the context menu
Right-click the record in the [planning board context menu](/user/components/planningboard/contextmenu) and confirm the item appears and resolves.
The [map](/user/components/map) works differently and is worth testing separately if you use it. Select the pin or pins, then click the anchor icon to get the list of available actions. Because you can select several pins at once, one action can cover every selected task, which is how you drive back-office logic over a batch.
One constraint on that: multi-item actions are only offered when **all selected items share the same source app and source type**. A mixed selection silently offers nothing, which looks like a broken action rather than a rule.
## Verify
Trigger it on a real record and confirm the target system opens the *right* record, not just any page. A placeholder that fails to resolve usually lands you on a generic landing page rather than throwing an error, so a successful-looking click is not proof.
Then check an entity type you did *not* bind, and a record from a different source app, and confirm the action is absent from both.
## Related
- [Automate notifications with Zapier](/cookbook/zapier) if you want events pushed out rather than a planner-triggered jump.
- [Set up a webhook connector](/cookbook/webhook-connector) for programmatic outbound events.
---
## Ad-hoc service planning with drag and drop
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
This is the reactive workflow: work arrives, it needs slotting in today, and there is no project structure to respect. If your work spans weeks and has dependencies, you want [the Gantt chart](/cookbook/gantt-project) instead.
Know which kind of task you are looking at, because only one of them can be dragged. The [open tasks](/user/components/opentasks) grid holds two kinds:
- **Planning tasks** are independent units of work. Drag one to a board and you have created an appointment.
- **Project tasks** belong to a project and are managed by the Gantt chart. They **cannot** be dragged onto the planning board at all. To schedule one, open its project from the row's context menu.
The **Task Type** column tells them apart. If you work with both, put that column on the grid, otherwise you will eventually spend a few minutes trying to drag something that will never move.
## 1. Open the open tasks grid
The grid is the planner's inbox: every task the back office sends that still needs scheduling appears here until it lands on a board.
A task stays open until it is *entirely* planned. Budgets can require a certain amount of working time before a task drops off the list, which is what lets one task be split across several appointments rather than disappearing after the first.
## 2. Narrow to the work you can actually plan
Tasks arrive carrying filter values from the back office: skills required, regions covered, departments involved. The **filter icon** next to a row turns those into a one-click resource filter, narrowing the board to the resources eligible for that work.
If a [resource filter component](/user/components/resourcefilters) is on the workspace, it picks up the same values automatically.
Watch the clear-filter button: it turns **red** while a filter is active. That is the tell when the board looks emptier than it should.
## 3. Drag it onto the board
One gesture. Drag a row, drop it on a time slot on the [planning board](/user/components/planningboard), release. The drop position sets both things an appointment needs at once: the resource is the row you drop on, the time is where along the row you drop.
Three variations are worth knowing, because they are the difference between this being fast and being tedious:
- **Several tasks at once.** Select rows with the checkbox, CTRL-click, SHIFT-click, or the header checkbox for the whole page, and drop them as a group. By default they line up **consecutively** on the same resource.
- **Simultaneously instead.** Hold **CTRL while dropping** and the selected tasks go on at the same time rather than back to back. This is what you want when several resources do the same work together.
- **Ignore a container for once.** A task [container](/user/features/containers) bundles related tasks, for example loading a lorry at the depot and unloading it at the customer. Planning any task in a container plans all of them, which is usually the point. To plan just the one task in hand, hold **SHIFT before you start dragging**. Note that it is before the drag, not during the drop, unlike the CTRL modifier above.
## 4. Adjust in place
Once it is on the board, refine it there rather than reopening dialogs. Drag to move, drag an edge to change duration, and use the [appointment](/user/components/planningboard/appointments) card for anything the gesture cannot express.
## Verify
Confirm the task left the open tasks grid. If it is still there, it was only partly planned, which is expected behavior when a budget requires more time than the appointment you just created covers.
## Related
- [Plan a recurring appointment](/cookbook/recurring) for work that repeats.
- [Optimize routes for a technician](/cookbook/routes) once the day is populated.
- [Long-term project planning with the Gantt chart](/cookbook/gantt-project) for structured work.
---
## Extend the appointment card with templates
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Do not confuse this with [field templates](/cookbook/field-templates). They sound alike and solve opposite problems:
- **Field templates** *display* existing data, composing a readable subject and body from fields the back office already sends.
- **Appointment templates** *capture* new data, adding tabs and fields to the appointment card that planners fill in, and sending the result back to the back office.
If you want the board to read better, you want field templates. If you want the planner to record something that has nowhere to live today, you are in the right recipe.
Have the back-office side settled before you build the template. The data flows back, so something at the far end needs to be ready to receive it.
## 1. Understand the shape
An [appointment template](/user/features/appointment-templates) adds one or more **tabs** to the appointment card. Each tab holds a set of fields: text, numbers, dates, yes/no, dropdowns. Planners fill them in as they work, and the values travel back to the back office with the rest of the appointment.
The important design property is that templates are **scoped to source types**. A maintenance job and a sales visit can each show only the fields that matter to them, rather than one bloated card carrying every field anyone ever asked for.
## 2. Define the fields and the template
Build the fields and assemble them into a template in [appointment templates](/administration/settings/appointmenttemplates).
Group fields into tabs the way the planner works, not the way the database is shaped. A single tab of thirty fields is where this feature goes wrong: planners stop filling it in, and half-complete data is worse than none.
Field type matters more than it looks. A dropdown produces data you can report on; a free-text field produces data you cannot. Reach for text only when the answer genuinely is prose.
## 3. Assign the template to source types
This is the step that makes the template appear at all, and the one most likely to be missed. Bind the template to the source types it applies to. An unassigned template is configured, saved, and invisible.
If your tenant feeds from several back offices, be deliberate here: the same source type name can exist in more than one source app.
## 4. Verify on a real appointment
Open an appointment of a matching source type on the [planning board](/user/components/planningboard/appointments) and confirm the new tab appears with the fields you defined.
Then close the loop properly. Fill a field in, save, and check the value **arrives in the back office**. The tab rendering is only half the feature; a template that displays but does not round-trip is the failure mode worth catching before rollout.
Finally, open an appointment of a *non-matching* source type and confirm the tab is absent. That verifies the scoping does what you intended rather than applying everywhere.
## Related
- [Generate appointment subjects with field templates](/cookbook/field-templates) for the display-side counterpart.
- [Populate a fresh instance from code](/cookbook/quickstart-developer) if you are pushing appointment field values over the API.
---
## Connect Business Central
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Check your BC version against the [requirements](/integrations/bc/requirements) first. The connector works on every supported version, but the specifics differ between waves, and finding that out halfway through the wizard is a poor use of an afternoon.
The mental model that makes the rest straightforward: the integration is **two independent channels, one in each direction**. BC pushes jobs, tasks, and resources into Dime.Scheduler. Dime.Scheduler pushes planning back into BC. You configure each one separately, and the loop only closes when both exist. A setup where data arrives but planning never returns almost always means the second channel was never built.
## 1. Install the extension in Business Central
The [extension](/integrations/bc/install/extension) is the codeunit and pages that know how to speak Dime.Scheduler's language. Without it BC has nothing to say.
See the [BC integration overview](/integrations/bc/intro) for what the connector adds to your environment.
## 2. Connect BC to Dime.Scheduler
Run the **FastTrack Wizard** in BC. You point it at a Dime.Scheduler tenant, authenticate, and choose which modules travel: jobs, service, assembly, manufacturing, and the rest.
Be deliberate about the module selection. Every module you enable is master data that starts flowing and needs to be right. Turning on everything "to see what happens" produces a noisy first sync that is harder to diagnose than a narrow one.
See [connecting BC to Dime.Scheduler](/integrations/bc/install/bc-to-ds).
## 3. Connect Dime.Scheduler back to BC
Add a [connector](/administration/connectors) in Dime.Scheduler that knows where to reach this BC instance and how to authenticate against it. This is the return path, and it is what makes the planning show up in BC.
See [connecting Dime.Scheduler to BC](/integrations/bc/install/ds-to-bc) for the web service URL and authentication options.
## 4. Verify the first sync
Go to [transactions](/administration/transactions) and confirm data actually arrived. Then plan something in Dime.Scheduler and confirm it lands in BC, because that exercises the second channel, which the first check does not.
If anything failed, the [errors page](/administration/transactions/errors) lists every faulted appointment, split into **Dynamics**, **Exchange**, and **Webhook** tabs so you can work one integration at a time.
## What real deployments add next
The three steps above give you a working trial. Anything running on real data goes on to tune behavior, and all of it is off by default:
- **Safeguards** against deleting work that has already been planned.
- A **scheduled job** that keeps master data fresh.
- An **outbound queue** that logs and optionally buffers everything BC sends, which is worth having before you need it.
See [configuring the connector](/integrations/bc/config/intro). Budget time for **permission tuning** as well, so the service accounts on both sides have exactly the access they need and no more.
## Related
- [Diagnose a failed Business Central sync](/cookbook/diagnose-sync) for when step 4 does not go to plan.
- [Sync appointments bidirectionally with Exchange](/cookbook/exchange-bidirectional) to add calendars on top.
---
## Lock down with data-driven security
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Dime.Scheduler secures access in two independent layers, and mixing them up is the usual source of confusion:
- **Role-based access control** answers *what can this user do?* Create appointments, delete them, save layouts.
- **Data-driven security** answers *what can this user see?* Which tasks land in their open task list and which resources appear on their board.
This recipe is about the second. If your problem is "this planner should not be allowed to delete appointments", you want [roles](/administration/user/roles) instead.
One principle governs everything below, and it is the opposite of what most people assume. Dime.Scheduler follows **management by exception**: when a user has *no* values assigned for a filter group, that group is ignored for them and they see everything in it. Blank means unrestricted, not restricted. This keeps the setup maintainable, because adding a new value to a group does not require revisiting every user, but it does mean a half-configured user is an unrestricted user.
## 1. Understand the model
[Filter groups](/user/authentication/datadrivensecurity) are logical groupings; filter values are the individual entries. Region with EMEA, LATAM and APAC. Skill with the skills you care about. Department, and so on.
You assign filter values to three kinds of entity, and each assignment means something different:
- On a **task**, a filter value is a *requirement*: what it takes to be allowed to plan this work.
- On a **user**, it is what that planner is *authorized for*.
- On a **resource**, it is what that resource is *qualified for*.
Start by reading [authentication and authorization](/user/authentication/intro) for how the two layers sit together.
## 2. Design the groups before you create them
This is the step people skip, and it is the one that costs the most to undo. Every filter group you add multiplies the assignments you have to maintain across users, resources, and tasks.
Model only the dimensions that genuinely gate access. Region and department usually do. Skill often does not: it describes who is *suitable* rather than who is *permitted*, and suitability is better handled with [resource filters](/user/components/resourcefilters), which guide the planner without hard-blocking them.
## 3. Configure the rules
Create the groups and values, then assign values to your users, resources and tasks. Remember the exception principle while you do it: leaving a group blank on a user grants access to all of it.
The matching runs at **filter group level**, and it runs automatically. A planner sees a task only if they satisfy the task's requirements in every group where they have values assigned. The same calculation then runs over resources, so a planner only sees resources they are cleared for.
Worth knowing: the check is enforced for the *planner*, but not for the *assignment*. Dime.Scheduler decides which tasks and resources a planner can see, and then trusts them to build a sensible plan from what remains. A planner can assign a task to a resource that does not meet its requirements. If you need to steer that choice, [resource filters](/user/components/resourcefilters) are the tool.
## 4. Consider categories and time markers
Data-driven security is not limited to filter values. [Categories and time markers](/administration/settings/indicators) carry appointment status, and you can secure on those too. The classic use is filtering out appointments marked **Completed** so nobody re-plans finished work.
The same management-by-exception rule applies here.
## Verify
Test with a real account, not by reading the configuration. Sign in as a restricted planner and confirm two separate things: that their open task list excludes work outside their scope, and that their board excludes resources outside it. Those are two different calculations and one can be right while the other is wrong.
Then test the inverse. Sign in as a planner who should see everything and confirm they still do, because an over-tight rule is easy to miss when you only ever test the restricted case.
## Related
- [Assign the rules and test](/user/authentication/roles) for role-based access control.
- [Onboard a new planner](/cookbook/onboard-planner) to create the accounts you are securing.
---
## Diagnose a failed Business Central sync
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Do not start by guessing at configuration. Every failure carries a **stable error code** in square brackets, and that code is the whole diagnosis. `[DSBC003] BC server too busy` will always be `DSBC003`, so the code is also searchable across transactions and logs to find every appointment that hit the same problem.
The single most useful distinction, because it decides whether you should do anything at all:
- **Retried** codes are picked up automatically by the retry job, which backs off between attempts and leaves the row `Failed` for the next pass if it runs out. Transient network and throttling failures live here, and they usually clear themselves without you doing anything.
- **Fix configuration** codes are *not* retried. They need a connector or back-office change first, and waiting will not help.
Retrying a fix-configuration error by hand is the most common wasted move.
## 1. Find the failing transaction
Open the [errors page](/administration/transactions/errors). It lists every appointment in the faulted state, split across **Dynamics**, **Exchange**, and **Webhook** tabs, so you are only ever looking at one connector family at a time.
Click the magnifying glass to drill in. The detail view shows the appointment, the connectors assigned to it, and every transaction with its status. That last part matters: an appointment can have several connectors, and only one of them may be failing.
## 2. Read the code
Take the code from the square brackets and look it up in [transaction errors](/integrations/bc/transaction-errors). Each entry pairs what it means with what to do.
The prefix already tells you where to look:
- `DSCFG*` is connector configuration. Authentication, URLs, missing fields, Entra ID registration and consent.
- `DSHTTP*` is transport. Transient 429s and 5xx responses, proxy tunnel failures. These retry.
- `DSBC*` is Business Central itself. Unauthorized requests, endpoints not found, a server too busy.
If the failure shape is **undiagnosed**, it has no rule yet. Send it to support so one can be added rather than trying to reverse-engineer it.
## 3. Apply the fix and re-run
Make the change the error calls for, then retry. You can retry a single transaction from within its connector's grid on the detail page, or retry from the list view directly.
For anything broader, [BC troubleshooting](/integrations/bc/troubleshooting) is searchable by symptom rather than by code, which is the better entry point when you have a behavior rather than an error.
## 4. Archive what you are not going to fix
If a failure is safe to ignore, archive it rather than leaving it in the list. You can archive everything at once or one appointment at a time.
This is worth doing rather than skipping. An errors page with a permanent backlog of known-ignorable failures stops being something anyone checks, and the next real failure hides in it.
## Verify
Confirm the retried transaction reaches a successful state, not merely that it left the errors list. Then re-run the original action end to end, since a transaction succeeding on retry and the workflow being correct are two different things.
## Related
- [Connect Business Central](/cookbook/connect-bc) for the setup this diagnoses.
- The [outbound queue](/integrations/bc/config/outbound-queue) logs everything BC sends, which is what you want configured *before* the next incident.
---
## Sync appointments bidirectionally with Exchange
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
This is the longest recipe in the book, and most of the time goes on the Microsoft side rather than on Dime.Scheduler. The connector runs on **Microsoft Graph**, which means an Entra ID app registration with the right permissions and consent. Line that up with whoever owns your tenant before you begin, because it is the step most likely to stall waiting on somebody else.
Know the default before you configure anything: **every appointment assigned to a synced resource ends up in that person's Outlook calendar**. That is rarely what anyone wants. Temporary appointments and placeholders add no value in a calendar and clutter it. The rule engine in step 5 is what narrows it, so treat that step as required rather than optional.
## 1. Understand the two directions
The [Exchange connector](/integrations/exchange/intro) is genuinely bidirectional, and it is two mechanisms rather than one:
- **Outbound**: something listens for planning board changes and pushes them into Outlook.
- **Inbound**: Microsoft notifies the connector when a calendar changes, and the connector adjusts the planning to match.
They fail independently. When one direction works and the other does not, this is the split to reason about.
## 2. Register the Entra ID app
[Registering the app](/integrations/exchange/azureapp) is how your tenant decides exactly which permissions an external application gets. Everything downstream depends on it, and a permissions gap here surfaces much later as a sync that silently does nothing.
## 3. Install and configure the connector
Follow [installation](/integrations/exchange/installation) and [configuration](/integrations/exchange/configuration).
## 4. Map calendars to resources
Resources manage their own [Exchange preferences](/integrations/exchange/resources). When a resource is added to the connector, either by an administrator or by the scheduled resource sync job, the system creates a user account for that resource.
There are two account types, administrator and resource, and a resource account can only touch its own preferences. Resources control whether calendar events sync back to Dime.Scheduler, and can scope that by **busy status** (the "Show as" field in Outlook) and **sensitivity** (the "Private" toggle).
Administrators can always override these from the connector's resources page, which is the thing to check when one person's calendar behaves differently from everyone else's.
## 5. Tune the rule engine
The [rule engine](/integrations/exchange/ruleengine) decides which appointments are allowed through to Outlook. Rules take effect immediately when changed, with no recompile.
Two ways to author them. The **visual workflow builder** is drag-and-drop with conditional branches and a real-time preview of which appointments would sync. The underlying **rule syntax** gives you access to the entire data graph, so you can filter on the appointment, the planning lines, the service order, and customer data. Both views produce the same definitions and you can move between them.
Start restrictive. It is far easier to widen a rule after planners ask for more than to explain why private calendars filled with placeholder appointments.
## 6. Verify both directions separately
Test outbound and inbound as two separate exercises, because passing one says nothing about the other:
- Plan an appointment in Dime.Scheduler and confirm it appears in the resource's Outlook calendar.
- Change something in Outlook and confirm the planning follows.
Then test an appointment your rules should **exclude** and confirm it does *not* reach Outlook. A rule engine that passes everything looks identical to a correctly configured one until the day it does not.
See [bidirectional sync](/integrations/exchange/bidirectional) for the full behavior.
## If something does not arrive
The [Exchange logs](/integrations/exchange/logs) and the **Exchange** tab of the [errors page](/administration/transactions/errors) are the two places to look. [Deltas](/integrations/exchange/deltas) explains how the connector tracks what changed, which is usually the missing piece when a sync appears to skip an appointment.
## Related
- [Connect Business Central](/cookbook/connect-bc) for the back-office side.
- [Categories](/integrations/exchange/categories) to keep board colors aligned with Outlook categories.
---
## Roll out the mobile app to field workers
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
The one thing that makes or breaks this rollout is the **e-mail match**. A field worker's user account and their resource record have to carry the same e-mail address, because that is how the app works out which resource's schedule to show them. Get it wrong and the app signs in successfully to an empty schedule, which looks like a broken app rather than a configuration slip.
So do the resource first, the user second, and copy the address across rather than typing it twice.
## 1. Create the resource record
The field worker needs to exist as a [resource](/administration/settings/resources) before they can be scheduled at all. Fill in the e-mail field on the resource, not just the name.
While you are on the resource card, decide whether this person needs GPS tracking. The **Enable GPS tracking** flag lives here, and turning it on later means coming back to this screen.
## 2. Create the user account
Create the [user account](/administration/users) with the **same e-mail address** as the resource. A forms account is the usual choice for field workers, since they often have no Entra ID identity in your tenant.
For a forms account Dime.Scheduler generates a random password and sends a reset link by e-mail. Field workers should watch for that e-mail: it is how they set the password they will use in the app.
## 3. Install the app
The app is on both stores, and the [installation page](/user/mobile/installation) carries the direct links and the current minimum OS versions. Check those before a rollout to a fleet of older handsets, because a device that falls short simply cannot install the app and you want to know that before the rollout day rather than during it.
## 4. Sign in
Two things happen on first launch, and only one of them is obvious.
The [sign-in screen](/user/mobile/signin) takes the e-mail and the password from the reset e-mail. Then, the first time the app runs (or after a reinstall), it also asks which **mobile backend** to connect to. Choose **Production** for live use. Sandbox is for evaluation before a production environment exists, and a field worker who picks it will sign in fine and see none of their real work.
Tell people about the backend prompt in advance. It is the single most common support call of a mobile rollout.
## 5. Enable location tracking, if you need it
[Location tracking](/user/mobile/location-tracking) turns the app into a location sensor, which beats buying dedicated hardware. It is **off by default** and there are two prerequisites worth knowing before you promise it to anyone:
- Pushing a location onto the map in the web app requires the **advanced map license**.
- The device must grant the **Always** location permission. This sounds worse than it is, and it is worth explaining to the field worker rather than letting them discover it in a permission dialog: the app does not track around the clock. It only sends updates during the hours configured on the tracking screen, and the "always" permission is simply what the operating system requires for an app to use location from the background.
Field workers can set their own tracking window and pause it for exceptions.
Set expectations on reliability too, because this is where the feature disappoints people who were promised live tracking. The app asks the device to run its background service on an interval, but the **device decides** whether to honor that, based on battery, memory, and how often the app is used. iOS is the strictest and can suspend the service outright for someone who has not opened the app in a while. Regular use keeps it running. [Location tracking](/user/mobile/location-tracking) has the current interval and the platform specifics.
## Verify
Sign in as the field worker on a real device and confirm their own appointments appear. An empty schedule with a successful sign-in almost always means the e-mail on the user and the e-mail on the resource do not match.
## Related
- [Onboard a new planner](/cookbook/onboard-planner) for the desk-side equivalent.
- [Optimize routes for a technician](/cookbook/routes) once field workers are live.
---
## Generate appointment subjects with field templates
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
The problem this solves: an appointment arriving from the back office carries raw data. A job number, a task description, a customer name. The planner sees "Task 4729" when what they need is "Service call - ACME Corp - 14 Main Street".
Field templates compose that readable string on Dime.Scheduler's side. The back office keeps sending its standard data and needs no changes, which is the whole point: this is the cheap fix when the alternative is a back-office development request.
One limitation to understand before you invest any time, because it changes how you test:
:::caution
Template changes do not affect **existing** appointments. Only new records coming in from the back office render with the updated template. To refresh an existing appointment's subject or body, you generally have to push it through the back office again.
:::
## 1. Understand the building blocks
A [field template](/administration/settings/fieldtemplates) is an ordered list of three kinds of block:
- A **field** resolves to a property on the job or task at render time. Drop in `JobNo` and you get the job number.
- **Free text** is literal text between the fields: separators, labels, punctuation.
- A **new line** inserts a line break. Useful in the body, rarely in the subject.
Render time fills in the field values and concatenates everything into a single string.
## 2. Compose the subject
Select the template on the left; it loads on the right. Drag a block type from the top-right corner and drop it where you want it. Then pick the source field, or type the literal text.
Reorder by dragging, or with the caret buttons. Remove with the trash icon.
Design the subject for a **narrow appointment bar**, not for the editor. Subjects get truncated on a dense board, so put the most identifying value first. Customer name usually beats job number, because a planner recognizes "ACME Corp" faster than "4729".
## 3. Compose the body
The body has room to breathe, so this is where new-line blocks and fuller labels pay off. It is what a planner reads once they have already found the appointment, so it answers "what exactly is this?" rather than "which one is this?"
## 4. Save
Edits stay **pending** until you press **Save** on the right-hand side. Until then they live only in your browser, so navigating away loses them. This catches people out, since the editor looks like it has already applied the change.
## Verify
Do not test by re-reading the template. Push a **fresh** appointment through from the back office and look at it on the board, because existing appointments will keep their old text no matter how correct your template is and will make a working template look broken.
Check the subject at realistic board density, where truncation actually happens, rather than on a wide empty board.
## Related
- The same page has a **Tooltip** tab that uses the same drag-and-drop builder against a richer field library. See [planning board tooltip](/administration/settings/planning-board-tooltip).
- [Extend the appointment card with templates](/cookbook/appointment-templates) when you need to *capture* extra data rather than display it.
---
## Long-term project planning with the Gantt chart
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Two facts shape everything else in this recipe.
**Projects are created in the back office.** Dime.Scheduler plans them; it does not create them. If there is no project to load, that is a back-office task, not a Gantt one.
**Nothing you do in the Gantt is committed until you sync.** Every edit is local. That is the point of the chart: it is a sandbox where a project manager can try ideas, undo them, and only push a plan that holds together.
## 1. Load the project
Two routes into the [Gantt chart](/user/components/gantt):
- The load-project icon on the Gantt itself, which opens a window listing every available project.
- The **Open project** item on the context menu of a project task in the open tasks, planned tasks, or route sequence grids.
Be aware that loading is not always limited to what you picked. When there are cross-project dependencies, Dime.Scheduler loads the entire graph, including projects you did not select.
## 2. Reshape the plan
Draw a dependency by dragging from one task's anchor to another's. A tooltip validates the link as you drag. Double-click the line to set its **type** (start-to-finish, finish-to-start, finish-to-finish, start-to-start) and its **lag** in days.
Constraints are the other half. Where dependencies wire tasks to each other, a constraint pins one task to a date: must start on, must finish on, start no earlier or later than, finish no earlier or later than. If a later edit would break one, the chart offers three ways out: cancel the change, drop the constraint, or move the task to satisfy it.
Editing a group cascades to everything beneath it. Push a phase out two weeks and every leaf task moves, and sibling dependencies fire too. That cascade is the feature and the hazard in equal measure, so make top-level moves deliberately.
Watch the **sync icon**: it turns red the moment anything changes. Undo and redo work on local changes, and reload throws them all away.
## 3. Use the lenses while you work
Several toggles exist to cut through a dense project:
- **Critical path** shows the chain that pushes the end date if anything in it slips. A long red line means tight slack everywhere, so it doubles as a risk readout.
- **Progress** draws a line per task against today: right of today is ahead, left is behind.
- **Baseline** shows the original plan frozen at sync time, underneath the current bars. This is the only reliable answer to "how far have we drifted?"
- **Task groups** shades the work breakdown so phases are obvious.
- **Rollup** controls whether subtask detail aggregates into the summary bar, project-wide from the toolbar or per task in the editor.
## 4. Assign resources
The **Resources** tab on the task editor is where the plan meets real people. Pick resources and set each one's percentage of involvement: 100% is full-time, 20% is one day a week.
## 5. Sync, and let appointments materialize
On sync, Dime.Scheduler reads those assignments and creates appointments on the planning board automatically.
The conversion takes two inputs, and understanding it prevents most surprises: the task's **dates** set the appointment boundaries, and the **calendars** (project plus resource) decide which hours inside those boundaries are actually working time. A task that nominally runs Monday to Friday becomes appointments only on the days and hours that resource actually works. The project stays an abstract plan; the board stays concrete.
Generated appointments carry an **anchor icon** so planners can tell them from manually scheduled work.
## 6. Know who wins when both sides edit
Planners can still nudge generated appointments on the board. Those edits are flagged back on the task assignment with a warning icon and a grid listing every change, so the project manager can see what happened.
The project manager has the last word: the next sync can override the planner's local edits, behind a confirmation prompt so the override is never accidental.
Worth telling both sides about up front. A planner who does not know this will lose an adjustment and assume it is a bug.
## Verify
After syncing, check the [planning board](/user/components/planningboard) and confirm the appointments landed on the days you expected. If a task produced fewer appointments than its span suggests, that is the calendar doing its job, not an error.
## Related
- [Ad-hoc service planning](/cookbook/adhoc-planning) for work with no project structure.
- The PDF export button renders the chart for meetings and email.
---
## Color-code the planning board with indicators
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Pick the right kind of indicator first. There are four, and they are not interchangeable:
- A **category** answers *what kind of work is this*. Order type, work type, status. An appointment has exactly one.
- A **time marker** answers *what is happening during this appointment*. Lunch, standby, travel, a customer meeting inside a longer job. Also exactly one.
- A **pin** answers *what kind of thing is this on the map*. Pins live on tasks, not appointments, and have no planning board equivalent.
- A **tag** answers *what else do I need to know*. Tags are the only multi-valued kind, and they are currently in preview.
The category and time marker distinction is the one to get right up front, since both are single-valued colors and it is tempting to treat them as the same thing. The rule of thumb: use the **category** for the classification you sort and report on, and the **time marker** for the state that changes during the day.
Also worth knowing before you start creating things: the back-office application is usually the master for indicators and sends them across with the planning data. What it typically does *not* send is what each one should look like, because that is a visualization choice. So in practice you create indicators by hand only when the back office is not doing it, and spend most of your time on appearance.
## 1. Define them in administration
Each kind has its own tab in [indicators](/administration/settings/indicators). Click **Add**, or the pencil to edit an existing row. Each tab exports to CSV, and the search box earns its keep once a catalog outgrows a screen.
Categories carry one extra column, **Outlook Preset**, which maps the category onto one of the fixed Outlook category colors. It only matters if you sync appointments to Exchange, in which case it keeps the color a planner sees in Outlook aligned with the board.
## 2. Choose colors that survive a dense board
This is where the recipe succeeds or fails, and the reason is that the four kinds render at wildly different sizes. A category fills the whole appointment bar; a time marker gets a thin strip on it. [Indicators](/administration/settings/indicators) has the exact mapping of each kind to where it appears.
Two consequences follow from that size difference. Categories dominate the board, so they want the clearest and most distinct palette you have, and a subtle category color washes out everything around it. A time marker only ever gets a sliver, so a low-contrast color there is effectively invisible no matter how good it looks in the picker.
See [appointment visualization](/user/setup/visualization) for choosing a palette that holds up.
## 3. Put the indicators panel on the workspace
The [indicators component](/user/components/indicators) does double duty: it is the legend that tells a planner what the colors mean, and it is the fastest way to change them. Add it to the profile from the `Profile` menu.
Without it, a new planner is looking at a board of colors nobody has explained.
## 4. Apply them
Drag an indicator from the panel onto an appointment. Validation runs during the drag: a green check in the tooltip means the drop will land, a red cross means the target is not a valid appointment and the action is cancelled. Locked appointments and records the planner cannot update refuse the drop regardless of what the tooltip said.
For more than a handful, select the appointments on the board first, then click the bulk-update icon next to the indicator. One round-trip instead of fifty drags.
One behavioral difference to flag to planners: dropping a category or time marker **replaces** the current value, because there is only one slot. Dropping a **tag adds** it to whatever tags are already there. Drop three tags and the appointment carries three. Clearing them is a different gesture, via right-click and **Remove tags**, or one at a time from the appointment card.
## Verify
Load a board with real data and check it reads at a glance, from a normal viewing distance, without hovering anything. If you have to squint to tell two categories apart, the palette is wrong, not the planner.
## Related
- [Build a personal workspace](/cookbook/workspace) to get the indicators panel onto the profile.
- [Generate appointment subjects with field templates](/cookbook/field-templates) for the text that goes alongside the color.
---
## Onboard a new planner
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Decide which account type the new planner gets, because it changes what you fill in and how they receive credentials. A **Microsoft Entra ID** account reuses an identity that already exists in your tenant, so there is no password to hand over. A **forms** account lives in Dime.Scheduler's own database and is the fallback when there is no central identity system to integrate with.
You will also want to know which role the planner should get before you start, rather than deciding it in the dialog. In most tenants the answer is simply **Planner**.
## 1. Create the user account
Go to `User Management → Users` and add the user. Both account types need the same two things: an **e-mail address** and a **display name**. For Entra ID the e-mail has to be a real address in your tenant, since that is what the sign-in is matched against.
Language and time zone are optional. Leave them blank and the planner picks them on first login, which is usually the better default for a distributed team.
For a forms account, Dime.Scheduler generates a random password and e-mails the planner a reset link. You can pass the generated password on directly, but you do not have to: they are prompted to change it on first login either way.
See [users](/administration/users) for the full dialog.
## 2. Assign the role
A [role](/administration/user/roles) is a bundle of permissions, and a user can hold several at once: their effective permission is the union. That means you compose access rather than inventing a new role for every combination.
For a planner, start with the standard **Planner** role, which grants full access to the planning view including creating, modifying, and deleting appointments. Add **Personalization** on top if you want them to build and share their own profiles, layouts, and filters. If they should look but not touch, use **Viewer** instead.
One thing that catches people out: **role changes take effect on the next sign-in**, not immediately. If you adjust a role while the planner is logged in, ask them to sign out and back in.
## 3. First sign-in
Point the planner at your Dime.Scheduler URL and tell them which [sign-in method](/user/authentication/types) to use, since the login screen offers both and they have no way to guess. Entra ID users clicking the Azure / Office 365 button are asked once to consent to Dime.Scheduler using their credentials.
If you left language and time zone blank in step 1, the planner is prompted for both on that first launch. Time zone matters more than it looks: it drives how every appointment time is rendered for them, so it is worth getting right rather than clicking through.
See [logging in for the first time](/user/guides/logging-in-for-the-first-time).
## 4. Give them a workable starting view
A new planner lands on the standard profile, which is deliberately generic. Leaving them there is the most common reason a rollout feels worse than the demo did.
The quickest fix is to build one profile that matches how the team actually plans and make it their default: add the components they need, remove the ones they do not, then save the profile and set a default layout for each component. Step 4 of this recipe links to [create a profile](/user/guides/create-profile-layouts), which walks the whole thing end to end.
Note that building profiles requires the **Personalization** user action, so if you skipped it in step 2 the planner cannot do this themselves and you will need to build the profile for them.
## Verify
Have the planner sign in and confirm three things: the planning view opens rather than an empty page, the components they expect are on screen, and they can drag a task onto the board and save it. If the board renders but nothing can be saved, the role is the thing to check first.
## Related
- [Build a personal workspace](/cookbook/workspace) picks up where step 4 leaves off.
- [Lock down with data-driven security](/cookbook/data-security) if this planner should only see part of the data.
---
## Populate a fresh instance from code
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
The single most important thing about this flow is **order**. Parents must exist before children: jobs before tasks, filter groups before filter values, indicators before you assign them. Get the order wrong and calls fail in ways that look like authentication or schema problems but are not.
The second most important thing is that `SourceApp` and `SourceType` tie everything together. Pick a pair and reuse it across every record, or the relationships will not line up.
## 1. Get an API key
An [API key](/administration/apikeys) proves that a *process* has permission to call the API. That is exactly the right fit here, and exactly why it is the wrong fit for a user-facing app: keys represent the integration as a whole and cannot represent a specific person. If you need per-user permissions or an audit trail of who did what, use OAuth instead.
Create the key from the API keys page, give it a name that identifies the calling system, and set an expiration.
:::warning
The full key is shown **once**, at generation. Copy it into your secret store immediately. There is no way to retrieve it afterwards; a lost key has to be revoked and reissued.
:::
Send it in the `X-API-KEY` header as the raw value, with no `Bearer` prefix, no quotes, and no whitespace.
## 2. Choose how you call the API
Everything is built on [the REST API](/develop/api). The [SDKs](/develop/sdk), the [CLI](/develop/cli), and the standard connectors are all wrappers around the same endpoints, so you can mix and match freely and drop to raw HTTP whenever you need to.
Available options are the .NET, JavaScript, and Python SDKs, the CLI for scripting and CI, or plain HTTP.
Point at the **sandbox** for this exercise rather than production. You are about to create master data with a throwaway source app, and you do not want that in a live tenant.
## 3. Create the master data
In order:
- **Resources**, the people or things being planned.
- A **filter group** and its **filter values**, so planners can filter by skill, region, or whatever dimension you choose.
- A **category** and a **time marker** to color-code the result.
## 4. Create the transactional data
Then the **job**, and the **tasks** beneath it. This is where the parent-before-child rule bites hardest, since a task without its job has nothing to attach to.
## 5. Create the assignments
Finally the links: filter values onto resources and tasks, indicators onto jobs. This is the step that turns a set of disconnected records into something a planner can actually use, and it is the step most often skipped in a first integration, which is why the data looks present but unusable.
## 6. Follow the guide
The [end-to-end quickstart](/develop/guide/quickstart) reproduces this exact flow with working code. It also carries an interactive walkthrough where you paste an API key, pick an environment, and get a ready-to-run `curl` command for each step. Your key stays in the browser; nothing is executed from the page.
## Verify
Open the [planning board](/user/components/planningboard) and confirm the resources appear as rows and the tasks show up in the open tasks grid, ready to plan.
That last part is the real test. Records that exist in the database but never reach the open tasks grid usually means the task was created without the assignments from step 5, or under a different source app and source type than the resources.
## Related
- [Extend the appointment card with templates](/cookbook/appointment-templates) if you are pushing custom field values.
- [Set up a webhook connector](/cookbook/webhook-connector) to receive events back out.
---
## Plan a recurring appointment
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
[Recurring appointments](/user/features/recurring-appointments) follow the same logic as Outlook, so the mental model transfers. Two Dime.Scheduler specifics are worth knowing before you build one:
- **Every series must end.** There is no infinite recurrence. A never-ending series would flood the back office, so you cap it either by a number of occurrences or by an end date.
- **The pattern is only editable from the appointment editor.** Any other gesture that changes an occurrence, including a simple drag on the board, converts that occurrence into an *exception* rather than editing the series.
Dime.Scheduler is compliant with the iCalendar (RFC 5545) specification, so a rule like `FREQ=DAILY;COUNT=10;INTERVAL=2` means what you would expect if you are pushing rules in over the API.
## 1. Create the appointment
Start from a normal appointment, planned the usual way. Recurrence is off by default: the repeat field sits at **None** and an appointment that does not need a pattern is already finished.
## 2. Build the recurrence rule
Pick an interval and a button appears next to the field. That button states the rule as a **sentence** rather than a pattern, which is the fastest way to catch a mistake: "every second weekend day, every two months" is far easier to check than the controls that produced it.
Click it to open the builder, which takes three things:
- **Frequency**: daily, weekly, monthly, yearly.
- **Interval**: options vary by frequency. Weekly requires at least one day. Monthly offers either a day picker or presets, so both "the 1st, 15th and 30th" and "every second weekend day" are expressible. Yearly mirrors monthly but works on months.
- **End**: on a date, or after a number of occurrences.
The distinction between the two end types is not cosmetic. For a *weekly on Monday* series starting Tuesday 1 January 2019, "5 occurrences" and "until 1 February" produce different series: the count runs to 4 February, the date stops at 28 January.
## 3. Expect the start date to move
If the appointment does not comply with its own rule, Dime.Scheduler moves it. Set a *weekly on Wednesday* rule on an appointment that starts on a Monday and the original shifts to the first date that satisfies the series.
This surprises planners who expect the appointment to stay where they put it. It applies to weekly, monthly, and yearly patterns alike.
## 4. Handle the exceptions
All occurrences are created and published immediately, and each carries a repeat icon on the board.
They are created independently but stay linked, so they can move freely without breaking the series. An occurrence that has been changed outside the editor becomes an **exception occurrence**, marked with an exclamation mark in front of the repeat icon. Dragging one to a different duration is enough to create one.
When you edit an appointment that belongs to a series, Dime.Scheduler asks whether you mean the whole series or this occurrence. The rule for what happens next is worth internalizing:
- Change the **pattern, dates, or duration** and the whole series is recalculated. Exceptions are reset back onto the pattern.
- Change only **subject, resources, or indicators** and exceptions keep their status, dates, and resources. Only the master data updates.
So editing a subject across a series is safe. Editing its duration is not, if planners have been moving individual occurrences around.
## 5. Watch the series start date
Choosing "the entire series" adds one more prompt: which start date the series should use. By default it keeps the first occurrence's date.
Two overrides exist, and one of them shifts everything. Ticking **use the appointment's start date** while editing, say, the second occurrence moves the whole series one occurrence to the right. Leave it unchecked to keep the original start.
## Verify
Check the far end of the series, not the first few. Confirm the last occurrence falls where you expect, since that is where the count-versus-date difference shows up.
If planners have already adjusted individual occurrences, confirm those survived your edit, or were intentionally reset.
## Related
- [Ad-hoc service planning](/cookbook/adhoc-planning) for one-off work.
- The [appointment editor](/user/components/planningboard/appointments#the-appointment-editor) reference.
---
## Optimize routes for a technician
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Everything here depends on coordinates, so check that first. Jobs hold the address that drives every task and appointment they spawn, and **anything without coordinates is silently skipped** on the [map](/user/components/map). Not flagged, not warned about: simply absent. A route that looks like it is missing stops usually means an address that never geocoded.
See [geocoding](/administration/settings/geocoding) for what it takes to turn an address into coordinates.
Two of the steps below also carry entitlements worth confirming before you promise this to anyone. The optimizer needs **Route optimization** enabled in [preview features](/administration/setup/preview#solver--optimization) and the **Advanced map** add-on on the tenant.
## 1. Confirm the addresses
Open the map and check that the day's jobs actually appear as pins. This is the cheapest possible test and it catches the failure mode above before you spend time on sequencing.
Resources carry two addresses: a **home address** marking where they start the day, and an **actual address** fed by a tracking system or the [mobile app](/user/mobile). A toggle switches the map between them. Which one is active matters here, because the round trip anchors on it.
## 2. Create the round trip
Select a cell on the planning board and press **R**, or right-click and choose **Show daily route**.
That produces a **round trip**: the resource's home or actual address as a fixed start and end, and the appointments already planned for that resource on that date as the variable stops in between. It is the classic travelling salesman shape, and Dime.Scheduler treats it as a first-class object rather than a drawing.
This distinction matters for what you can do next. The [route sequence](/user/components/routesequence) grid runs in two modes:
- **Free composition** is the default, where you pick pins on the map and the grid composes a route from your selection. Good for inspecting or comparing, but it cannot write back.
- **Round trip** mode is committed planning, and it is the only mode that unlocks writing to the planning or calling the solver, because both need a specific resource and date as context.
Creating the trip from the board as above puts you in round-trip mode.
## 3. Read the numbers
The grid lays the stops out as rows with the figures the planner acts on: distance per leg, cumulative distance, travel time per leg, cumulative travel time, working time at each stop, and total duration.
A live summary pinned to the bottom keeps the trip-wide totals visible regardless of how many stops are in view: distance, travel time, work time, and the total of the last two. Travel and work times are rounded to the nearest five minutes.
These columns are computed in memory as the route changes rather than stored, but they behave like any other column, so you can hide, reorder, and save them into the [layout](/user/features/grid).
## 4. Optimize
Hand the trip to the solver with the sparkle button. It returns an order that minimizes travel time and distance, and both the map and the grid update in place.
The useful property: **optimizing does not write back to the planning**. The planner can optimize, add more appointments, pull in open tasks, and reoptimize as often as they like without committing anything. Nothing changes on the board until they confirm.
You can also reorder by hand: drag a row in the grid and the route on the map recalculates. For small adjustments this beats re-running the solver.
## 5. Commit it
When the day looks right, confirm the trip to push the new order to the planning board.
## Verify
Compare the summary totals before and after optimizing. If they are identical, either the route was already optimal or the solver never ran, and the two are worth telling apart.
Then check the board reflects the new sequence, since optimizing alone deliberately does not.
## Related
- [Roll out the mobile app to field workers](/cookbook/field-mobile) so technicians see the result.
- [Ad-hoc service planning](/cookbook/adhoc-planning) to populate the day first.
---
## Set up a webhook connector
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
The [webhook connector](/integrations/webhook/intro) is the answer for everything the standard connectors do not cover: a custom ERP, a CRM nobody targets, an internal microservice, a Power Automate flow, a Slack notifier.
Understand what it deliberately does *not* do, because it sets your expectations for the work on your side. Unlike the BC and NAV connectors, it does not interpret the payload, link it back to a record on the receiving side, or run a back-office workflow on success. The contract is simple: **Dime.Scheduler delivers the message, the receiver does the rest.** All the mapping and business logic is yours to build.
You need an HTTPS endpoint that can accept a POST before any of this is testable, so stand that up first even if it only logs what it receives.
## 1. Know what gets published
Every appointment change on the planning board (create, update, delete) fires a POST to your URL with the full appointment payload as JSON.
Routing works the way it does for every other connector: Dime.Scheduler routes each transaction by the appointment's **Source App** against the registered connectors. Register the webhook against a source app, and every appointment with that source app lands at your URL.
The difference is where it stops. The standard connectors route further by **Source Type** to run the right back-office workflow. The webhook connector stops at the HTTP POST.
## 2. Set up authentication
Configure how Dime.Scheduler authenticates against your endpoint in [authentication](/integrations/webhook/authentication). Do this before the endpoint is live to real traffic rather than after: an open endpoint receiving planning data is a data exposure, not a staging convenience.
## 3. Wire up the configuration
Work through [configuration](/integrations/webhook/configuration) for the endpoint URL and the connector's behavior.
## 4. Register the connector
Add it in [connectors](/administration/connectors) against the source app whose appointments you want delivered.
The **Enabled** flag is worth knowing about here. It lets you register a connector without activating it, which is exactly what you want while staging a new endpoint or silencing a source during maintenance.
## 5. Trigger an appointment and check the payload
Create or move an appointment with a matching source app and confirm the POST arrives with the payload you expect.
Check it in [transactions](/administration/transactions), which shows what Dime.Scheduler sent and how your endpoint responded. If it failed, the **Webhook** tab on the [errors page](/administration/transactions/errors) isolates webhook failures from Dynamics and Exchange ones, and [webhook transaction errors](/integrations/webhook/transaction-errors) explains the codes.
## Verify
Test all three verbs, not just create. Delete in particular is the one that gets missed, and a receiver that handles creates but silently drops deletes leaves stale records that nobody notices for weeks.
Then test the failure path deliberately: return a 500 from your endpoint on purpose and confirm the failure shows up in transactions where you expect it. Knowing what a failure looks like before you need to diagnose one is worth the five minutes.
## Related
- [Automate notifications with Zapier](/cookbook/zapier) is the lower-effort path when Zapier already talks to your target.
- [Add a deep-link action to the planning board](/cookbook/action-uri) for planner-triggered calls rather than automatic events.
---
## Build a personal workspace
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
You need a role carrying the **Personalization** user action. Without it the save buttons stay greyed out and none of this works.
It also helps to know the two words apart before you begin, because they are easy to conflate:
- A **profile** is the whole workspace: which components exist, where they sit, the color and theme, the planning board presets.
- A **layout** is the configuration of a single component inside that profile: its columns, filters, and view.
One profile contains many layouts. You save them separately, which surprises people the first time.
## 1. Start from the standard profile
Do not build on a blank canvas. Take the standard profile Dime Software ships and shape it, which saves you rebuilding things that already work.
Go to `Profile → Manage`, uncheck **Default**, save, and refresh. You are now on the standard profile with somewhere sensible to start from. See [profiles](/user/profiles).
A blank profile is flagged by an orange warning icon in the top bar. If you see it, you are on an unsaved canvas and most component buttons stay disabled until you save.
## 2. Arrange the components
Every component lives in a [dock panel](/user/profiles-dockpanels), and the panels all behave the same way regardless of what is inside them.
Two ways to add one: drag it from the `Profile` menu onto the canvas, or select a stack first and then click the component, which drops it into that stack as a new tab. The selected stack is the one with the slightly lighter shade.
Where you drop decides the result. Drop on the top, bottom, left, or right edge of an existing stack and the space splits equally into a new stack. Drop over the title bar next to existing tabs and it joins that stack as another tab. A shaded preview shows which you are about to get before you release.
Remove what you do not need with the small **×** next to each component. Be careful not to hit the **×** on the far right, which closes the whole stack rather than the one component.
Then resize: hover a stack edge until the cursor changes, and drag.
## 3. Save the profile
Go to `Profile → Save`. If this is a fresh profile you are asked for a name and whether it becomes your default.
The orange warning icon disappears the moment it saves, and a batch of component buttons light up that were disabled before. That is the signal you actually have a profile rather than a scratch canvas.
## 4. Set a default layout per component
Now configure each component and save its layout separately. Filter the planning board to the resource types you care about, pick the daily or weekly view, set the columns on your grids. Then save the layout and mark it as **default**, so it loads automatically next time.
See [layouts](/user/profiles-layouts) for what each component can store, and [create a profile](/user/guides/create-profile-layouts) for a worked example with video.
## 5. Set the profile-level presets
One catch worth knowing: some settings are not on the component at all. The date range, snap interval, and start and end times of the planning board live in the **profile**, behind the cogwheel on the right.
That is also why they apply to *every* planning board and *every* map in the profile, not just the one you had selected. Change the range there and all boards follow.
Remember to save the profile again afterwards, since those settings are stored on it rather than on the layout.
## 6. Share it
A profile can be shared with other users and with whole [user groups](/user/authentication/usergroups). This is what makes the effort worth it: build the workspace once for a team, share it, and nobody else has to repeat steps 1 to 5.
## Verify
Refresh the browser. The workspace should come back exactly as you left it: same components, same theme, same board range, same filters. If a component reverts to a generic view, its layout was never saved as the default, even though the profile was.
## Related
- [Onboard a new planner](/cookbook/onboard-planner), which ends where this recipe begins.
- [Color-code the planning board with indicators](/cookbook/indicators) to add meaning to the board you just built.
---
## Automate notifications with Zapier
import Recipe from '@site/src/components/docs/cookbook/Recipe';
## Before you start
Zapier is the low-effort path for getting events out of Dime.Scheduler and into something else. Reach for the [webhook connector](/cookbook/webhook-connector) instead when you need full control over the payload, or when the target system is one Zapier does not reach.
Know the shape of what is on offer before you design anything. The connector triggers on **appointments only**: created, updated, removed. If you need to react to a change on a task or a resource, a trigger will not fire for it.
Actions are much broader. You can create or update appointments, tasks, resources, jobs, categories, time markers, pins, containers, assignments, notifications, action URLs, captions, filter groups, and filter values, as well as set attributes and delete items.
So the asymmetry to design around is: narrow triggers, wide actions.
## 1. Get the prerequisites in place
You need a Zapier account, a running Dime.Scheduler instance, and an [API key](/administration/apikeys). See [prerequisites](/integrations/zapier/prerequisites).
Create the key with a name that says what it is for, so that revoking it later is an obvious decision rather than a risky one. Remember the key is displayed only once at generation.
Build against the **sandbox** first. A misconfigured Zap that loops or duplicates is much cheaper to discover there.
## 2. Create the Zap
[Installing](/integrations/zapier/install) walks through finding the connector, creating the Zap, and authenticating.
At authentication you choose the environment, and it belongs to the **connection** rather than to the Zap, so every Zap built on that connection inherits it. Check you picked the one you meant: pointing a test Zap at production is the mistake that turns a five-minute experiment into a cleanup job.
When you go live, make a second connection for production instead of editing the sandbox one, so the configuration you tested stays intact.
## 3. Pick the trigger
Choose from [triggers](/integrations/zapier/triggers): new, updated, or removed appointment. Each delivers the full appointment payload, including job details, resource information, timing, and custom fields, so you rarely need a follow-up lookup to enrich it.
**Updated appointment** is the one to think hardest about. Any change fires it, including ones you do not care about, so a Zap that notifies on every update gets muted by its recipients within a week. Add a Zapier filter immediately after the trigger to narrow to the changes that matter.
## 4. Configure the action
Point the action at wherever the event should land: a Slack channel, an email, a CRM record.
If the action writes *back* into Dime.Scheduler, get **source app** and **source type** right. They are what identify where a record came from and how it routes, and they are the usual cause of records that are created successfully but never appear where expected. See [actions](/integrations/zapier/actions).
## 5. Test before enabling
Run the Zap against sample data and confirm both ends: the trigger fires when you expect, and the action produces what you want at the far end.
Then enable it.
## Verify
Do the round trip with a real appointment rather than Zapier's sample payload. Sample data has every field populated; real data often does not, and a Zap that depends on an empty optional field fails only in production.
If the Zap writes back into Dime.Scheduler, check [transactions](/administration/transactions) to confirm what actually arrived.
## Related
- [Set up a webhook connector](/cookbook/webhook-connector) for full control over the payload.
- [Zapier troubleshooting](/integrations/zapier/troubleshooting) when a Zap does not fire.
---
## Action
import Content from './global/sourceapp.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Action URIs can launch another application while passing parameters as the selected resource and date or time. For instructions how to construct an URI, see [configuring actions](/administration/settings/actions).
## Upsert
Inserts or updates an action uri.
Only URI's with matching `SourceApp` and `SourceType` fields are shown. `UrlDesc` is the description shown to the user.
| Name | Data Type | Default | Required | Description |
| ----------- | -------------- | ------- | -------- | ------------------------------ |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| UrlType | int | | ✔️ | See [API reference](#url-type) |
| Url | nvarchar(1000) | | ✔️ | |
| UrlDesc | nvarchar(255) | | | |
| DefaultUrl | bit | 0 | | |
| RequestType | int | | ✔️ | See [API reference](#request-type) |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertActionUrl`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertActionUrl",
"ParameterNames": ["UrlType", "Url", "UrlDesc"],
"ParameterValues": ["2", "https://actionableURI.domain", "Lock appointment"]
}
]
```
See `POST /actionUri`, `PUT /actionUri` in the [REST API reference](/develop/api-reference#tag/action-uri).
## API reference
### Source
### URL Type
Values for `UrlType` are:
- 0: Url is shown when user right-clicks an empty slot in the planning board
- 1: Url is shown when user right-clicks an appointment;
- 2: Task
- 3: Map
### Request Type
Values for `RequestType` are:
- 1: Regular, HTTP GET request using placeholders that are resolved in runtime.
- 2: HTTP POST that publishes the entire appointment graph to the endpoint.
---
## Appointment
import Content from './global/sourceapp.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
The appointment entity is what Dime.Scheduler is all about. An appointment is the main deliverable of the software and represents a block in the planning board. The appointment contains the information of the appointment such as the start and end date, and it holds references to the resources that have been assigned to it.
The following entities are exposed through the API.
```mermaid
erDiagram
Appointment ||--}| Assignment : ""
Appointment }o--o| Category : ""
Appointment }o--o| TimeMarker : ""
Appointment ||--|| AppointmentContainer : ""
AppointmentContainer }o--|| Container : ""
Assignment }o--|| Resource : ""
```
## Appointment
### Upsert appointment
Inserts or updates an appointment record. The behavior of this procedure depends on the data you pass to it:
- The appointment id is required if you want to update an existing appointment.
- Otherwise, it inserts an appointment resource record (linking a resource to the appointment) if the record does not exist.
| Name | Data Type | Default | Required | Description |
| --------------------- | ------------- | ------- | -------- | ---------------------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ | |
| TaskNo | nvarchar(50) | | ✔️ | |
| AppointmentNo | nvarchar(max) | 0 | | |
| AppointmentId | bigint | 0 | | |
| Subject | nvarchar(max) | | | |
| Body | nvarchar(max) | | | |
| Start | datetime | | | Use ISO 8601 format: `yyyy-mm-ddThh:mm` |
| End | datetime | | | Use ISO 8601 format: `yyyy-mm-ddThh:mm` |
| IsAllDayEvent | bit | 0 | | This field is deprecated. |
| Category | nvarchar(100) | | | [See API reference](#category) |
| TimeMarker | nvarchar(100) | | | [See API reference](#time-marker) |
| Tags | nvarchar(1000)| | | [See API reference](#tags) |
| Importance | int | 0 | | [See API reference](#set-importance) |
| Locked | bit | 0 | | |
| ResourceNo | nvarchar(50) | | | |
| AppointmentGuid | nvarchar(50) | | | |
| ReplaceResource | bit | 0 | | |
| SentFromBackOffice | bit | 1 | | |
| BackofficeID | nvarchar(100) | | | |
| BackofficeParentID | nvarchar(100) | | | |
| PlanningUOM | nvarchar(20) | | | |
| PlanningUOMConversion | decimal(18,6) | 0 | | |
| PlanningQty | decimal(18,6) | 0 | | |
| UseFixPlanningQty | bit | 0 | | |
| RoundToUOM | bit | 0 | | |
| IsManualAppointment | bit | 0 | | [See API reference](#manual-appointment) |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertAppointment`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertAppointment",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "Subject", "Start", "End"],
"ParameterValues": ["BCCOMPANY1", "JOB", "JOB001", "TASK001", "Important meeting", "2024-01-01T15:00","2024-01-02T15:00"]
}
]
```
- See `POST /appointment`, `PUT /appointment` in the [REST API reference](/develop/api-reference#tag/appointment).
- Example body:
```json
{
"SourceApp": "BCCOMPANY1",
"SourceType": "JOB",
"JobNo": "JOB001",
"TaskNo": "TASK001",
"AppointmentNo": "xj0rJJb3",
"Start": "2024-01-01T15:00",
"End": "2024-01-02T15:00",
"Subject": "Important meeting"
}
```
### Delete appointment
The behavior of this procedure depends on the data you pass to it:
- If a resource number is provided, then the appointment resource record is deleted. The appointment record is also deleted if no more appointment resource records exist for the appointment.
- If the resource number is not provided, then the appointment record and all linked appointment resource records are deleted.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| AppointmentNo | nvarchar(max) | 0 | | |
| AppointmentId | bigint | | |
| AppointmentGuid | nvarchar(50) | | |
| ResourceNo | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteAppointment`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteAppointment",
"ParameterNames": ["SourceApp", "SourceType", "AppointmentNo"],
"ParameterValues": ["BCCOMPANY1", "JOB", "x0rnJJ2b"]
}
]
```
See `DELETE /appointment` in the [REST API reference](/develop/api-reference#tag/appointment).
## Upsert appointment URI
Inserts or updates an appointment URI record.
An appointment URI contains links to documents, web pages and the back-office system.
You need to pass an existing appointment id or appointment GUID.
| Name | Data Type | Default | Required | Description |
| ---------------- | -------------- | ------- | -------- | ---------------------------- |
| pSourceApp | nvarchar(30) | | | [See API reference](#source) |
| pSourceType | nvarchar(10) | | | [See API reference](#source) |
| pAppointmentId | bigint | 0 | | |
| pAppointmentGuid | nvarchar(50) | | | |
| pUrl | nvarchar(1000) | | ✔️ | |
| pUrlDesc | nvarchar(255) | | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertAppointmentUrl`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertAppointmentUrl",
"ParameterNames": ["pUrl", "pAppointmentId"],
"ParameterValues": ["https://docs.dimescheduler.com", "15123"]
}
]
```
See `POST /appointmentUri`, `PUT /appointmentUri` in the [REST API reference](/develop/api-reference#tag/appointment-uri).
## Upsert appointment container
Adds or updates the allocation of an appointment to a container.
| Name | Data Type | Default | Required | Description |
| ----------- | ------------- | ------- | -------- | --------------------------------- |
| Container | nvarchar(100) | | ✔️ | Name of the container |
| Appointment | nvarchar(100) | | ✔️ | Appointment GUID or AppointmentNo |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertAppointmentContainer`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertAppointmentContainer",
"ParameterNames": ["Container", "Appointment"],
"ParameterValues": ["CONTAINER001", "200"]
}
]
```
- See `POST /appointmentContainer`, `PUT /appointmentContainer` in the [REST API reference](/develop/api-reference#tag/appointment-container).
- Example body:
```json
[
{
"container": "Container 1",
"appointment": "ad37cfba-f502-4592-883d-4b10433722fe"
},
{
"container": "Container 1",
"appointment": "ad37cfba-f502-4592-883d-4b10433722ff"
}
]
```
- Note: The `appointment` field accepts either an appointment GUID or AppointmentNo.
## Add assignment
Add a resource to an existing appointment, thereby creating a linked appointment.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | ✔️ |
| AppointmentNo | nvarchar(max) | 0 | | |
| ResourceNo | nvarchar(50) | | ✔️ |
| AppointmentGuid | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_addAppointmentResource`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_addAppointmentResource",
"ParameterNames": ["AppointmentId", "ResourceNo"],
"ParameterValues": ["100", "2"]
}
]
```
See `POST /assignment`, `PUT /assignment` in the [REST API reference](/develop/api-reference#tag/assignment).
## Properties
### Update appointment content
Updates the body and subject of the selected appointment.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | |
| AppointmentNo | nvarchar(max) | 0 | | |
| AppointmentGuid | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
| Subject | nvarchar(max) | | |
| Body | nvarchar(max) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateAppointmentContent`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_updateAppointmentContent",
"ParameterNames": ["AppointmentId", "Subject"],
"ParameterValues": ["1", "Hello world!"]
}
]
```
See `POST /appointmentContent` in the [REST API reference](/develop/api-reference#tag/appointment-attribute).
### Update appointment planning quantity
Updates the planning quantity of an appointment.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | |
| AppointmentNo | nvarchar(max) | 0 | | |
| PlanningQty | decimal(18,6) | | |
| AppointmentGuid | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateAppointmentPlanningQty`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_updateAppointmentPlanningQty",
"ParameterNames": ["AppointmentId", "PlanningQty"],
"ParameterValues": ["1", "24"]
}
]
```
See `POST /appointmentPlanningQuantity` in the [REST API reference](/develop/api-reference#tag/appointment-attribute).
## Indicators
### Set time marker
Update the time marker assigned to the appointment. Can be used to update status or progress of the appointment.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | |
| AppointmentNo | nvarchar(max) | 0 | | |
| TimeMarker | nvarchar(100) | | |
| AppointmentGuid | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateAppointmentTimeMarker`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_updateAppointmentTimeMarker",
"ParameterNames": ["AppointmentId", "TimeMarker"],
"ParameterValues": ["1", "TIMEMARKER002"]
}
]
```
See `POST /appointmentTimeMarker` in the [REST API reference](/develop/api-reference#tag/appointment-time-marker).
### Set category
Updates the category assigned to the appointment. Can be used to update status or progress of the appointment.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | |
| AppointmentNo | nvarchar(max) | 0 | | |
| Category | nvarchar(100) | | |
| AppointmentGuid | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateAppointmentCategory`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_updateAppointmentCategory",
"ParameterNames": ["AppointmentId", "Category"],
"ParameterValues": ["1", "CATEGORY002"]
}
]
```
See `POST /appointmentCategory` in the [REST API reference](/develop/api-reference#tag/appointment-attribute).
## Field values
Sets the field value of an appointment
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | | |
| AppointmentNo | nvarchar(max) | 0 | | |
| AppointmentGuid | nvarchar(50) | | | |
| FieldCode | nvarchar(100) | 0 | | |
| FieldValue | nvarchar(MAX) | 1 | | |
| SentFromBackOffice | nvarchar(MAX) | 1 | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateAppointmentFieldValues`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_updateAppointmentFieldValues",
"ParameterNames": ["AppointmentId","FieldCode","FieldValue"],
"ParameterValues": ["1","FINISHED","true"]
}
]
```
See `POST /appointmentFieldValue`, `PUT /appointmentFieldValue` in the [REST API reference](/develop/api-reference#tag/appointment-field-value).
### Set importance
Sets the importance of an appointment.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | -------------------------------- |
| SourceApp | nvarchar(30) | | | [See API reference](#source) |
| SourceType | nvarchar(10) | | | [See API reference](#source) |
| AppointmentId | bigint | | |
| AppointmentNo | nvarchar(max) | 0 | | |
| Importance | int | 0 | | [See API reference](#set-importance) |
| AppointmentGuid | nvarchar(50) | | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateAppointmentImportance`.
- Example body:
```json
{
"AppointmentNo": "ZVELNXY4",
"FieldCode": "FINISHED",
"FieldValue": "true"
}
```
## API reference
### Source
### Category
Controls the visual indicator of the planned task in the planning board. Read more about [categories](/user/components/planningboard/appointments#category).
### Time Marker
Controls the visual indicator of the planned task in the planning board. Read more about [time markers](/user/components/planningboard/appointments#time-marker).
### Tags
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases.
:::
A semicolon-delimited list of [tag](/develop/api/indicator#tag) codes, for example `URGENT;NIGHTSHIFT`. An appointment can carry as many tags as fit within the 1000 characters, and each one renders as an icon badge on the appointment bar.
Omitting the parameter and sending an empty value mean different things:
- **Omitted (`NULL`)** means "not provided". The stored tags are kept, so a connector that knows nothing about tags never wipes the tags a planner set by hand.
- **Empty string** means "no tags", and clears them.
An appointment created from a task starts out with that task's tags, after which the two are independent. Read more about [tags](/user/features/tags).
### Manual appointment
Set this flag to true to create a manual appointment. Manual appointments don't require a job and task.
---
## Caption
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Upsert
Inserts or updates a caption for a `DATABASEFIELD` in the specified language. These captions are used in the open tasks grid, the task details pane and in the field templates.
| Name | Data Type | Default | Required |
| ----------- | ------------- | ------- | -------- |
| Context | int | 6 | |
| SourceTable | nvarchar(255) | | |
| FieldName | nvarchar(255) | | |
| Language | nvarchar(10) | | |
| Caption | nvarchar(100) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertCaption`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertCaption",
"ParameterNames": ["SourceTable", "FieldName", "Language", "Caption"],
"ParameterValues": ["Job", "FreeBit1", "en", "Pineapple on pizza?"]
}
]
```
- See `POST /caption`, `PUT /caption` in the [REST API reference](/develop/api-reference#tag/caption).
- Example body:
```json
{
SourceTable": "Job",
"FieldName":"FreeBit1",
"Language":"en",
"Caption":"Pineapple on pizza?"
}
```
## API reference
### Context
| Value | Name |
| ----- | --------------------- |
| 1 | RESOURCE |
| 2 | APPOINTMENT |
| 3 | APPOINTMENTFIELDVALUE |
| 4 | EMAIL |
| 5 | EMAILRECIPIENT |
| 6 | DATABASEFIELD |
| 7 | TASKDETAILS |
| 8 | EMAILIMPORTANCE |
| 9 | RECIPIENTTYPE |
| 10 | CONTEXT |
| 11 | TARGETPANE |
| 12 | TARGETPANEGROUP |
| 13 | TASK |
| 14 | JOB |
---
## Connector
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Upsert
Inserts or updates a connector.
| Name | Data Type | Default | Required |
| -------------------------------- | -------------- | ------- | -------- |
| Name | nvarchar(MAX) | | |
| Description | nvarchar(MAX) | | |
| SourceApp | nvarchar(30) | | ✔️ |
| SendAppointmentToWebService | bit | False | |
| WebServiceAddress | bit | True | ✔️ |
| [Type](#back-office-system) | INT | 0 | |
| [AuthType](#authentication-type) | INT | 0 | |
| [ApiType](#api-type) | INT | 0 | |
| Login | NVARCHAR (50) | | |
| Password | NVARCHAR (MAX) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertConnector`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertConnector",
"ParameterNames": ["SourceApp","SendAppointmentToWebService", "WebServiceAddress", "Type", "Login", "Password"],
"ParameterValues": ["MYSOURCEAPP","true", "https://myendpoint.domain","1","test_user","test_password"]
}
]
```
- See `POST /connector` in the [REST API reference](/develop/api-reference#tag/connector).
- Example body:
```json
{
"SourceApp": "MYSOURCEAPP",
"Enabled": true,
"Uri": "https://myendpoint.domain",
"Login": "test_user",
"Password": "test_password",
"Type": 1
}
```
## Delete
Removes a connector.
| Name | Data Type | Default | Required |
| --------- | ------------- | ------- | -------- |
| SourceApp | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteConnector`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteConnector",
"ParameterNames": ["SourceApp"],
"ParameterValues": ["MYSOURCEAPP"]
}
]
```
This endpoint is not yet available in the REST API.
## API reference
### Back-Office System
The back office type enum supports the following values:
| Enum | Represents |
| ---- | ---------- |
| 0 | None |
| 1 | BC |
| 2 | NAV |
| 3 | Webhook |
:::warning
Use the enum integer value, not the text value.
:::
### Authentication Type
The authentication type enum supports the following values:
| Enum | Represents |
| ---- | --------------------------------------- |
| 0 | None |
| 1 | Basic |
| 2 | NTLM |
| 3 | Windows |
| 4 | MS Entra ID |
| 5 | OAuth2: Client Credentials (Body Auth) |
| 6 | OAuth2: Client Credentials (Basic Auth) |
:::warning
Use the enum integer value, not the text value.
:::
### API type
| Enum | Represents |
| ---- | ----------- |
| 0 | SOAP (XML) |
| 1 | REST (JSON) |
:::warning
Use the enum integer value, not the text value.
:::
---
## Container
A [container](/user/features/containers) is a meta entity that groups records together and allows the planner to schedule those records as if it was one entity. It's a flexible tool to enforce business rules and they can be used in a variety of ways, such as creating trips, enforce work breakdown structures, manufacturing sequences, and more.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Upsert
Inserts or updates a container.
| Name | Data Type | Default | Required |
| ------------ | ------------- | ------- | -------- |
| Name | nvarchar(100) | | ✔️ |
| HandleDate | datetime | | |
| HandleLocked | bit | True | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertContainer`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertContainer",
"ParameterNames": ["Name","HandleDate", "HandleLocked"],
"ParameterValues": ["Container 1","2024-01-01", "false"]
}
]
```
- See `POST /container`, `PUT /container` in the [REST API reference](/develop/api-reference#tag/container).
- Example body:
```json
{
"Name": "Container 1",
"HandleDate": "2024-01-01",
"HandleLocked": false
}
```
## Delete
Removes a container and its members
| Name | Data Type | Default | Required |
| ---- | ------------- | ------- | -------- |
| Name | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteContainer`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteContainer",
"ParameterNames": ["Name"],
"ParameterValues": ["Container 1"]
}
]
```
- See `DELETE /container` in the [REST API reference](/develop/api-reference#tag/container).
- Example body:
```json
{
"Name": "Container 1",
"HandleDate": "2024-01-01",
"HandleLocked": false
}
```
## Read more
- [Appointment containers](/develop/api/appointment#upsert-appointment-container)
- [Task containers](/develop/api/task#task-container)
---
## Filter
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Filter group
A filter group is the base definition for the powerful filtering mechanism of Dime.Scheduler. Filter groups are used to filter on resources and tasks, but also to set up the data-driven part of the security system.
The following entities are exposed through the API.
```mermaid
erDiagram
FilterGroup }o--|| FilterValue : "has"
```
## Upsert filter group
Inserts or updates a filter group record.
| Name | Data Type | Default | Required |
| ---------- | ------------ | ------- | -------- |
| Id | int | 0 | |
| GroupName | nvarchar(50) | | ✔️ |
| ColumnNo | int | 0 | |
| DataFilter | bit | 0 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertFilterGroup`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertFilterGroup",
"ParameterNames": ["GroupName"],
"ParameterValues": ["Skill"]
}
]
```
See `POST /filterGroup`, `PUT /filterGroup` in the [REST API reference](/develop/api-reference#tag/filter-group).
### Rename filter group
Renames the filter group.
| Name | Data Type | Default | Required |
| ------------ | ------------ | ------- | -------- |
| GroupName | nvarchar(50) | | ✔️ |
| NewGroupName | nvarchar(50) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_renameFilterGroup`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_renameFilterGroup",
"ParameterNames": ["GroupName", "NewGroupName"],
"ParameterValues": ["Skill", "Useless skill"]
}
]
```
This endpoint is not available yet.
### Delete filter group
Removes the filter group.
| Name | Data Type | Default | Required |
| --------- | ------------ | ------- | -------- |
| GroupName | nvarchar(50) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteFilterGroup`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteFilterGroup",
"ParameterNames": ["GroupName"],
"ParameterValues": ["Skill"]
}
]
```
See `DELETE /filterGroup` in the [REST API reference](/develop/api-reference#tag/filter-group).
## Filter value
A filter value is the child of filter group, and a filter group is required before any filter value can be added.
### Upsert filter value
Inserts or updates a filter value record.
| Name | Data Type | Default | Required |
| --------------- | ------------- | ------- | -------- |
| FilterGroupName | nvarchar(50) | | ✔️ |
| FilterValue | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertFilterValue",
"ParameterNames": ["FilterGroupName", "FilterValue"],
"ParameterValues": ["Language", "German"]
}
]
```
See `POST /filterValue`, `PUT /filterValue` in the [REST API reference](/develop/api-reference#tag/filter-value).
### Rename filter value
Renames a filter value record. A filter value is the child of filter group, and a filter group is required before any filter value can be added.
| Name | Data Type | Default | Required |
| --------------- | ------------- | ------- | -------- |
| FilterGroupName | nvarchar(50) | | ✔️ |
| FilterValue | nvarchar(100) | | ✔️ |
| NewFilterValue | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_renameFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_renameFilterValue",
"ParameterNames": ["FilterGroupName", "FilterValue", "NewFilterValue"],
"ParameterValues": ["Language", "German", "Swiss German"]
}
]
```
This endpoint is not available yet.
### Delete filter value
Removes the filter value.
| Name | Data Type | Default | Required |
| --------------- | ------------- | ------- | -------- |
| FilterGroupName | nvarchar(50) | | ✔️ |
| FilterValue | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteFilterValue",
"ParameterNames": ["FilterGroupName", "FilterValue"],
"ParameterValues": ["Skill", "Small talk"]
}
]
```
See `DELETE /filterValue` in the [REST API reference](/develop/api-reference#tag/filter-value).
---
## Free fields
Free fields are unmapped fields that you can link custom fields to.
The caption can be changed through the [captions localization system](/administration/settings/localization#captions). Either use the GUI and look for the corresponding field of the corresponding source table for the 'DATABASEFIELD' context, or use the [API](/develop/api/action).
---
## Source App and Source Type
Dime.Scheduler is a centralized planning application. One of its key tenets is the ability to accommodate data from different origins. For instance, it is perfectly acceptable to plan work orders from Business Central, CRM, imported Excel sheets, etc., and Dime.Scheduler won't break a sweat because of the existence of the `Source App` and `Source Type` fields in the data model.
#### Source App
When the planner creates an appointment for a resource or task, Dime.Scheduler knows which system to notify. Through the [connector setup](/administration/connectors), Dime.Scheduler will find a matching `SourceApp` and subsequently send the appointment to the corresponding URI.
The source app field is a unique identifier that identifies a back-office instance such as a tenant of Business Central, a test environment, an instance of CRM cloud, or any other application you wish to use to feed data into Dime.Scheduler.
#### Source Type
The `SourceType` is used inside the back-office system itself. A source type represents a distinct entity such as a service order, production order, assembly order, etc.
The standard connectors that Dime Software provides make it possible to plan different types of resources and tasks. When Dime.Scheduler posts an appointment to a standard connector, it ends up in a staging table, which is then processed by the connector to ensure the appointment produced in Dime.Scheduler is traced back to the original record using the externally identifiable fields (`JobNo`, `TaskNo`, `ResourceNo`) that identify the record.
---
## Indicator
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import ReactPlayer from 'react-player'
The video below showcases the functionality of some of the indicators:
## Category
A category is the property or attribute of an appointment that determines the color in which an appointment is displayed. The category is used to provide an immediate visual insight in the type or status of appointments.
### Upsert
Inserts or updates a category record.
| Name | Data Type | Default | Required |
| ---------------- | ------------- | -------------- | -------- |
| CategoryName | nvarchar(100) | | ✔️ |
| DisplayName | nvarchar(100) | `CategoryName` | |
| CategoryHexColor | nvarchar(50) | | |
| ColorR | int | | |
| ColorG | int | | |
| ColorB | int | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertCategory`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertCategory",
"ParameterNames": ["CategoryName", "DisplayName", "CategoryHexColor"],
"ParameterValues": ["INPROGRESS", "In Progress", "#6e64b5"]
}
]
```
See `POST /category`, `PUT /category` in the [REST API reference](/develop/api-reference#tag/category).
### Rename category
Renames the category.
| Name | Data Type | Default | Required |
| --------------- | ------------- | ------- | -------- |
| CategoryName | nvarchar(100) | | ✔️ |
| NewCategoryName | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_renameCategory`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_renameCategory",
"ParameterNames": ["CategoryName", "NewCategoryName"],
"ParameterValues": ["INPROCESSTYPO", "INPROCESS"]
}
]
```
This endpoint is not available yet.
### Delete category
Removes this indicator from the database.
| Name | Data Type | Default | Required |
| ------------ | ------------- | ------- | -------- |
| CategoryName | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteCategory`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteCategory",
"ParameterNames": ["CategoryName"],
"ParameterValues": ["INPROCESS"]
}
]
```
See `DELETE /category` in the [REST API reference](/develop/api-reference#tag/category).
## Time Marker
A time marker is the property or attribute of an appointment that determines the colored bar displayed below the appointment in the planning board. The time marker is used to provide an immediate visual insight in the type or status of appointments.
### Upsert time marker
Inserts or updates a time marker record.
| Name | Data Type | Default | Required |
| ---------- | ------------- | ------- | -------- |
| TimeMarker | nvarchar(100) | | ✔️ |
| HexColor | nvarchar(50) | | |
| ColorR | int | | |
| ColorG | int | | |
| ColorB | int | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertTimeMarker`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertTimeMarker",
"ParameterNames": ["TimeMarker", "HexColor"],
"ParameterValues": ["In Progress", "#6e64b5"]
}
]
```
See `POST /timeMarker`, `PUT /timeMarker` in the [REST API reference](/develop/api-reference#tag/time-marker).
### Rename time marker
Renames the time marker.
| Name | Data Type | Default | Required |
| ------------- | ------------- | ------- | -------- |
| TimeMarker | nvarchar(100) | | ✔️ |
| NewTimeMarker | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_renameTimeMarker`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_renameTimeMarker",
"ParameterNames": ["TimeMarker", "NewTimeMarker"],
"ParameterValues": ["INPROCESSTYPO", "INPROCESS"]
}
]
```
This endpoint is not available yet.
### Delete time marker
Removes this indicator from the database.
| Name | Data Type | Default | Required |
| ---------- | ------------- | ------- | -------- |
| TimeMarker | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteTimeMarker`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteTimeMarker",
"ParameterNames": ["TimeMarker"],
"ParameterValues": ["INPROCESS"]
}
]
```
See `DELETE /timeMarker` in the [REST API reference](/develop/api-reference#tag/time-marker).
## Pin
A pin is the property or attribute of an appointment that determines the colored pin on the map.
### Upsert pin
Inserts or updates a pin record.
| Name | Data Type | Default | Required |
| -------- | ------------- | ------- | -------- |
| Name | nvarchar(100) | | ✔️ |
| HexColor | nvarchar(50) | | |
| ColorR | int | | |
| ColorG | int | | |
| ColorB | int | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertPin`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertPin",
"ParameterNames": ["Name", "HexColor"],
"ParameterValues": ["In Progress", "#6e64b5"]
}
]
```
See `POST /pin`, `PUT /pin` in the [REST API reference](/develop/api-reference#tag/pin).
### Rename pin
Renames the pin.
| Name | Data Type | Default | Required |
| ------- | ------------- | ------- | -------- |
| Name | nvarchar(100) | | ✔️ |
| NewName | nvarchar(100) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_renamePin`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_renamePin",
"ParameterNames": ["Name", "NewName"],
"ParameterValues": ["INPROCESSTYPO", "INPROCESS"]
}
]
```
This endpoint is not available yet.
### Delete pin
Removes the indicator from the database.
| Name | Data Type | Default | Required |
| ---- | ------------ | ------- | -------- |
| Name | nvarchar(30) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deletePin`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deletePin",
"ParameterNames": ["Name"],
"ParameterValues": ["INPROCESS"]
}
]
```
See `DELETE /pin` in the [REST API reference](/develop/api-reference#tag/pin).
## Tag
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases. Not recommended for production-critical workflows yet.
:::
A tag is a reusable, admin-defined label that tasks and appointments reference by code. Unlike the other indicators, a record can carry several tags at once: the `Tags` parameter on [task](/develop/api/task#tags) and [appointment](/develop/api/appointment#tags) is a semicolon-delimited list of tag codes, for example `URGENT;NIGHTSHIFT`. Read more about [tags](/user/features/tags).
### Upsert tag
Inserts or updates a tag, identified by its code. The tag's code is its stable identifier and cannot be changed after creation. Parameters that are left empty on an update keep their stored value, and `Name` falls back to the code when the tag is created without one.
| Name | Data Type | Default | Required |
| -------- | ------------- | ------- | -------- |
| Code | nvarchar(50) | | ✔️ |
| Name | nvarchar(100) | `Code` | |
| IconSet | nvarchar(50) | | |
| IconName | nvarchar(100) | | |
| HexColor | nvarchar(7) | | |
`IconSet` and `IconName` identify the icon rendered on the appointment bar. The only supported icon set today is `fontawesome`, and `IconName` is the icon's name within that set, for example `bolt` or `moon`.
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertTag`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertTag",
"ParameterNames": ["Code", "Name", "IconSet", "IconName", "HexColor"],
"ParameterValues": ["URGENT", "Urgent", "fontawesome", "bolt", "#e03131"]
}
]
```
See `GET /tag`, `POST /tag`, `PUT /tag` in the [REST API reference](/develop/api-reference#tag/tag).
### Delete tag
Removes the tag from the database. Tasks and appointments that still reference the code are left untouched; the tag simply stops rendering on them.
| Name | Data Type | Default | Required |
| ---- | ------------ | ------- | -------- |
| Code | nvarchar(50) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteTag`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteTag",
"ParameterNames": ["Code"],
"ParameterValues": ["URGENT"]
}
]
```
See `DELETE /tag` in the [REST API reference](/develop/api-reference#tag/tag).
---
## Job
import Content from './global/sourceapp.md';
import FreeFields from './global/freefield.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
A **job** in Dime.Scheduler is the encompassing entity that groups tasks. This can be a service order header with multiple lines, a project with several tasks, a cargo transport with multiple containers, etc. A job record is mandatory for a task. In other words, you cannot create a task if the corresponding job record does not exist.
The following entities are exposed through the API.
```mermaid
erDiagram
Job only one--zero or more Task : ""
Job zero or more--zero or one Category : ""
Job zero or more--zero or one TimeMarker : ""
Job zero or more--zero or one Pin : ""
```
## Upsert
Inserts or updates a job record.
| Name | Data Type | Default | Required | Description |
| ---------------------- | -------------- | ------- | -------- | ---------------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ | |
| ShortDescription | nvarchar(50) | | ✔️ | |
| Description | nvarchar(MAX) | | | |
| Type | nvarchar(50) | | | |
| Name | nvarchar(255) | | | |
| Category | nvarchar(100) | | | [See API reference](#category) |
| TimeMarker | nvarchar(100) | | | [See API reference](#time-marker) |
| Pin | nvarchar(100) | | | [See API reference](#pin) |
| CustomerNo | nvarchar(20) | | | |
| CustomerName | nvarchar(50) | | | |
| CustomerAddress | nvarchar(MAX) | | | |
| CustomerAddressGeoLong | nvarchar(50) | | | |
| CustomerAddressGeoLat | nvarchar(50) | | | |
| CustomerPhone | nvarchar(50) | | | |
| CustomerEmail | nvarchar(50) | | | |
| ContactNo | nvarchar(20) | | | |
| ContactName | nvarchar(50) | | | |
| ContactAddress | nvarchar(MAX) | | | |
| ContactAddressGeoLong | nvarchar(50) | | | |
| ContactAddressGeoLat | nvarchar(50) | | | |
| ContactPhone | nvarchar(50) | | | |
| ContactEmail | nvarchar(50) | | | |
| SiteNo | nvarchar(20) | | | |
| SiteName | nvarchar(50) | | | |
| SiteAddress | nvarchar(MAX) | | | |
| SiteAddressGeoLong | nvarchar(50) | | | Used to diplay the job on the map. |
| SiteAddressGeoLat | nvarchar(50) | | | Used to diplay the job on the map. |
| SitePhone | nvarchar(50) | | | |
| SiteEmail | nvarchar(50) | | | |
| SiteRegion | nvarchar(10) | | | Used in the geocoding process. |
| SiteStreet | nvarchar(255) | | | Used in the geocoding process. |
| SiteStreetNo | nvarchar(20) | | | Used in the geocoding process. |
| SitePostcode | nvarchar(20) | | | Used in the geocoding process. |
| SiteCity | nvarchar(50) | | | Used in the geocoding process. |
| SiteCounty | nvarchar(50) | | | Used in the geocoding process. |
| SiteState | nvarchar(50) | | | Used in the geocoding process. |
| SiteCountry | nvarchar(50) | | | Used in the geocoding process. |
| SiteFromNo | nvarchar(20) | | | |
| SiteFromName | nvarchar(50) | | | |
| SiteFromAddress | nvarchar(MAX) | | | |
| SiteFromAddressGeoLong | nvarchar(50) | | | |
| SiteFromAddressGeoLat | nvarchar(50) | | | |
| SiteFromPhone | nvarchar(50) | | | |
| SiteFromEmail | nvarchar(50) | | | |
| SiteFromRegion | nvarchar(10) | | | |
| SiteFromStreet | nvarchar(255) | | | |
| SiteFromStreetNo | nvarchar(20) | | | |
| SiteFromPostcode | nvarchar(20) | | | |
| SiteFromCity | nvarchar(50) | | | |
| SiteFromCounty | nvarchar(50) | | | |
| SiteFromState | nvarchar(50) | | | |
| SiteFromCountry | nvarchar(50) | | | |
| BillNo | nvarchar(20) | | | |
| BillName | nvarchar(50) | | | |
| BillAddress | nvarchar(MAX) | | | |
| BillAddressGeoLong | nvarchar(50) | | | |
| BillAddressGeoLat | nvarchar(50) | | | |
| BillPhone | nvarchar(50) | | | |
| BillEmail | nvarchar(50) | | | |
| BillRegion | nvarchar(10) | | | |
| Importance | int | 0 | | [See API reference](#importance) |
| CreationDateTime | datetime | | | |
| CustomerReference | nvarchar(50) | | | |
| Language | nvarchar(10) | | | |
| Responsible | nvarchar(50) | | | |
| Creator | nvarchar(50) | | | |
| FreeText1 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText2 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText3 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText4 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText5 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText6 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText7 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText8 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText9 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText10 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText11 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText12 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText13 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText14 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText15 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText16 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText17 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText18 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText19 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText20 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeDecimal1 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal2 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal3 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal4 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal5 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDate1 | datetime | | | See [API reference](#free-fields) |
| FreeDate2 | datetime | | | See [API reference](#free-fields) |
| FreeDate3 | datetime | | | See [API reference](#free-fields) |
| FreeDate4 | datetime | | | See [API reference](#free-fields) |
| FreeDate5 | datetime | | | See [API reference](#free-fields) |
| FreeBit1 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit2 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit3 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit4 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit5 | bit | 0 | | See [API reference](#free-fields) |
| EnableManualSelection | bit | 0 | | |
| AvailableInGantt | bit | 0 | | |
| StartDate | datetime | | | |
| EndDate | datetime | | | |
| AllowDependencies | bit | 1 | | |
| Note | nvarchar(max) | | | |
| OverRuleGanttPlanning | bit | 0 | | |
| ReadOnly | bit | 0 | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertJob`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertJob",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "ShortDescription"],
"ParameterValues": ["BC", "JOB", "JOB001", "Important job"]
}
]
```
See `POST /job`, `PUT /job` in the [REST API reference](/develop/api-reference#tag/job).
## Delete
Deletes a job record. The behavior of this procedure depends on the `CheckAppointments` flag:
- When `CheckAppointments` is true: the job is not deleted when there are appointments for any task attached to the job.
- When `CheckAppointments` is false: the job is deleted without further checks. When a job is deleted, all tasks and appointments attached to the job, if any, are also deleted.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ | |
| CheckAppointments | bit | | | |
| SentFromBackOffice | bit | 1 | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteJob`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteJob",
"ParameterNames": ["SourceApp", "SourceType", "JobNo"],
"ParameterValues": ["BC", "JOB", "JOB001"]
}
]
```
See `DELETE /job` in the [REST API reference](/develop/api-reference#tag/job).
## API reference
### Source
### Importance
Controls the visual indicator of the planned task in the planning board. Read more about [importance](/user/components/planningboard/appointments#importance).
### Category
Controls the visual indicator of the planned task in the planning board. Read more about [categories](/user/components/planningboard/appointments#category).
### Time Marker
Controls the visual indicator of the planned task in the planning board. Read more about [time markers](/user/components/planningboard/appointments#time-marker).
### Pin
Controls the visual indicator of the planned task in the map. Read more about [marker colors](/user/components/map#marker-colors).
### Free fields
---
## Notification
import Content from './global/sourceapp.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Upsert
Inserts or updates a notification record. Notifications can be applied for either one of the following dimensions:
- Global
- Job
- Task
- Appointment
This is a cascading mechanism. The appointment has a reference to a task, and a task has a reference to a job. Notifications for a job will be shown for all corresponding tasks and jobs. Same goes for tasks, whose notifications will be shown to the planned appointments.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------- | ------- | -------- | --------------------------------------- |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| NotificationType | int | 0 | | See [API reference](#notification-type) |
| NotificationCode | nvarchar(20) | | | |
| NotificationText | nvarchar(max) | | | |
| NotificationDate | datetime | | | |
| mboc_id | nvarchar(50) | | | |
| JobNo | nvarchar(50) | | | |
| TaskNo | nvarchar(50) | | | |
| AppointmentId | bigint | | | |
| AppointmentGuid | nvarchar(50) | | | |
| SentFromBackOffice | bit | 1 | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertConnector`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertNotification",
"ParameterNames": ["SourceApp","SourceType", "NotificationType", "NotificationCode", "NotificationText", "NotificationDate", "JobNo", "TaskNo"],
"ParameterValues": ["MYSOURCEAPP","MYSOURCETYPE", "1","CODE","TEXT","DATE", "JOBNO", "TASKNO"]
}
]
```
- See `POST /notification`, `PUT /notification` in the [REST API reference](/develop/api-reference#tag/notification).
- Example body:
```json
{
"SourceApp": "MYSOURCEAPP",
"SourceType": "MYSOURCETYPE",
"AppointmentNo": "MYAPPOINTMENTNO",
"TaskNo": "MYTASKNO",
"JobNo": "MYJOBNO",
"Type": 1,
"Code": "MYCODE",
"Text": "MYTEXT",
"Date": "MYDATE"
}
```
## Delete
Deletes the notification record. The behavior of this procedure depends on the data passed to it:
- A filled out `JobNo` and `TaskNo` will lead to all notification records attached to the `TaskNo` being deleted.
- Populating `JobNo` will delete all notification records attached to the `JobNo`.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| mboc_id | nvarchar(50) | | | |
| JobNo | nvarchar(50) | | | |
| TaskNo | nvarchar(50) | | | |
| AppointmentId | bigint | 0 | | |
| AppointmentGuid | nvarchar(50) | | | |
| SentFromBackOffice | bit | 1 | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertConnector`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteNotification",
"ParameterNames": ["SourceApp","SourceType", "JobNo", "TaskNo", "AppointmentGuid"],
"ParameterValues": ["MYSOURCEAPP","MYSOURCETYPE", "JOBNO","TASKNO","APPOINTMENTGUID"]
}
]
```
- See `DELETE /notification` in the [REST API reference](/develop/api-reference#tag/notification).
- Example body:
```json
{
"SourceApp": "MYSOURCEAPP",
"SourceType": "MYSOURCETYPE",
"AppointmentNo": "MYAPPOINTMENTNO",
"TaskNo": "MYTASKNO",
"JobNo": "MYJOBNO",
"Type": 1,
"Code": "MYCODE",
"Text": "MYTEXT",
"Date": "MYDATE"
}
```
## API reference
### Source
### Notification type
The values for notification types are:
- 0 for **Information**
- 1 for **Warning**
- 2 for **Error**.
---
## Recommendation API
:::info[Status: Preview]
The Recommendation API is in preview. Functionality, request/response shape, and behavior may change between releases. Not recommended for production yet.
:::
The Recommendation API supports planning automation and optimization. It finds available time slots for resources based on location, requirements, and other constraints. The API will evolve based on feedback before production release.
## Endpoint
**POST** `/recommendation`
For the full reference, see [the API Reference](https://sandbox.api.dimescheduler.com/docs/#tag/recommendation/POST/recommendation).
## Request format
The API accepts a JSON request with the following parameters:
- `startDate`: start date for the search window (ISO 8601).
- `endDate`: end date for the search window (ISO 8601).
- `duration`: duration of the appointment (ISO 8601 duration, e.g. `PT1H` for 1 hour).
- `requirements`: array of requirement filters (e.g. skills, regions).
- `location`: object containing:
- `address`: address string.
- `country`: country code.
- `options`: optional configuration object:
- `locationMode`: e.g. `home` to use the resource's home address.
- `maxDistanceKm`: maximum distance in kilometers.
- `granularity`: time slot granularity (ISO 8601 duration, e.g. `PT15M` for 15 minutes).
- `timeZone`: time zone identifier (e.g. `Europe/Brussels`).
### Example request
```bash
curl --location 'https://sandbox.api.dimescheduler.com/recommendation' \
--header 'content-type: application/json' \
--header 'X-Requested-With: XMLHttpRequest' \
--header 'X-API-KEY: DS-XXXX-XXXX-XXXXX' \
--data '{
"startDate": "2025-12-22T00:00:00",
"endDate": "2025-12-22T23:59:59",
"duration": "PT1H",
"requirements": [
"WEST"
],
"location": {
"address": "The customer's address",
"country": "DE"
},
"options": {
"locationMode": "home",
"maxDistanceKm": 100,
"granularity": "PT15M",
"timeZone": "Europe/Brussels"
}
}'
```
## Response format
The API returns a JSON object containing:
- `slots`: array of available time slots, each containing:
- `start`: start time of the slot (ISO 8601).
- `end`: end time of the slot (ISO 8601).
- `resourceNo`: resource number/identifier.
- `resourceName`: resource name.
- `distance`: distance in kilometers from the resource's location to the appointment location.
- `totalSlots`: total number of slots found.
- `resourceCount`: number of unique resources with available slots.
### Example response
```json
{
"slots": [
{
"start": "2025-12-23T08:00:00Z",
"end": "2025-12-23T09:00:00Z",
"resourceNo": "JEANNE",
"resourceName": "Jeanne De Gant",
"distance": 23.32
},
{
"start": "2025-12-23T08:00:00Z",
"end": "2025-12-23T09:00:00Z",
"resourceNo": "WILLIAM",
"resourceName": "William Wallonia",
"distance": 85.17
}
],
"totalSlots": 63,
"resourceCount": 3
}
```
## Read more
- [API overview](/develop/api)
- [API keys](/administration/apikeys)
- [Geocoding](/administration/settings/geocoding), which is what makes the `distance` field meaningful
- [Filter groups](/administration/settings/filter-groups), the source of the values you pass in `requirements`
---
## Recurring appointments API
The Recurring API enables programmatic creation of recurring appointments. It exposes the same functionality as the UI's recurring-appointment feature, accessible through an API endpoint so you can automate recurring appointment patterns without manual intervention.
## Endpoint
**POST** `/appointmentRecurring`
For the full reference, see [the API Reference](https://sandbox.api.dimescheduler.com/docs/#tag/recurring-appointment/POST/appointmentRecurring).
## What it does
The Recurring API mirrors the UI's recurring-appointment feature, but exposes it programmatically. Useful for:
- Automating appointment creation workflows.
- Integrating with external systems that need to schedule recurring appointments.
- Bulk creation of recurring appointment patterns.
- Scripting and automation scenarios.
## Example request
```bash
curl https://sandbox.api.dimescheduler.com/appointmentRecurring \
--request POST \
--header 'Content-Type: application/json' \
--data '{
"startDate": "",
"endDate": "",
"resource": "",
"taskNo": null,
"jobNo": null,
"categoryName": null,
"timeMarkerName": null,
"recurrenceRule": "",
"subject": null,
"body": null
}'
```
### Request parameters
- `startDate`: start date for the recurring appointment series.
- `endDate`: end date for the recurring appointment series.
- `resource`: resource identifier.
- `taskNo`: task number (optional).
- `jobNo`: job number (optional).
- `categoryName`: category name (optional).
- `timeMarkerName`: time marker name (optional).
- `recurrenceRule`: recurrence rule defining the pattern (e.g. daily, weekly, monthly).
- `subject`: appointment subject (optional).
- `body`: appointment body/description (optional).
---
## Resource
import Content from './global/sourceapp.md';
import FreeFields from './global/freefield.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
A resource in Dime.Scheduler is someone or something that needs to be allocated during a certain timeframe in order for a task to be performed or fulfilled. This can be a service technician, a sales person, an employee, a car, a machine or equipment, a hotel or meeting room, etc.
The following entities are exposed through the API.
```mermaid
erDiagram
Resource }o--o| ResourceGpsTracking : ""
Resource }o--o| ResourceUri : ""
Resource ||--|| ResourceCapacity : ""
Resource ||--|| ResourceFilterValue : ""
ResourceFilterValue }o--|| Filter : ""
```
## Resource
### Upsert
Inserts or updates a resource record.
| Name | Data Type | Default | Required | Description |
| ---------------------------- | -------------- | ------- | -------- | -------------------------------------------------- |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| ResourceType | nvarchar(100) | | | The resource type's name |
| ResourceTypeId | int | | | The resource type's internal id |
| ResourceName | nvarchar(100) | | | |
| DisplayName | nvarchar(100) | | | |
| Department | nvarchar(50) | | | |
| ResourceNo | nvarchar(50) | | ✔️ | The resource's external identifier. |
| Email | nvarchar(100) | | | |
| Phone | nvarchar(50) | | | |
| MobilePhone | nvarchar(50) | | | |
| ReplacementResource | bit | | | |
| FieldServiceEmail | nvarchar(100) | | | |
| PersonalEmail | nvarchar(100) | | | |
| GpsTrackingResourceNo | nvarchar(50) | | | |
| HomeAddress | nvarchar(max) | | | |
| HomeAddressGeoLong | float | | | |
| HomeAddressGeoLat | float | | | |
| HomePhone | nvarchar(50) | | | |
| HomeEmail | nvarchar(50) | | | |
| HomeRegion | nvarchar(10) | | | |
| HomePostcode | nvarchar(20) | | | |
| HomeCity | nvarchar(50) | | | |
| HomeCounty | nvarchar(50) | | | |
| HomeState | nvarchar(50) | | | |
| HomeCountry | nvarchar(50) | | | |
| TeamCode | nvarchar(20) | | | |
| TeamName | nvarchar(100) | | | |
| TeamType | nvarchar(20) | | | |
| TeamSort | int | | | |
| TeamMemberType | nvarchar(20) | | | |
| TeamMemberSort | int | | | |
| FreeText1 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText2 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText3 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText4 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText5 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText6 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText7 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText8 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText9 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText10 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText11 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText12 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText13 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText14 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText15 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText16 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText17 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText18 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText19 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText20 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeDecimal1 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal2 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal3 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal4 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal5 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDate1 | datetime | | | See [API reference](#free-fields) |
| FreeDate2 | datetime | | | See [API reference](#free-fields) |
| FreeDate3 | datetime | | | See [API reference](#free-fields) |
| FreeDate4 | datetime | | | See [API reference](#free-fields) |
| FreeDate5 | datetime | | | See [API reference](#free-fields) |
| FreeBit1 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit2 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit3 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit4 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit5 | bit | 0 | | See [API reference](#free-fields) |
| DoNotShow | bit | | | See [API reference](#do-not-show) |
| InServiceFrom | date | | | |
| InServiceTill | date | | | |
| ExchangeIntegrationEnabled | bit | | | See [API reference](#exchange-integration-enabled) |
| url1 | nvarchar(1000) | | | |
| urldesc1 | nvarchar(255) | | | |
| url2 | nvarchar(1000) | | | |
| urldesc2 | nvarchar(255) | | | |
| url3 | nvarchar(1000) | | | |
| urldesc3 | nvarchar(255) | | | |
| BulkPlanning | bit | | | |
| BulkCapacity | decimal(18,6) | | | |
| ResourceGpsTrackingEnabled | bit | | | See [API reference](#gps-tracking-enabled) |
| Pin | nvarchar(100) | | | [See API reference](#pin) |
| ExcludeFromBudgetCalculation | bit | 0 | | [See API reference](#exclude-from-budget-calculation) |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertResource`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertResource",
"ParameterNames": ["ResourceNo", "DisplayName"],
"ParameterValues": ["LINDA", "Linda Martin"]
}
]
```
See `POST /resource`, `PUT /resource` in the [REST API reference](/develop/api-reference#tag/resource).
## Filter value
### Upsert filter value
Inserts or updates a resource filter value record. This assigns a filter value to a resource.
| Name | Data Type | Default | Required | Description |
| -------------- | ------------- | ------- | -------- | -------------------------------------- |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| ResourceNo | nvarchar(50) | | ✔️ | |
| FilterGroup | nvarchar(50) | | ✔️ | |
| FilterValue | nvarchar(100) | | ✔️ | |
| TransferToTemp | bit | 0 | | [See API reference](#transfer-to-temp) |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertResourceFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertResourceFilterValue",
"ParameterNames": ["ResourceNo", "FilterGroup", "FilterValue"],
"ParameterValues": ["LINDA", "Skill", "Service management"]
}
]
```
See `POST /resourceFilterValue`, `PUT /resourceFilterValue` in the [REST API reference](/develop/api-reference#tag/resource-filter-value).
### Delete filter value
Removes a filter value from a resource.
| Name | Data Type | Default | Required | Description |
| ----------- | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| ResourceNo | nvarchar(50) | | ✔️ | |
| FilterGroup | nvarchar(20) | | ✔️ | |
| FilterValue | nvarchar(20) | | ✔️ | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteResourceFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteResourceFilterValue",
"ParameterNames": ["ResourceNo", "FilterGroup", "FilterValue"],
"ParameterValues": ["LINDA", "Skill", "Service management"]
}
]
```
See `DELETE /resourceFilterValue` in the [REST API reference](/develop/api-reference#tag/resource-filter-value).
### Clear filter value
Removes all filter values from a resource.
| Name | Data Type | Default | Required | Description |
| ---------- | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | | See [API reference](#source) |
| SourceType | nvarchar(10) | | | See [API reference](#source) |
| ResourceNo | nvarchar(50) | | ✔️ | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_clearResourceFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_clearResourceFilterValue",
"ParameterNames": ["ResourceNo"],
"ParameterValues": ["LINDA"]
}
]
```
This endpoint is not available yet.
## Upsert live location
Inserts or updates the live location of a resource. The position on the map in Dime.Scheduler is updated automatically when a record is inserted or updated.
| Name | Data Type | Default | Required | Description |
| --------------------- | ------------- | ------- | -------- | ----------- |
| ResourceNo | nvarchar(50) | | ✔️ | |
| GpsTrackingResourceNo | nvarchar(50) | | ✔️ | |
| Latitude | float | | ✔️ | |
| Longitude | float | | ✔️ | |
| Speed | int | | | Deprecated |
| Date | date | | | Deprecated |
| RowId | nvarchar(100) | | | Deprecated |
| Power | nvarchar(50) | | | Deprecated |
> The resource number **or** the GPS resource tracking number is required in order to successfully insert or update a record.
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertResourceGpsTracking`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertResourceGpsTracking",
"ParameterNames": ["ResourceNo", "GpsTrackingResourceNo", "Longitude", "Latitude"],
"ParameterValues": ["LINDA", "LINDA", "50.45545","30.5308981"]
}
]
```
- See `POST /resourceGpsTracking`, `PUT /resourceGpsTracking` in the [REST API reference](/develop/api-reference#tag/resource-gps-tracking).
- Example body:
```json
[
{
"ResourceNo": "JEANNE",
"Latitude": 50.8503,
"Longitude": 4.3517
},
{
"ResourceNo": "WILLIAM",
"Latitude": 51.2194,
"Longitude": 4.4025
}
]
```
## Upsert capacity
The resource capacity is set per day. If omitted, default values are set for `CapacityUOM` (hour by default) and `CapacityUOMConversion` (3600 by default).
You need to provide either `CapacityInSeconds` or `CapacityQty`. The other value is calculated using `CapacityUOMConversion`.
The resource's capacity is vital for the [capacity pivot grid](/user/components/pivot) to generate reliable results.
| Name | Data Type | Default | Required |
| --------------------- | ------------- | ------- | -------- |
| ResourceNo | nvarchar(50) | | ✔️ |
| CapacityDate | date | | |
| CapacityInSeconds | bigint | | |
| CapacityQty | decimal(18,6) | | |
| CapacityUOM | nvarchar(20) | | |
| CapacityUOMConversion | decimal(18,6) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertResourceCapacity`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertResourceCapacity",
"ParameterNames": ["ResourceNo", "CapacityDate","CapacityQty" ],
"ParameterValues": ["LINDA", "2024-01-01", "8"]
}
]
```
See `POST /resourceCapacity`, `PUT /resourceCapacity` in the [REST API reference](/develop/api-reference#tag/resource-capacity).
## Upsert URI
Inserts or updates a resource URI record. Resource URIs contain links to documents, web pages and the back-office system.
| Name | Data Type | Default | Required |
| ---------- | -------------- | ------- | -------- |
| ResourceNo | nvarchar(50) | | ✔️ |
| Url | nvarchar(1000) | | |
| UrlDesc | nvarchar(255) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertResourceUrl`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertResourceUrl",
"ParameterNames": ["ResourceNo", "Url"],
"ParameterValues": ["LINDA", "https://docs.dimescheduler.com"]
}
]
```
See `POST /resourceUri`, `PUT /resourceUri` in the [REST API reference](/develop/api-reference#tag/resource-uri).
## Resource calendar
### Upsert
Assigns a calendar to a resource.
| Name | Data Type | Default | Required |
| -------------------- | ------------- | ------- | -------- |
| ResourceCalendarCode | nvarchar(50) | | ✔️ |
| ResourceNo | nvarchar(50) | | ✔️ |
| CalendarCode | nvarchar(255) | | ✔️ |
| StartDate | date | | ✔️ |
| EndDate | date | | ✔️ |
The `ResourceCalendarCode` identifies the particular assignment of a given resource to a certain calendar for a certain date range.
### Delete
Deletes a resource calendar assignment.
| Name | Data Type | Default | Required |
| -------------------- | ------------ | ------- | -------- |
| ResourceCalendarCode | nvarchar(50) | | ✔️ |
## Resource certificate
:::caution
This feature is deprecated and will be removed in a future version.
:::
### Upsert certificate
Inserts or updates a resource certificate record. A resource certificate contains information on certificates/exams/scores held or passed by the resource. Resource certificates can be searched and matched against a task's certificate in the resource and appointment page when `UseCertificate` is activated on the resource types.
| Name | Data Type | Default | Required |
| ------------- | ------------- | ------- | -------- |
| ResourceNo | nvarchar(50) | | ✔️ |
| CertificateNo | nvarchar(50) | | ✔️ |
| Score | nvarchar(100) | '' | |
| LastScoreDate | datetime | | |
| ValidUntil | datetime | | |
### Delete certificate
Deletes a resource certificate record.
| Name | Data Type | Default | Required |
| ------------- | ------------ | ------- | -------- |
| ResourceNo | nvarchar(50) | | ✔️ |
| CertificateNo | nvarchar(50) | | ✔️ |
## API reference
### Source
### Exchange Integration Enabled
This field determines whether to synchronize the appointment with the resource's Outlook calendar.
### GPS Tracking Enabled
This field determines whether to enable live location tracking. To use this feature, the 'Advanced map' optional modules must be acquired.
### Do not show
This field determines if the resource should be visible in the planning board. The number of resources with this field switched off (i.e. they are shown on the planning board) determines the [license validity](/administration/license).
### Pin
Controls the visual indicator of the planned task in the map. Read more about [marker colors](/user/components/map#marker-colors).
### Free fields
### Transfer to temp
The `TransferToTemp` field indicates whether the resource filter value(s) are being sent to Dime.Scheduler before (value = 1 or True) or after (value = 0 or False) the resource is sent.
### Exclude from budget calculation
When set to true, resources assigned to appointments will be ignored when updating the remaining budget of a task.
---
## Task
import Content from './global/sourceapp.md';
import FreeFields from './global/freefield.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
A task is a child entity of a job, and a job is required before any task can be added. A task record for which no appointments exist is considered as an open task, and will therefore be displayed in the open tasks component.
The following entities are exposed through the API.
```mermaid
erDiagram
Job only one--zero or more Task: ""
Task zero or more--zero or one Category : ""
Task zero or more--zero or one TimeMarker : ""
Task zero or more--one TaskContainer : ""
TaskContainer only one--zero or more Container : ""
Task zero or one--one TaskFilterValue : ""
TaskFilterValue one -- one Filter : ""
```
## Task
### Upsert
Inserts or updates a task record.
| Name | Data Type | Default | Required | Description |
| ------------------------------ | -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ | The job's external identifier to assign this task to. |
| TaskNo | nvarchar(50) | | ✔️ | The task's external identifier. |
| ShortDescription | nvarchar(50) | | ✔️ | |
| Description | nvarchar(MAX) | | | |
| TaskType | int | 0 | | [See API reference](#task-type) |
| Name | nvarchar(255) | | | |
| Type | nvarchar(20) | | | |
| Category | nvarchar(100) | | | [See API reference](#category) |
| TimeMarker | nvarchar(100) | | | [See API reference](#time-marker) |
| Pin | nvarchar(100) | | | [See API reference](#pin) |
| Tags | nvarchar(1000) | | | [See API reference](#tags) |
| ServiceNo | nvarchar(50) | | | |
| ServiceGroup | nvarchar(20) | | | |
| ServiceClass | nvarchar(20) | | | |
| ServiceSerialNo | nvarchar(50) | | | |
| ServiceName | nvarchar(50) | | | |
| IRISFault | nvarchar(10) | | | |
| IRISSymptom | nvarchar(10) | | | |
| IRISArea | nvarchar(10) | | | |
| IRISReason | nvarchar(10) | | | |
| IRISResolution | nvarchar(10) | | | |
| Skill1 | nvarchar(10) | | | |
| Skill2 | nvarchar(10) | | | |
| Skill3 | nvarchar(10) | | | |
| ContractNo | nvarchar(20) | | | |
| ContractType | nvarchar(30) | | | |
| ContractDescription | nvarchar(MAX) | | | |
| ContractStartDate | date | | | |
| ContractEndDate | date | | | |
| PartsWarrantyStartDate | date | | | |
| PartsWarrantyEndDate | date | | | |
| LaborWarrantyStartDate | date | | | |
| LaborWarrantyEndDate | date | | | |
| Importance | int | 0 | | [See API reference](#importance) |
| Status | nvarchar(20) | | | |
| ExpectedResponseDateTime | datetime | | | |
| ActualResponseDateTime | datetime | | | |
| RequestedStartDate | datetime | | | [See API reference](#requested-dates) |
| RequestedEndDate | datetime | | | [See API reference](#requested-dates) |
| RequestedStartTime | time | | | [See API reference](#requested-times) |
| RequestedEndTime | time | | | [See API reference](#requested-times) |
| ConfirmedStartDate | datetime | | | |
| ConfirmedEndDate | datetime | | | |
| ActualStartDate | datetime | | | |
| ActualEndDate | datetime | | | |
| LocationDescription | nvarchar(MAX) | | | |
| Duration | time(7) | | | |
| DurationInSeconds | bigint | | | The estimated duration of the task. Determines the initial length of the appointment when dropped on the planning board. |
| Subject | nvarchar(max) | | | See [API reference](#subject-and-body) |
| Body | nvarchar(max) | | | See [API reference](#subject-and-body) |
| InfiniteTask | bit | 0 | | See [API reference](#infinite) |
| TaskOpenAsOf | datetime | | | |
| TaskOpenTill | datetime | | | |
| RequiredTotalDuration | time(7) | | | |
| RequiredNoResources | int | | | |
| AppointmentEarliestAllowed | datetime | | | [See API reference](#time-windows) |
| AppointmentLatestAllowed | datetime | | | [See API reference](#time-windows) |
| FreeText1 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText2 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText3 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText4 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText5 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText6 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText7 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText8 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText9 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText10 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText11 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText12 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText13 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText14 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText15 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText16 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText17 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText18 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText19 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeText20 | nvarchar(100) | | | See [API reference](#free-fields) |
| FreeDecimal1 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal2 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal3 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal4 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDecimal5 | decimal(18, 6) | | | See [API reference](#free-fields) |
| FreeDate1 | datetime | | | See [API reference](#free-fields) |
| FreeDate2 | datetime | | | See [API reference](#free-fields) |
| FreeDate3 | datetime | | | See [API reference](#free-fields) |
| FreeDate4 | datetime | | | See [API reference](#free-fields) |
| FreeDate5 | datetime | | | See [API reference](#free-fields) |
| FreeBit1 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit2 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit3 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit4 | bit | 0 | | See [API reference](#free-fields) |
| FreeBit5 | bit | 0 | | See [API reference](#free-fields) |
| url1 | nvarchar(1000) | | | |
| urldesc1 | nvarchar(255) | | | |
| url2 | nvarchar(1000) | | | |
| urldesc2 | nvarchar(255) | | | |
| url3 | nvarchar(1000) | | | |
| urldesc3 | nvarchar(255) | | | |
| CertificateNo | nvarchar(50) | | | |
| RequiredTotalDurationInSeconds | bigint | | | |
| DoNotCountAppointmentResource | bit | 0 | | |
| IsComplete | bit | 0 | | |
| PlanningUOM | nvarchar(20) | | | |
| PlanningUOMConversion | decimal(18,6) | 0 | | |
| PlanningQty | decimal(18,6) | 0 | | |
| UseFixPlanningQty | bit | 0 | | |
| RoundToUOM | bit | 0 | | |
| AppointmentTemplate | nvarchar(100) | | | |
| BulkPlanningQty | decimal(18, 6) | | | |
| StartDate | datetime | | | |
| EndDate | datetime | | | |
| PercentDone | int | | | |
| SchedulingMode | int | 0 | | |
| BaseLineStartDate | datetime | | | |
| BaseLineEndDate | datetime | | | |
| BaseLinePercentDone | int | | | |
| DeadLine | datetime | | | |
| Index | int | | | |
| ConstraintType | int | 0 | | |
| ConstraintDate | datetime | | | |
| ParentTaskNo | nvarchar(50) | | | |
| CalendarCode | nvarchar(255) | | | |
| PredecessorTaskNo | nvarchar(50) | | | |
| PredecessorLag | int | | | |
| ManuallyScheduled | bit | 0 | | |
| Note | nvarchar(max) | | | |
| OverRuleGanttPlanning | bit | 0 | | |
| IgnoreCalendars | bit | 0 | | |
| ContainerName | nvarchar(255) | | | |
| ContainerIndex | int | | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertTask`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertTask",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "ShortDescription", "Description"],
"ParameterValues": ["BC", "JOB", "JOB001", "TASK001", "Important work", "Important work, but in more words"]
}
]
```
See `POST /task`, `PUT /task` in the [REST API reference](/develop/api-reference#tag/task).
### Delete
Deletes a task record. The behavior of this procedure depends on the `CheckAppointments` flag:
- When `CheckAppointments` is true: the task is not deleted when there are appointments for the task.
- When `CheckAppointments` is false: the task is deleted without further checks. When a task is deleted, all appointments attached to the task, if any, are also deleted.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
| CheckAppointments | bit | | ✔️ |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteTask`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteTask",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "CheckAppointments"],
"ParameterValues": ["BC", "JOB", "JOB001", "TASK001", "false"]
}
]
```
See `DELETE /task` in the [REST API reference](/develop/api-reference#tag/task).
## Upsert task URI
Inserts or updates a task uri record. A task link contains links to documents, web pages and the back-office system.
| Name | Data Type | Default | Required | Description |
| ---------- | -------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
| url | nvarchar(1000) | | |
| urldesc | nvarchar(255) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertTaskUrl`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertTaskUrl",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "url", "urldesc"],
"ParameterValues": ["BC", "Job", "JOB001", "JOBTASK001", "https://docs.dimescheduler.com", "Dime.Scheduler docs"]
}
]
```
See `POST /taskUri`, `PUT /taskUri` in the [REST API reference](/develop/api-reference#tag/task-uri).
## Set lock
Locks or unlocks all appointments for the task. A locked appointment can not be modified nor deleted on the planning board.
| Name | Data Type | Default | Required | Description |
| ------------------ | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
| Locked | bit | 0 | |
| SentFromBackOffice | bit | 1 | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_updateTaskLocked`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_updateTaskLocked",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "Locked"],
"ParameterValues": ["BC", "Job", "JOB001", "JOBTASK001", "true"]
}
]
```
See `POST /taskLocked` in the [REST API reference](/develop/api-reference#tag/task-lock).
## Filter value
### Upsert filter value
Inserts or updates a task value record. This assigns a filter value to a task.
| Name | Data Type | Default | Required | Description |
| -------------- | ------------- | ------- | -------- | -------------------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ | |
| TaskNo | nvarchar(50) | | ✔️ | |
| FilterGroup | nvarchar(50) | | ✔️ | |
| FilterValue | nvarchar(100) | | ✔️ | |
| TransferToTemp | bit | 0 | | [See API reference](#transfer-to-temp) |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertTaskFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertTaskFilterValue",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "FilterGroup", "FilterValue"],
"ParameterValues": ["BC", "Job", "JOB001", "JOBTASK001", "Skill", "Yodeling"]
}
]
```
See `POST /taskFilterValue`, `PUT /taskFilterValue` in the [REST API reference](/develop/api-reference#tag/task-filter-value).
### Delete filter value
Deletes a task filter value record. This removes a filter value from a task.
| Name | Data Type | Default | Required | Description |
| ----------- | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
| FilterGroup | nvarchar(20) | | ✔️ |
| FilterValue | nvarchar(20) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteTaskFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteTaskFilterValue",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "FilterGroup", "FilterValue"],
"ParameterValues": ["BC", "Job", "JOB001", "JOBTASK001", "Skill", "Yodeling"]
}
]
```
See `DELETE /taskFilterValue` in the [REST API reference](/develop/api-reference#tag/task-filter-value).
### Clear filter value
Deletes all task filter value records. This removes all filter values from a task.
| Name | Data Type | Default | Required | Description |
| ---------- | ------------ | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_clearTaskFilterValue`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_clearTaskFilterValue",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo"],
"ParameterValues": ["BC", "Job", "JOB001", "JOBTASK001"]
}
]
```
This endpoint is not available yet.
## Task container
### Upsert task container
Inserts or updates a task container.
| Name | Data Type | Default | Required | Description |
| ------------- | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
| ContainerName | nvarchar(255) | | ✔️ |
| Index | int | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_upsertTaskContainer`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_upsertTaskContainer",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "ContainerName"],
"ParameterValues": ["BC", "Job", "JOB001", "JOBTASK001", "PROJECT_ABC"]
}
]
```
See `POST /taskContainer`, `PUT /taskContainer` in the [REST API reference](/develop/api-reference#tag/task-container).
### Delete task container
Removes the task container.
| Name | Data Type | Default | Required | Description |
| ------------- | ------------- | ------- | -------- | ---------------------------- |
| SourceApp | nvarchar(30) | | ✔️ | [See API reference](#source) |
| SourceType | nvarchar(10) | | ✔️ | [See API reference](#source) |
| JobNo | nvarchar(50) | | ✔️ |
| TaskNo | nvarchar(50) | | ✔️ |
| ContainerName | nvarchar(255) | | |
- This endpoint is available in the import API endpoint `/import` via the procedure `mboc_deleteTaskContainer`.
- Example body:
```json
[
{
"StoredProcedureName": "mboc_deleteTaskContainer",
"ParameterNames": ["SourceApp", "SourceType", "JobNo", "TaskNo", "ContainerName"],
"ParameterValues": ["BC", "JOB", "JOB001", "JOBTASK001", "PROJECT_ABC"]
}
]
```
See `DELETE /taskContainer` in the [REST API reference](/develop/api-reference#tag/task-container).
## API Reference
### Requested dates
This set of fields are behavioral data. See [requested dates](/user/components/planningboard/appointments#requested-dates) for more info.
### Requested times
This set of fields are behavioral data. See [requested times](/user/components/planningboard/appointments#requested-times) for more info.
### Source
### Infinite
When set to true, the task will always remain available in the open task list. Typical examples include holiday and sickness tasks.
### Time windows
The earliest and latest allowed dates make it possible to define a date range in which the task has to be scheduled. See [allowed date constraints](/administration/setup/planning#respect-allowed-date-constraints) for more information.
### Task type
Planning can be done in two ways: through the planning board or through the Gantt chart. When the task type is the latter (value 1), drag and drop will be disabled in the open task list, and planning will need to be done through the Gantt chart.
Read more on [task types](/user/components/opentasks#what-ends-up-here).
### Importance
Controls the visual indicator of the planned task in the planning board. Read more about [importance](/user/components/planningboard/appointments#importance).
### Subject and body
Through the [field template](/administration/settings/fieldtemplates) setup, the task's subject and body is set when imported in Dime.Scheduler.
### Category
Controls the visual indicator of the planned task in the planning board. Read more about [categories](/user/components/planningboard/appointments#category).
### Time Marker
Controls the visual indicator of the planned task in the planning board. Read more about [time markers](/user/components/planningboard/appointments#time-marker).
### Pin
Controls the visual indicator of the planned task in the map. Read more about [marker colors](/user/components/map#marker-colors).
### Tags
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases.
:::
A semicolon-delimited list of [tag](/develop/api/indicator#tag) codes, for example `URGENT;NIGHTSHIFT`. Unlike the category, which is a single value, a task can carry as many tags as fit within the 1000 characters. Codes that do not match a tag are stored but render as nothing.
Omitting the parameter and sending an empty value mean different things:
- **Omitted (`NULL`)** means "not provided". The stored tags are kept. A connector that knows nothing about tags therefore never wipes the tags a planner set by hand.
- **Empty string** means "no tags", and clears them.
When a task is planned on the board, the new appointment inherits the task's tags. From that point the two are independent: later updates to the task's tags do not reach the appointments that were already created. Read more about [tags](/user/features/tags).
### Free fields
### Transfer to temp
The `TransferToTemp` field indicates whether the task filter value(s) are being sent to Dime.Scheduler before (value = 1 or True) or after (value = 0 or False) the task is sent.
---
## User
import Content from './global/sourceapp.md';
import FreeFields from './global/freefield.md';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
### Create
Inserts a user to Dime.Scheduler.
| Name | Data Type | Default | Required | Description |
| ----------- | ------------- | ------- | -------- | -------------------------------------------------------- |
| name | nvarchar(255) | | ✔️ | The user's display name. |
| email | nvarchar(255) | | ✔️ | The user's email address. |
| isMSEntraID | boolean | false | ✔️ | True if MS Entra ID authentication scheme is to be used. |
- Endpoint: `/user`
- Supported HTTP methods: POST (create)
- Example body:
```json
{
"name": "John Doe",
"email": "john.doe@contoso.com"
}
```
---
## API
The API service lets you read and manipulate Dime.Scheduler data over a RESTful interface. You can fetch, create, update, and delete data in a predictable, uniform way.
:::tip
The OpenAPI specification is available on [api.dimescheduler.com/docs](https://api.dimescheduler.com/docs/index.html).
:::
## Environments
- Sandbox: https://sandbox.api.dimescheduler.com
- Production: https://api.dimescheduler.com
## Authentication
Two authentication methods are supported:
- **API key** (preferred)
- JWT (deprecated)
### JWT
:::warning[Deprecated]
JWT authentication is deprecated. Please use API key authentication instead, which is the preferred method.
:::
To get a JSON Web Token, call the `/token` endpoint on the API service (`https://sandbox.api.dimescheduler.com/token` or `https://api.dimescheduler.com/token`).
In the body, pass a JSON object with this shape:
```json
{
"username": "myuser",
"password": "mypassword"
}
```
Set the `Content-Type` header to `application/json`. Here's an example cURL request:
```bash
curl --location 'https://sandbox.api.dimescheduler.com/token'
--header 'Content-Type: application/json'
--data-raw '{
"username": "myuser@mydomain.com",
"password": "mypassword"
}
```
Run the request and take the `token` property from the response. Pass this value in the `Authorization` header of every subsequent request to the authenticated endpoints, prefixed with `Bearer`. The header should be formatted as `Bearer YOURTOKENHERE`. Here's an example cURL request to the import API:
```bash
curl --location 'https://sandbox.api.dimescheduler.com/import'
--header 'content-type: application/json'
--header 'Authorization: Bearer thetokencomeshere'
--data '[
{
"StoredProcedureName": "mboc_upsertJob",
"ParameterNames": ["SourceApp","SourceType","JobNo","ShortDescription","Description","SiteAddress","SiteCountry"],
"ParameterValues": ["BC1","SERVICE","SVC001","Service 1","Service 1","Street 1","USA"]
}
]'
```
### API key
:::tip[Preferred Method]
API key authentication is the preferred method for authenticating with the API.
:::
Create an [API key](/administration/apikeys) in Dime.Scheduler and pass its value in the `X-API-KEY` header of every request to the API.
Here's an example cURL request to the import API using an API key:
```bash
curl --location 'https://sandbox.api.dimescheduler.com/import'
--header 'content-type: application/json'
--header 'X-API-KEY: MYAPIKEYCOMESHERE'
--data '[
{
"StoredProcedureName": "mboc_upsertJob",
"ParameterNames": ["SourceApp","SourceType","JobNo","ShortDescription","Description","SiteAddress","SiteCountry"],
"ParameterValues": ["BC1","SERVICE","SVC001","Service 1","Service 1","Street 1","USA"]
}
]'
```
## Endpoints
For details on each group of endpoints, open one of the cards below:
import DocCardList from '@theme/DocCardList';
## Data model
```mermaid
erDiagram
Job only one--zero or more Task: ""
Appointment zero or one --zero or more Task: ""
Appointment zero or more--zero or one Category : ""
Appointment zero or more--zero or one TimeMarker : ""
Appointment zero or more--one AppointmentContainer : ""
AppointmentContainer only one--zero or more Container : ""
Appointment one or more --one Assignment : ""
Assignment one -- zero or more Resource : ""
Task zero or more--zero or one Category : ""
Task zero or more--zero or one TimeMarker : ""
Task zero or more--one TaskContainer : ""
TaskContainer only one--zero or more Container : ""
Task one--one or more TaskFilterValue : ""
TaskFilterValue one -- one Filter : ""
Appointment one or more --zero or more Notification : ""
Task one or more --zero or more Notification : ""
Job one or more --zero or more Notification : ""
Resource one--one or more ResourceFilterValue : ""
ResourceFilterValue one -- one Filter : ""
```
---
## Architecture
Dime.Scheduler is built to integrate. Its API and its ability to post planning data back to your system let you connect almost any ERP, CRM, or line-of-business application. This page walks through the components that make that integration work and the data flow between the two systems.
Your integration follows the same workflow as the standard connectors that Dime Software ships out of the box. Step through it below:
The work on your side is to extend your ERP, CRM, or LoB system so it can send data to Dime.Scheduler and receive and process the planning data that comes back.
The simplified data model below shows the components you need to get data in and out of Dime.Scheduler. Alongside the transactional data, always supply the `Source App` and `Source Type` fields with every record that originates in your system. Select an entity to see its key fields and how it connects to the rest of the model.
For the full entity-relationship diagrams, including cardinality and every field, see the [data model reference](/develop/model).
To write back the planning data that Dime.Scheduler generates, your system must be able to receive incoming web service calls and run code that writes the planning data into the correct tables.
The API gives you access to all data in Dime.Scheduler. To get planning data produced in Dime.Scheduler back into your system, consider the standard [webhook connector](/integrations/webhook/intro). If that connector does not meet your needs, get in touch with us to build a standard connector for the new system.
---
## Authentication & configuration
This page explains how `dimescheduler` finds your credentials and how to point the CLI at the right environment. The CLI resolves credentials from this chain, highest precedence first:
1. Command-line flags (`--api-key`, `--environment`, `--profile`, `--config`).
2. Environment variables (`DIMESCHEDULER_API_KEY`, `DIMESCHEDULER_ENVIRONMENT`, `DIMESCHEDULER_OUTPUT`, `DIMESCHEDULER_PROFILE`, `DIMESCHEDULER_CONFIG`).
3. The active profile in the config file.
4. Compiled-in defaults.
So you can keep sensible defaults in a config file and override them per call without editing the file.
## Interactive login
```bash
$ dimescheduler auth login
API key: ********
Logged in. Config saved to ~/.config/dimescheduler/config.yaml
```
`auth login` reads the key from stdin (so it never lands in shell history), writes it to the config file, and verifies it with a probe call against the chosen environment.
Other commands in the same family:
```bash
dimescheduler auth status # show the active profile and environment
dimescheduler auth logout # remove the key from the config file
```
## Non-interactive (CI, scripts)
In CI you usually don't want to write to a config file. Set the environment variable instead:
```bash
export DIMESCHEDULER_API_KEY=...
export DIMESCHEDULER_ENVIRONMENT=production # optional
dimescheduler resources list
```
Or pass `--api-key` per call.
:::warning[Don't paste keys]
A key on the command line lands in shell history and process listings. Read it from a secret manager and pipe it in.
:::
## Configuration
The config file lives at:
| OS | Path |
| -------- | ---- |
| Linux | `$XDG_CONFIG_HOME/dimescheduler/config.yaml` or `~/.config/dimescheduler/config.yaml` |
| macOS | `~/.config/dimescheduler/config.yaml` |
| Windows | `%APPDATA%\dimescheduler\config.yaml` |
Override the location with `--config ` or `DIMESCHEDULER_CONFIG`.
A minimal file:
```yaml
api_key: prod-key
environment: production
```
### Profiles
Keep multiple sets of credentials in the same file and switch between them. A typical setup pairs a default production key with a sandbox profile:
```yaml
api_key: prod-key
environment: production
profiles:
sandbox:
api_key: sandbox-key
environment: sandbox
test:
api_key: test-key
environment: test
```
Switch profiles per call:
```bash
dimescheduler --profile sandbox resources list
```
Or persist the switch:
```bash
dimescheduler config set profile sandbox
```
### Inspecting and editing the config
```bash
dimescheduler config list # print the resolved config
dimescheduler config get api_key # print one value
dimescheduler config set environment sandbox
dimescheduler config path # print the path to the config file
```
## Environment switch
The `--environment` flag (or `DIMESCHEDULER_ENVIRONMENT`) accepts a short name or a full URL:
| Value | Base URL |
| ------------- | -------- |
| `production` | `https://api.dimescheduler.com` |
| `sandbox` | `https://sandbox.api.dimescheduler.com` |
| `test` | `https://test.api.dimescheduler.com` |
| `https://…` | Any URL - useful for pointing at the [local mock server](https://github.com/dime-scheduler/sdk#mock-server). |
## Where to get an API key
Generate one in Dime.Scheduler before you authenticate:
- [Production](https://app.dimescheduler.com/keys)
- [Sandbox](https://sandbox.app.dimescheduler.com/keys)
See [API keys](/administration/apikeys) for the full guide.
---
## Commands reference
Every command is discoverable via `--help`. This page is the map: what's available, how it's organised, and the rules every command follows.
## The shape, again
```text
dimescheduler [flags]
```
`` is the entity in plural kebab-case (`categories`, `resource-gps-trackings`, …). `` is `list`, `create`, `update`, or `delete`. Most entities support all four; a few are read-only.
For `create` / `update` / `delete`, the flags mirror the entity's fields. Run `dimescheduler --help` to see them - the flag set is generated from the OpenAPI specification, so it can't drift from the API.
## Top-level operational commands
These commands aren't tied to an entity:
```bash
dimescheduler auth login | logout | status
dimescheduler config get | set | list | path
dimescheduler api [--body ...] [--body-file ...]
dimescheduler completion bash | zsh | fish | powershell
dimescheduler version [--check-update]
```
`auth` and `config` are covered in [Authentication](./authentication.md). The rest are below.
## Entities
Here's the full list of entity commands, grouped by how they relate. The first column is the command name (plural, kebab-case); the second lists any aliases the CLI also accepts.
### Work items
| Entity | Aliases | Verbs |
| ------ | ------- | ----- |
| `jobs` | `job` | `list / create / update / delete` |
| `tasks` | `task` | `list / create / update / delete` |
| `task-containers` | | `list / create / update / delete` |
| `task-filter-values` | | `list / create / update / delete` |
| `task-locked` | | `list / create / update / delete` |
| `task-uris` | | `list / create / update / delete` |
### Resources
| Entity | Aliases | Verbs |
| ------ | ------- | ----- |
| `resources` | `resource`, `res` | `list / create / update / delete` |
| `resource-calendars` | | `list / create / update / delete` |
| `resource-capacities` | | `list / create / update / delete` - `list` requires `--start` and `--end` |
| `resource-certificates` | | `list / create / update / delete` |
| `resource-filter-values` | | `list / create / update / delete` |
| `resource-gps-trackings` | | `list / create / update / delete` |
| `resource-uris` | | `list / create / update / delete` |
| `resource-types` | | `list` (read-only) |
### Appointments
| Entity | Aliases | Verbs |
| ------ | ------- | ----- |
| `appointments` | `appointment`, `appt` | `list / create / update / delete` - `list` requires `--from` and `--to` (RFC 3339) |
| `appointment-categories` | | `list / create / update / delete` |
| `appointment-contents` | | `list / create / update / delete` |
| `appointment-containers` | | `list / create / update / delete` |
| `appointment-field-values` | | `list / create / update / delete` |
| `appointment-importances` | | `list / create / update / delete` |
| `appointment-locked` | | `list / create / update / delete` |
| `appointment-planning-quantities` | | `list / create / update / delete` |
| `appointment-time-markers` | | `list / create / update / delete` |
| `appointment-uris` | | `list / create / update / delete` |
| `assignments` | `assignment` | `list / create / update / delete` |
| `appointment-dependencies` | | `list` (read-only) |
| `appointment-fields` | | `list` (read-only) |
### Filters and indicators
| Entity | Aliases | Verbs |
| ------ | ------- | ----- |
| `filter-groups` | | `list / create / update / delete` |
| `filter-values` | | `list / create / update / delete` |
| `categories` | `category`, `cat` | `list / create / update / delete` |
| `time-markers` | | `list / create / update / delete` |
| `pins` | `pin` | `list / create / update / delete` |
### Other
| Entity | Aliases | Verbs |
| ------ | ------- | ----- |
| `action-uris` | `action-uri` | `list / create / update / delete` |
| `captions` | `caption` | `list / create / update / delete` |
| `containers` | `container` | `list / create / update / delete` |
| `notifications` | `notification` | `list / create / update / delete` - `list` accepts `--page`, `--limit`, `--sort`, `--group`, `--filter` |
| `calendars` | | `list` (read-only) |
## Special list flags
Most `list` commands take no arguments, but three need parameters:
```bash
# Date range (RFC 3339)
dimescheduler appointments list --from 2026-05-01T00:00:00Z --to 2026-05-31T23:59:59Z
dimescheduler appointments list --from 2026-05-01T00:00:00Z --to 2026-05-31T23:59:59Z \
--resource RES1 --resource RES2
# Date range (capacities use --start / --end, not --from / --to)
dimescheduler resource-capacities list --start 2026-05-01T00:00:00Z --end 2026-05-31T23:59:59Z
# Paginated notifications
dimescheduler notifications list --page 1 --limit 50 --sort createdAt:desc
```
## `api` - the escape hatch {#api-the-escape-hatch}
For endpoints the typed commands don't cover, `api` makes any authenticated request. It works the same way as `gh api`:
```bash
# GET
dimescheduler api GET /category
# POST with an inline body
dimescheduler api POST /category --body '{"name":"VIP","color":"#22d3ee"}'
# POST from a file
dimescheduler api POST /task --body-file ./task.json
# Query parameters
dimescheduler api GET /notification --query 'page=1&limit=50'
```
Output formatting and `--jq` work the same as for typed commands.
## Completions
```bash
dimescheduler completion bash # source from .bashrc
dimescheduler completion zsh # source from .zshrc
dimescheduler completion fish # write to ~/.config/fish/completions/dimescheduler.fish
dimescheduler completion powershell # source from your PowerShell profile
```
## Version & update check
```bash
dimescheduler version # print version
dimescheduler version --check-update # compare against GitHub releases
```
## Exit codes
| Code | Meaning |
| ---- | ------- |
| `0` | Success. |
| `1` | Unexpected error (panic, internal bug). |
| `2` | Usage error (bad flags, missing required value). |
| `3` | Authentication error - missing or invalid API key. |
| `4` | API error - the server returned 4xx or 5xx. The response body is on stderr. |
| `5` | Network error - DNS, TCP, or TLS failure. |
Scripts can branch on these without parsing output. Combine with `--quiet` for clean `set -e` pipelines.
---
## The Dime.Scheduler CLI
`dimescheduler` is a single self-contained binary. There's nothing else to install - no .NET runtime, no Node, no Python. It works on macOS, Linux, and Windows.
The CLI is generated from the same OpenAPI specification as the [SDKs](../sdk/about.md), so its commands and flags stay in lockstep with the API.
## Install
### Homebrew (macOS, Linux)
```bash
brew tap dime-scheduler/distro https://github.com/dime-scheduler/distro
brew install dime-scheduler/distro/cli
```
### Scoop (Windows)
```powershell
scoop bucket add dime-scheduler https://github.com/dime-scheduler/distro
scoop install dime-scheduler/cli
```
### Direct download
Pre-built binaries for Linux, macOS, and Windows are attached to every release in the [`dime-scheduler/distro`](https://github.com/dime-scheduler/distro/releases) repo (look for `cli-v*` tags). They're also mirrored on the Azure CDN at `https://stdimescheduler.blob.core.windows.net/cli/v{version}/`.
Drop the binary somewhere on your `PATH` and you're done.
## Verify the install
```bash
dimescheduler version
```
Pass `--check-update` to compare against the latest release on GitHub.
## First call
The fastest way to get going - interactively log in, then list your resources:
```bash
dimescheduler auth login
dimescheduler resources list
```
`auth login` saves your API key to the config file (`~/.config/dimescheduler/config.yaml` on macOS/Linux, `%APPDATA%\dimescheduler\config.yaml` on Windows). For CI and other non-interactive contexts, set `DIMESCHEDULER_API_KEY` instead - see [Authentication](./authentication.md).
## Command shape
Every command follows the same shape:
```text
dimescheduler [flags]
```
- `` is the entity (plural, kebab-case): `categories`, `appointments`, `resource-gps-trackings`, …
- `` is `list`, `create`, `update`, or `delete`. Read-only entities only have `list`.
A handful of operational commands sit at the top level:
| Command | Purpose |
| --------------- | ------- |
| `auth` | Log in, log out, check status. See [Authentication](./authentication.md). |
| `config` | Inspect or change the config file. See [Configuration](./authentication.md#configuration). |
| `api` | Make any authenticated request - like `gh api`. See [API command](./commands.md#api-the-escape-hatch). |
| `completion` | Print shell completions for bash, zsh, fish, or PowerShell. |
| `version` | Print version, optionally check for updates. |
See [Commands](./commands.md) for the full reference, and [Scripting](./scripting.md) for output formats and piping recipes.
## Global flags
These apply to every command:
| Flag | What it does |
| ---------------- | ------------ |
| `--api-key` | API key. Overrides `DIMESCHEDULER_API_KEY` and the config file. |
| `--environment` | `production` (default) or `sandbox`. |
| `--output`, `-o` | `table` (default in a TTY), `json` (default when piped), or `yaml`. |
| `--profile`, `-p`| Named profile from the config file. |
| `--config` | Path to a config file. |
| `--timeout` | Per-request HTTP timeout, e.g. `30s`, `2m`. |
| `--debug` | Print every HTTP request/response to stderr. |
| `--verbose` | Print extra progress info. |
| `--quiet` | Suppress non-error output. |
| `--no-color` | Disable ANSI colours (also honours `NO_COLOR`). |
## Discovering options
Every command and subcommand has `--help`. That's the canonical reference - it ships with the binary you have installed and never drifts from the actual flags:
```bash
dimescheduler --help # all commands
dimescheduler categories --help # verbs for an entity
dimescheduler categories create --help # flags for a specific call
```
The pages in this section describe the **shape and intent** of each capability - install, auth, command reference, scripting. For the exact flags of any command, use `--help`.
---
## Scripting & output
The CLI is designed to be piped, scripted, and dropped into CI. Output format, filtering, and bulk operations all work the same way for every command.
## Output formats
```bash
dimescheduler resources list # default: table in a TTY, json when piped
dimescheduler resources list --output json
dimescheduler resources list --output yaml
dimescheduler resources list -o table
```
The default switches automatically - JSON when stdout isn't a terminal, table when it is - just like `kubectl`, `gh`, and `stripe`. That means you can pipe to `jq` without juggling flags:
```bash
dimescheduler resources list | jq '.[] | select(.disabled == false) | .name'
```
Override the default globally with `DIMESCHEDULER_OUTPUT=json` or per call with `--output`.
## Built-in `--jq`
For one-off filters, the CLI ships with `--jq` built in, so you don't need to install `jq` separately:
```bash
dimescheduler resources list --jq '.[] | select(.disabled == false) | .name'
dimescheduler appointments list --from 2026-05-01T00:00:00Z --to 2026-05-31T23:59:59Z \
--jq 'map(.appointmentNo)'
```
`--jq` implies `--output json` and runs the expression in-process.
## Bulk operations
Every `create`, `update`, and `delete` command accepts `--body-file` for batch payloads:
```bash
# Create many tasks at once
dimescheduler tasks create --body-file ./tasks.json
# Update many resources
dimescheduler resources update --body-file ./resources.yaml
```
The file can be JSON or YAML - the CLI detects the format for you. For quick inline bodies, use `--body`:
```bash
dimescheduler categories create --body '{"name":"VIP","color":"#22d3ee"}'
```
The flag-based shorthand (`--name`, `--color`, …) is the handiest choice for one-off CRUD. For bulk loads, the file route is faster, safer, and reviewable.
## Piping between commands
Output one command's results into the next without writing temp files:
```bash
# Lock every appointment in a date range
dimescheduler appointments list --from 2026-05-01T00:00:00Z --to 2026-05-01T23:59:59Z \
--jq 'map({appointmentNo: .appointmentNo, locked: true})' \
| dimescheduler appointment-locked create --body-file -
# Soft-delete every resource flagged inactive
dimescheduler resources list --jq 'map(select(.disabled == true) | {resourceNo})' \
| dimescheduler resources delete --body-file -
```
`--body-file -` reads from stdin.
## CI patterns
In a non-interactive shell, the CLI:
- Reads credentials from `DIMESCHEDULER_API_KEY` (and other `DIMESCHEDULER_*` env vars).
- Defaults `--output` to `json`, so post-processing is predictable.
- Returns one of the documented exit codes (see [Commands](./commands.md#exit-codes)).
A typical pipeline step:
```yaml
- name: Sync resources to sandbox
env:
DIMESCHEDULER_API_KEY: ${{ secrets.DS_SANDBOX_KEY }}
DIMESCHEDULER_ENVIRONMENT: sandbox
run: dimescheduler resources update --body-file ./resources.json
```
Use `--quiet` if you only want errors to land in the log:
```bash
dimescheduler resources update --body-file ./resources.json --quiet
```
## Debugging
When something looks off, `--debug` prints every HTTP request and response to stderr:
```bash
dimescheduler resources list --debug 2> debug.log
```
`--verbose` is lighter: progress, redirects, retry attempts, but no payload dumps.
If the server is returning unexpected errors, the `api` escape hatch lets you reproduce them at the raw HTTP level:
```bash
dimescheduler api GET /resource --debug
```
---
## Integrate your own system end to end
The [end-to-end quickstart](/develop/guide/quickstart) gets data *into* Dime.Scheduler. That is half an integration. This guide covers the whole loop: what to decide before you write anything, how to get the planning back out again, and what a custom integration needs before it can carry real work.
It is deliberately stack-agnostic. Everything here applies whether you use an SDK, the CLI, or raw HTTP, because they all wrap [the same REST API](/develop/api).
## The shape of an integration
Your integration is two independent channels, one in each direction, and they are built and configured separately.
**Inbound**, your system pushes master data and work into Dime.Scheduler: resources, jobs, tasks, and the filter and indicator catalogs that make them plannable. This is an API client you write and run.
**Outbound**, Dime.Scheduler pushes the resulting planning back to you. This is an HTTP endpoint you host and Dime.Scheduler calls.
The two fail independently, and the outbound half is the one most often left until last and then discovered to be the larger job. A planner scheduling work that never reaches your system is not an integration, it is a second system to keep in sync by hand. Plan for both from the start.
This is the same workflow the standard Business Central and NAV connectors follow. You are building what they provide.
## Decide your source app and source type first
Before writing a line of code, settle these two values. They travel with every record, they cannot be changed casually afterwards, and almost every confusing failure downstream traces back to getting them wrong.
**Source app** identifies the system a record came from, for example `ACME-ERP` or `CRM-EU`. It does two jobs. It tells Dime.Scheduler which connector to call when a planner changes something, so the connector and the inbound data must agree on the same value. And it keeps records from separate systems distinct when they share a tenant: two unrelated `SO100` job numbers stay separate because their source apps differ.
**Source type** identifies the kind of record within that system, for example `SERVICEORDER` or `WORKORDER`. It is what lets you route different kinds of work through different logic on the way back.
Two rules of thumb. Use one source app per system *instance*, not per system: a test ERP and a production ERP feeding the same tenant need different values. And keep the taxonomy small, because every source type you invent is another branch in your return-path code.
## Model the data
Dime.Scheduler's model is small, and the relationships are what matter. A job holds tasks; a task is what a planner schedules; scheduling one produces an appointment against a resource. Filter groups and values drive who may see and plan what, and indicators drive what it looks like.
See [architecture](/architecture) for the interactive model, and the [data model reference](/develop/model) for the full entity-relationship diagrams.
Map your entities onto that model before you start pushing. The common mistake is mapping one of your records onto a job when it should be a task, which only becomes obvious once planners find they cannot schedule at the granularity they need.
## Build the inbound channel
Authenticate with an [API key](/administration/apikeys), which is the right choice here because a key represents a process rather than a person. Use OAuth instead only if you are building something user-facing where individual permissions or an audit trail of who did what matters.
Then push, in dependency order, because parents must exist before children:
1. Master data: resources, filter groups and their values, categories and time markers.
2. Transactional data: jobs, then the tasks beneath them.
3. Assignments: filter values onto resources and tasks, indicators onto jobs.
The [quickstart](/develop/guide/quickstart) walks this with working code and an interactive `curl` walkthrough.
Two things worth designing in now rather than retrofitting:
**Make it idempotent.** The API upserts on the combination of source app, source type, and the record's own number, so re-sending the same record updates rather than duplicates. Lean on that: a sync you can safely re-run is one you can recover with, and the alternative is reconciling duplicates by hand.
**Decide between push and poll.** Pushing on change keeps Dime.Scheduler current but couples your write path to its availability. A periodic full or delta sync is simpler and more forgiving. Many integrations do both: push for responsiveness, and a nightly sync to repair anything the push missed.
## Build the outbound channel
This is the half the quickstart does not cover, and the half that makes the loop real.
Use the [webhook connector](/integrations/webhook/intro). It is the generic outbound path: every appointment change on the planning board fires a POST to a URL you own, carrying the full appointment as JSON. Register it in [connectors](/administration/connectors) against your source app, set the HTTP POST URI, and that is the entire Dime.Scheduler-side configuration.
Everything else is yours. The webhook connector deliberately does not interpret the payload, link it back to a record on your side, or run a workflow on success. Unlike the BC and NAV connectors it delivers the message and stops.
So your receiver needs to:
- **Authenticate the caller.** [Several types are supported](/integrations/webhook/authentication). Prefer **MS Entra ID** or **OAuth2** for anything crossing the public internet, since neither sends a credential on every call and both let you rotate secrets without redeploying. Use **basic** only if your receiver cannot speak OAuth2, and only over HTTPS. Use **none** only when network controls do the authentication for you.
- **Match the appointment back to your record.** This is where source app and source type earn their keep, together with the job and task numbers you originally sent.
- **Handle create, update, and delete.** Delete is the one that gets skipped, and it fails quietly: cancelled work stays live in your system and nobody notices for weeks.
- **Be idempotent.** Assume a message can arrive twice.
If your receiver would mostly be glue, consider [Power Automate](/integrations/powerplatform/triggers) instead. It registers its own connector behind the scenes and gives you hundreds of destinations without hosting anything.
## Handle failures
Both channels fail, and they surface in different places.
Inbound calls return errors to your client directly, so handle them where you make the call.
Outbound failures land in Dime.Scheduler. The [errors page](/administration/transactions/errors) lists every faulted appointment, with a **Webhook** tab that isolates yours from Dynamics and Exchange traffic. From there you can inspect an appointment's transactions and retry them, and [webhook transaction errors](/integrations/webhook/transaction-errors) explains the codes.
Two habits pay for themselves. Return an accurate status code from your endpoint rather than swallowing errors into a 200, because a receiver that always reports success makes the errors page useless and hides real failures. And decide who watches that page, since an errors list nobody owns silently accumulates until it is worthless.
## Go to production
Build against the **sandbox**. Point the client at it, register the connector there, and keep a throwaway source app for experiments so test records never mix with real ones.
Before switching over, work through the whole loop deliberately:
- Push master data and confirm resources appear on the board and tasks in the [open tasks grid](/user/components/opentasks). Records that exist but never reach open tasks usually means the assignments were skipped or the source app and source type do not match between the task and its job.
- Plan something and confirm it reaches your endpoint with a payload you can act on.
- Move it, then delete it, and confirm both arrive.
- Make your endpoint fail on purpose and confirm the failure shows up where you expect.
- Re-run the inbound sync and confirm nothing duplicates.
Then create the production connector as a **new** connector rather than repointing the tested one, so the working configuration survives.
The **Enabled** flag on a connector is useful here: it lets you register production ahead of time without activating it, and gives you a way to silence a source during maintenance without deleting anything.
## Where to go next
- [REST API reference](/develop/api-reference) for every endpoint.
- [SDKs](/develop/sdk) for .NET, JavaScript, and Python.
- [CLI](/develop/cli) for scripting and CI.
- If you would rather not build and maintain a connector at all, [get in touch](https://www.dimescheduler.com) about making yours a standard one.
---
## End-to-end quickstart
This guide walks you through populating a fresh Dime.Scheduler instance from code. By the end, you'll have:
- A handful of **resources** to plan against.
- A **filter group** with **values**, assigned to both resources and tasks so planners can filter by skill, region, or whatever you choose.
- A **category** and a **time marker** to color-code appointments.
- A **job** with a couple of **tasks** under it, with the indicators applied.
The samples below use the [.NET SDK](/develop/sdk/dotnet/install). The same flow works with the [JavaScript SDK](/develop/sdk/js/install), the [CLI](/develop/cli), or the raw [REST API](/develop/api). Order matters: parents (jobs, filter groups, indicators) must exist before their children (tasks, filter values, assignments).
## Try it first
Walk through the same sequence of API calls below, one step at a time. Paste your API key and pick an environment, and each step gives you a ready-to-run `curl` command you can copy straight into your terminal. The panel at the bottom shows what the full flow builds up.
Your key never leaves the browser - the commands are not run from this page, and the responses shown are samples.
The rest of this guide reproduces the exact same flow with the SDKs.
## Prerequisites
- An [API key](/administration/apikeys) for your Dime.Scheduler instance.
- The .NET SDK installed - see [installation](/develop/sdk/dotnet/install).
- A target environment. The samples below default to production; pass `Environment.Sandbox` to the client constructor if you want to use the sandbox first.
## 1. Set up the client
```csharp
using Dime.Scheduler;
using Dime.Scheduler.Entities;
using TaskModel = Dime.Scheduler.Entities.Task;
const string apiKey = "YOUR_API_KEY";
const string sourceApp = "QUICKSTART";
const string sourceType = "DEMO";
var client = new DimeSchedulerClient(apiKey);
```
`SourceApp` and `SourceType` identify where the data came from. Reuse the same pair across every record in this guide so the relationships line up. The `TaskModel` alias avoids the clash with `System.Threading.Tasks.Task`.
Every call below returns a `Result` - check `result.IsFailure` and inspect `result.Error` if you want to handle problems inline. The samples skip that for brevity.
## 2. Create resources
A resource is anyone or anything that gets planned - technicians, sales reps, vehicles, rooms.
```csharp
Resource[] resources =
[
new() { SourceApp = sourceApp, SourceType = sourceType, ResourceNo = "TECH-001", ResourceName = "Alex Doe", DisplayName = "Alex Doe" },
new() { SourceApp = sourceApp, SourceType = sourceType, ResourceNo = "TECH-002", ResourceName = "Jamie Roe", DisplayName = "Jamie Roe" },
new() { SourceApp = sourceApp, SourceType = sourceType, ResourceNo = "TECH-003", ResourceName = "Sam Park", DisplayName = "Sam Park" },
];
await client.Resources.CreateAsync(resources);
```
See the [Resource API reference](/develop/api/resource) for the full field list.
## 3. Create a filter group and values
A filter group is a category planners can filter resources and tasks by - _Skill_, _Region_, _Certification_. Values are the options inside that group.
```csharp
await client.FilterGroups.CreateAsync(new FilterGroup { Name = "Skill" });
FilterValue[] skillValues =
[
new() { Group = "Skill", Value = "HVAC" },
new() { Group = "Skill", Value = "Electrical" },
new() { Group = "Skill", Value = "Plumbing" },
];
await client.FilterValues.CreateAsync(skillValues);
```
See the [Filter API reference](/develop/api/filter) for details on filter groups and values.
## 4. Create a category and a time marker
Categories drive the **background color** of an appointment on the planning board. Time markers drive the **colored bar** below it. Together they let planners see status or type at a glance.
```csharp
await client.Categories.CreateAsync(new Category
{
Name = "INPROGRESS",
DisplayName = "In Progress",
Color = "#6e64b5",
});
await client.TimeMarkers.CreateAsync(new TimeMarker
{
Name = "URGENT",
Color = "#e63946",
});
```
See the [Indicator API reference](/develop/api/indicator) for categories, time markers, and pins.
## 5. Create a job with indicators applied
A job groups tasks - typically a service order, project, or case. Indicators set on the job propagate to the appointments planners create from its tasks.
```csharp
await client.Jobs.CreateAsync(new Job
{
SourceApp = sourceApp,
SourceType = sourceType,
JobNo = "JOB-1001",
ShortDescription = "Quarterly maintenance - Customer XYZ",
Category = "INPROGRESS",
TimeMarker = "URGENT",
});
```
See the [Job API reference](/develop/api/job) for the full field list.
## 6. Create tasks under the job
Tasks are the schedulable units inside a job. A job can have many.
```csharp
TaskModel[] tasks =
[
new()
{
SourceApp = sourceApp,
SourceType = sourceType,
JobNo = "JOB-1001",
TaskNo = "TASK-1",
ShortDescription = "Inspect HVAC unit",
},
new()
{
SourceApp = sourceApp,
SourceType = sourceType,
JobNo = "JOB-1001",
TaskNo = "TASK-2",
ShortDescription = "Replace filters",
},
];
await client.Tasks.CreateAsync(tasks);
```
See the [Task API reference](/develop/api/task).
## 7. Assign filter values to resources
Now wire up which resources are qualified for which work - _Alex_ does HVAC, _Jamie_ handles Electrical, _Sam_ covers Plumbing.
```csharp
ResourceFilterValue[] resourceSkills =
[
new() { ResourceNo = "TECH-001", FilterGroup = "Skill", FilterValue = "HVAC" },
new() { ResourceNo = "TECH-002", FilterGroup = "Skill", FilterValue = "Electrical" },
new() { ResourceNo = "TECH-003", FilterGroup = "Skill", FilterValue = "Plumbing" },
];
await client.ResourceFilterValues.CreateAsync(resourceSkills);
```
## 8. Assign filter values to tasks
Tag each task with the skills required to perform it. Planners can then filter the open tasks list down to work that matches a resource's skills.
```csharp
TaskFilterValue[] taskSkills =
[
new() { SourceApp = sourceApp, SourceType = sourceType, JobNo = "JOB-1001", TaskNo = "TASK-1", FilterGroup = "Skill", FilterValue = "HVAC" },
new() { SourceApp = sourceApp, SourceType = sourceType, JobNo = "JOB-1001", TaskNo = "TASK-2", FilterGroup = "Skill", FilterValue = "HVAC" },
];
await client.TaskFilterValues.CreateAsync(taskSkills);
```
## Verify
Open the planning board and confirm:
- The three resources appear in the resource list.
- The two tasks show up in the [open tasks grid](/user/components/opentasks) under job `JOB-1001`.
- Filtering on `Skill = HVAC` narrows the tasks (and resources) accordingly.
- Drag a task onto a resource - the resulting appointment uses the _In Progress_ background color and shows the _Urgent_ time marker bar.
If anything's missing, check the [transactions view](/administration/transactions) for errors against the records you sent.
## Next steps
- Schedule the tasks programmatically by sending [appointments](/develop/api/appointment).
- Replace the SDK with raw HTTP - see the [REST API reference](/develop/api-reference).
- Push appointment changes back into your own system with the [webhook connector](/integrations/webhook/intro).
---
## Updating the actual locations of resources
Feed live GPS coordinates from tracking devices into Dime.Scheduler so each resource shows up at its real-world location on the map.
The map can display a resource's location from two types of addresses:
- **Static addresses** such as the depot address or the employee's home address
- **Dynamic addresses** as provided by tracking devices, mobile phones, tablets, etc.
Static addresses travel with the `Resource` entity. Dynamic addresses have their own entity, `ResourceGpsTracking`. Any system that can make an HTTP POST request can call this API to push a resource's last known location to the map.
To update a resource's current location, provide three properties:
- The `Resource No`
- The latitude
- The longitude
Location trackers provide the coordinates and an identifier for the tracker, but not the Dime.Scheduler resource number. So you'll need to maintain a list that maps each tracking device to its resource number. Once that mapping is in place, the rest is straightforward.

## Requirements
- The endpoint is `https://api.dimescheduler.com/resourceGpsTracking`, or `https://sandbox.api.dimescheduler.com/resourceGpsTracking` for the sandbox environment.
- You'll need an [API key](/administration/apikeys) to call this endpoint.
- To run the script below, install [HTTPie](https://httpie.io/). We use HTTPie because it's succinct and clean, but any HTTP client or CLI tool such as Postman and cURL will do, as long as it can make HTTP POST requests. Our SDKs and CLI handle these tasks as well.
- Set the map's location mode to "Actual location" in the layers menu at the top-right corner of the map component.
## The script
```powershell
# Set these variables
$key = 'YOUR API KEY GOES HERE'
$resourceNo = 'THE RESOURCE NUMBER GOES HERE'
# Set of coordinates around New York's Central Park
$coordinates = @(
(40.768989, -73.981252),
(40.772393, -73.978810),
(40.775322, -73.976642),
(40.778077, -73.974745),
(40.782615, -73.971368),
(40.787153, -73.967980),
(40.791478, -73.964905),
(40.798254, -73.959996),
(40.799872, -73.958773),
(40.800017, -73.956635),
(40.799102, -73.954424),
(40.797941, -73.951653),
(40.796234, -73.949751),
(40.794612, -73.950820),
(40.793887, -73.951454),
(40.791395, -73.953256),
(40.788493, -73.955364),
(40.786373, -73.956897),
(40.784124, -73.958607),
(40.783633, -73.958905),
(40.781138, -73.960833),
(40.779300, -73.962090),
(40.776249, -73.964257),
(40.772280, -73.967290),
(40.768403, -73.970025),
(40.765863, -73.971855),
(40.765292, -73.975203),
(40.766593, -73.978481),
(40.767629, -73.980935)
)
# Every 5 seconds, the API is invoked
Foreach ($coordinate in $coordinates)
{
$lat = $coordinate[0]
$lng = $coordinate[1]
http -v POST https://sandbox.api.dimescheduler.com/resourceGpsTracking X-API-KEY:$key resourceNo=$resourceNo latitude=$lat longitude=$lng
timeout 5
}
```
Here's what the script does:
- Sets the `$key` and `$resourceNo` variables. You must fill these in, otherwise the script won't run.
- Defines a sample set of coordinates. This example traces a quick run around New York's Central Park.
- Loops through the coordinates, waiting 5 seconds between each iteration.
With HTTPie's minimal syntax, a single line updates the location:
```powershell
http -v POST https://sandbox.api.dimescheduler.com/resourceGpsTracking X-API-KEY:$key resourceNo=$resourceNo latitude=$lat longitude=$lng
```
The script makes an HTTP POST request to `https://sandbox.api.dimescheduler.com/resourceGpsTracking` with four data fields:
- `X-API-KEY` is added to the headers and is required. Without it, the request returns a 401 error.
- `resourceNo`, `latitude` and `longitude` are passed in the request body as a JSON object:
```json
{
"ResourceNo": "YOURRESOURCENO",
"Latitude": YOURLATITUDE,
"Longitude": YOURLONGITUDE,
}
```
After the command runs, Dime.Scheduler pushes the update to open browser sessions as well.
---
## Development
import { FaCode, FaCubes, FaTerminal, FaPlug } from 'react-icons/fa';
import { LandingHero, CardGrid, Card, Note, CTA } from '@site/src/components/LandingKit';
}
featured
/>
}
/>
}
/>
}
/>
Everything in Dime.Scheduler is built around **the public REST API**. The .NET and JavaScript **SDKs**, the **CLI**, and the standard **Business Central**, **NAV**, **Power Platform**, and **Zapier** connectors are all wrappers around it. Whichever entry point you pick, you're hitting the same endpoints, so you can mix and match, or drop down to raw HTTP whenever you need to.
---
## MCP
:::info[This is the documentation MCP server]
This page is about an MCP server for **this documentation site** - it lets an AI agent search and read these docs. It is **not** the MCP server for the Dime.Scheduler application. To let an agent work with your planning data (tasks, appointments, resources), see the [application MCP server](/integrations/mcp/intro) instead.
:::
Point an AI agent at the Dime.Scheduler documentation and let it search and read the pages directly. The docs are exposed as a Model Context Protocol (MCP) server, so agents call tools instead of scraping HTML.
The endpoint is:
```text
https://docs.dimescheduler.com/api/mcp
```
It exposes two tools:
| Tool | Purpose |
| --- | --- |
| `docs_search` | Full-text search across all documentation pages. Returns titles, URLs and snippets ranked by relevance. |
| `docs_fetch` | Retrieve the full markdown of a specific page by URL. |
The server runs in **stateless mode** - every request is independent, no session is needed.
## Add to Claude Code
```sh
claude mcp add --transport http dimescheduler-docs https://docs.dimescheduler.com/api/mcp
```
In any Claude Code session, ask Claude to search the Dime.Scheduler docs and it will call the MCP tools automatically.
## Other MCP clients
The endpoint follows the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) from the MCP specification. Any MCP-compatible client can connect: point it at the URL above.
## Test with curl
List the available tools:
```sh
curl -X POST https://docs.dimescheduler.com/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
Run a search:
```sh
curl -X POST https://docs.dimescheduler.com/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"docs_search","arguments":{"query":"business central setup","limit":3}}}'
```
A `GET` request returns a status object - useful as a liveness check. The search index loads lazily on the first `POST`, so a fresh `GET` will report `initialized: false` until a real MCP call has been made against the same instance.
---
## Data model
Use the diagrams below to see how the core Dime.Scheduler entities fit together. Each one is a simplified entity-relationship view, showing the key fields and the relationships you'll work with when integrating.
## Explore the model
Select an entity to see its key fields, how it connects to the rest of the model, and a jump to its API reference.
## Entity-relationship diagrams
The diagrams below give the same model in formal entity-relationship notation, with cardinality and the full field set.
### Jobs, tasks and appointments
```mermaid
erDiagram
JOB {
string JobNo
string SourceApp
string SourceType
string Description
string CustomerNo
string SiteNo
string ContactNo
string FreeText1
DateTime FreeDate1
bool FreeBit1
decimal FreeDecimal1
}
TASK {
string TaskNo
string JobNo
string SourceApp
string SourceType
string ServiceNo
string ContractNo
string FreeText1
DateTime FreeDate1
bool FreeBit1
decimal FreeDecimal1
bool IsOpenTask
}
APPOINTMENT {
DateTime Start
DateTime End
string Subject
string Body
int CategoryId
int TimeMarkerId
}
RESOURCE {
string Name
string Email
bool GpsTrackingEnabled
bool DoNotShow
bool ExchangeIntegrationEnabled
}
ASSIGNMENT {
long AppointmentId
int ResourceId
}
APPOINTMENT ||--o{ ASSIGNMENT : "has assigned resources"
ASSIGNMENT ||--o{ RESOURCE : "assigns"
JOB ||--|{ TASK : has
JOB ||--o{ CATEGORY : has
JOB ||--o{ TIME-MARKER : has
TASK ||--o{ CATEGORY : has
TASK ||--o{ TIME-MARKER : has
TASK ||--o{ APPOINTMENT : has
APPOINTMENT ||--o{ CATEGORY : has
APPOINTMENT ||--o{ TIME-MARKER : has
```
### Filter groups and values
```mermaid
erDiagram
FILTER-GROUP ||--o{ FILTER-VALUE : contains
FILTER-GROUP {
string Name
}
FILTER-VALUE {
string Value
int GroupId
}
FILTER-VALUE ||--|{ RESOURCE-FILTER-VALUE : assigned
FILTER-VALUE ||--|{ TASK-FILTER-VALUE : assigned
TASK-FILTER-VALUE o{--|| TASK : requirement
RESOURCE-FILTER-VALUE o{--|| RESOURCE : qualification
```
### Task container
```mermaid
erDiagram
TASK {
string Name
}
TASK-CONTAINER {
string Value
int GroupId
}
CONTAINER {
string Name
}
TASK ||--o{ TASK-CONTAINER : contains
TASK-CONTAINER ||--|{ CONTAINER : has
```
## Read more
- [API overview](/develop/api)
- [Jobs](/develop/api/job)
- [Tasks](/develop/api/task)
- [Resources](/develop/api/resource)
- [Free fields](/develop/api/global/freefield)
---
## About the SDK
Dime.Scheduler ships official SDKs in three languages. All of them live in the [`dime-scheduler/sdk`](https://github.com/dime-scheduler/sdk) monorepo, all are generated from the same [OpenAPI specification](https://github.com/dime-scheduler/sdk/blob/main/spec/openapi.json), and all expose the same surface - only the casing and idioms change.
| Language | Package | Status |
| -------- | ------- | ------ |
| .NET | [`Dime.Scheduler`](https://www.nuget.org/packages/Dime.Scheduler) on NuGet | Stable |
| JavaScript / TypeScript | [`@dimescheduler/sdk`](https://www.npmjs.com/package/@dimescheduler/sdk) on npm | Stable |
| Python | [`dimescheduler`](https://pypi.org/project/dimescheduler/) on PyPI | Alpha |
The [`dimescheduler` CLI](../cli/intro.md) is generated from the same OpenAPI specification, so its commands stay in lockstep with the SDKs.
## Shape
Every SDK exposes a single `DimeSchedulerClient` constructed with an API key and (optionally) an environment. Each entity hangs off a typed accessor:
```ts
// JavaScript / TypeScript
const client = new DimeSchedulerClient({ apiKey: "MY_API_KEY" });
await client.categories.create({ name: "VIP", color: "#22d3ee" });
```
```csharp
// .NET
var client = new DimeSchedulerClient("MY_API_KEY");
await client.Categories.CreateAsync(new Category { Name = "VIP", Color = "#22d3ee" });
```
```python
# Python
client = DimeSchedulerClient(api_key="MY_API_KEY")
client.categories.create({"name": "VIP", "color": "#22d3ee"})
```
There are two kinds of accessor:
- **CRUD entities** - about thirty of them - expose `create` / `update` / `delete` / `getAll` on a generic accessor. Both single entities and batches work.
- **Specialized endpoints** (appointments date-range queries, geocoding, optimization, recommendation, notifications, messages, …) get their own methods.
Pick a language and follow the install, usage, and examples pages on the left. If you already know one SDK, the other two read the same way: the accessor names are kept in lockstep across all three.
## When to use the SDK vs the CLI vs raw HTTP
- **SDK** - embedding in an application (web app, integration service, scheduled worker). You get typed entities, retries, and per-language ergonomics.
- **[CLI](../cli/intro.md)** - scripts, CI jobs, one-off batch loads. Same accessor model, but you script it from a shell.
- **[Raw HTTP](/develop/api)** - when neither fits, or you're working in a language without an official SDK. The API is documented in the [reference](/develop/api).
---
## Examples
These end-to-end snippets show the most common tasks you'll run with the .NET SDK. The flow is the same as in the [JS](../js/example.md) and [Python](../python/example.md) SDKs:
1. Build the typed entity.
2. Call the matching accessor on the client.
3. Branch on `result.IsSuccess` / `result.IsFailure`.
## Create a category
```csharp
using Dime.Scheduler;
using Dime.Scheduler.Entities;
var client = new DimeSchedulerClient("MY_API_KEY", Environment.Sandbox);
var category = new Category { Name = "VIP", Color = "#22d3ee" };
var result = await client.Categories.CreateAsync(category);
if (result.IsFailure)
throw new InvalidOperationException(result.Error?.ToString());
```
## Create a job and its first task
A task must belong to a job - send the job first, then the task:
```csharp
const string sourceApp = "BC_CRONUS";
const string sourceType = "SERVICE";
const string jobNo = "SO001";
await client.Jobs.CreateAsync(new Job
{
SourceApp = sourceApp,
SourceType = sourceType,
JobNo = jobNo,
ShortDescription = "Repair HVAC at HQ London",
});
await client.Tasks.CreateAsync(new Dime.Scheduler.Entities.Task
{
SourceApp = sourceApp,
SourceType = sourceType,
JobNo = jobNo,
TaskNo = "SO001_LINE10",
ShortDescription = "Replace compressor",
});
```
Need many at once? Pass an `IEnumerable`:
```csharp
await client.Tasks.CreateAsync(new[] { task1, task2, task3 });
```
## Update the live location of a resource
```csharp
var location = new ResourceGpsTracking
{
ResourceNo = "GEOFFREY",
Latitude = 52.372746m,
Longitude = 4.893086m,
};
await client.ResourceGpsTrackings.CreateAsync(location);
```
## Read appointments for a date range
```csharp
Result> result = await client.Appointments.GetAsync(
new DateTime(2026, 5, 1, 0, 0, 0, DateTimeKind.Utc),
new DateTime(2026, 5, 31, 23, 59, 59, DateTimeKind.Utc),
new[] { "GEOFFREY", "JOHNNY" });
foreach (var appointment in result.Value ?? Array.Empty())
{
Console.WriteLine($"{appointment.AppointmentNo} {appointment.StartDate}");
}
```
## Send a fly-in message to online users
```csharp
await client.Messages.PostAsync(new Message
{
Text = "Heads up - planning board reload in 5 minutes.",
Severity = Severity.Warning,
});
```
## Lock an appointment
The narrow `AppointmentLocked` accessor changes only the lock state - no need to re-send the whole appointment:
```csharp
await client.AppointmentLocked.CreateAsync(new AppointmentLocked
{
SourceApp = "BC_CRONUS",
SourceType = "SERVICE",
AppointmentGuid = Guid.Parse("2a77f6c6-c502-4ad2-9ee2-6fba0f9361a1"),
Locked = true,
});
```
More examples live alongside the source in the [`dime-scheduler/sdk`](https://github.com/dime-scheduler/sdk/tree/main/packages/dotnet/tests) repo.
---
## Installing the SDK
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
[](https://www.nuget.org/packages/Dime.Scheduler)
Add the Dime.Scheduler .NET SDK to your project to start working with the API in code. The SDK is published to NuGet as [`Dime.Scheduler`](https://www.nuget.org/packages/Dime.Scheduler) and targets `net8.0`, `net9.0`, and `net10.0`. Pick the install method that suits your setup:
```bash
dotnet add package Dime.Scheduler
```
```xml
```
## Prereleases
Alpha, beta, and release-candidate builds (e.g. `5.0.1-beta.0`) ship as NuGet prereleases. Opt in with `--prerelease`:
```bash
dotnet add package Dime.Scheduler --prerelease
```
## Get an API key
The SDK needs an [API key](/administration/apikeys) to authenticate. Grab one from your environment:
- [Production](https://app.dimescheduler.com/keys)
- [Sandbox](https://sandbox.app.dimescheduler.com/keys)
---
## Using the SDK
This page walks you through the everyday workflow with the .NET SDK: create a client, call typed accessors against the Dime.Scheduler REST API, read results, and tune retries and cancellation.
## Creating a client
`DimeSchedulerClient` is the single entry point. It needs an API key and optionally an environment:
```csharp
using Dime.Scheduler;
using Dime.Scheduler.Entities;
// Production (default)
var client = new DimeSchedulerClient("MY_API_KEY");
// Sandbox
var client = new DimeSchedulerClient("MY_API_KEY", Environment.Sandbox);
// Custom retry policy
var client = new DimeSchedulerClient("MY_API_KEY", Environment.Sandbox, RetryOptions.Default);
```
`DimeSchedulerClient` is thread-safe and meant to be long-lived - instantiate it once at startup (e.g. as a singleton in your DI container) and reuse it.
## Domain accessors
Every entity hangs off a typed accessor on the client. The accessor list mirrors the [JS](../js/usage.md) and [Python](../python/usage.md) SDKs 1:1 - same names, idiomatic casing per language:
```csharp
client.Categories // /category
client.Appointments // /appointment
client.Jobs // /job
client.Tasks // /task
client.Resources // /resource
client.FilterGroups // /filterGroup
client.FilterValues // /filterValue
client.Messages // /message
client.Notifications // /notification
```
CRUD-shaped entities expose `CreateAsync`, `UpdateAsync`, `DeleteAsync`, and `GetAllAsync()`. All write methods accept either a single entity or an `IEnumerable`:
```csharp
await client.Categories.CreateAsync(new Category { Name = "VIP", Color = "#22d3ee" });
await client.Categories.CreateAsync(new[] { cat1, cat2, cat3 });
await client.Categories.UpdateAsync(category);
await client.Categories.DeleteAsync(category);
Result> categories = await client.Categories.GetAllAsync();
```
Endpoints with their own shape get purpose-built methods:
```csharp
await client.Appointments.GetAsync(startDate, endDate, new[] { "R1", "R2" });
await client.Notifications.GetAsync(page: 1, limit: 50, sort: "createdAt:desc");
await client.Messages.PostAsync(new Message { Text = "Heads up!", Severity = Severity.Warning });
await client.Geocoding.GeocodeAsync(address);
await client.Optimization.FieldServiceAsync(request);
```
For the full accessor list, see [`DimeSchedulerClient.cs`](https://github.com/dime-scheduler/sdk/blob/main/packages/dotnet/src/DimeSchedulerClient.cs).
## Results
Every call returns a `Result`:
```csharp
var result = await client.Categories.CreateAsync(category);
result.IsSuccess // bool
result.IsFailure // !IsSuccess
result.Value // the payload on success
result.Error // typed Error on failure
```
The SDK never throws on a non-2xx - you decide how to react. A typical guard:
```csharp
var result = await client.Categories.CreateAsync(category);
if (result.IsFailure)
{
logger.LogWarning("Create failed: {Error}", result.Error);
return;
}
```
## Retries
Transient failures retry by default - connection errors, timeouts, and HTTP `408 / 429 / 500 / 502 / 503 / 504`. Backoff is exponential with jitter and honours `Retry-After`.
Override the policy with `RetryOptions`:
```csharp
var client = new DimeSchedulerClient(
"MY_API_KEY",
Environment.Production,
new RetryOptions(maxRetries: 5, initialDelay: TimeSpan.FromSeconds(1)));
```
Pass `RetryOptions.Disabled` to turn retries off entirely.
## Cancellation
Every async method accepts a `CancellationToken` as the last argument:
```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await client.Imports.RunAsync(payload, cts.Token);
```
This is on top of the SDK's built-in per-request timeout - use it when you need to abort an in-flight call from the outside (a user closing a UI, a long-running batch job hitting a deadline).
---
## Examples(Js)
These examples show how to handle common tasks with the JavaScript and TypeScript SDK. The flow is the same as in the [.NET](../dotnet/example.md) and [Python](../python/example.md) SDKs:
1. Build the typed entity (or array of entities).
2. Call the matching accessor on the client.
3. Branch on `response.ok` / `error`.
## Create a category
```ts
import DimeSchedulerClient, { Environment } from "@dimescheduler/sdk";
import type { Category } from "@dimescheduler/sdk/models";
const client = new DimeSchedulerClient({
apiKey: "MY_API_KEY",
environment: Environment.Sandbox,
});
const category: Category = { name: "VIP", color: "#22d3ee" };
const { data, error } = await client.categories.create(category);
if (error) throw new Error(JSON.stringify(error));
```
## Create a job and its first task
Every task belongs to a job, so send the job first, then the task:
```ts
import type { Job, Task } from "@dimescheduler/sdk/models";
const sourceApp = "BC_CRONUS";
const sourceType = "SERVICE";
const jobNo = "SO001";
const job: Job = {
sourceApp,
sourceType,
jobNo,
shortDescription: "Repair HVAC at HQ London",
};
await client.jobs.create(job);
const task: Task = {
sourceApp,
sourceType,
jobNo,
taskNo: "SO001_LINE10",
shortDescription: "Replace compressor",
};
await client.tasks.create(task);
```
To create several at once, pass an array:
```ts
await client.tasks.create([task1, task2, task3]);
```
## Update the live location of a resource
```ts
await client.resourceGpsTrackings.create({
resourceNo: "GEOFFREY",
latitude: 52.372746,
longitude: 4.893086,
});
```
## Read appointments for a date range
```ts
const { data } = await client.appointments.get(
new Date("2026-05-01T00:00:00Z"),
new Date("2026-05-31T23:59:59Z"),
["GEOFFREY", "JOHNNY"],
);
for (const a of data ?? []) {
console.log(a.appointmentNo, a.startDate);
}
```
## Send a fly-in message to online users
```ts
await client.messages.send({
text: "Heads up - planning board reload in 5 minutes.",
severity: "Warning",
});
```
## Lock an appointment
The narrow `appointmentLocked` accessor changes only the lock state:
```ts
await client.appointmentLocked.create({
sourceApp: "BC_CRONUS",
sourceType: "SERVICE",
appointmentGuid: "2a77f6c6-c502-4ad2-9ee2-6fba0f9361a1",
locked: true,
});
```
You'll find more examples alongside the source in the [`dime-scheduler/sdk`](https://github.com/dime-scheduler/sdk/tree/main/packages/js/test) repo.
---
## Installing the SDK(Js)
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
[](https://www.npmjs.com/package/@dimescheduler/sdk)
Add the JS SDK to your project to start scripting against Dime.Scheduler. It is published to npm as [`@dimescheduler/sdk`](https://www.npmjs.com/package/@dimescheduler/sdk), ships dual CJS + ESM builds, and includes TypeScript types.
Install it with your package manager of choice:
```bash
npm install @dimescheduler/sdk
```
```bash
yarn add @dimescheduler/sdk
```
```bash
pnpm add @dimescheduler/sdk
```
```bash
bun i @dimescheduler/sdk
```
## Prereleases
Alpha, beta, and release-candidate builds (e.g. `0.7.1-beta.0`) ship to the `next` dist-tag. To opt in, append `@next`:
```bash
npm install @dimescheduler/sdk@next
```
## Get an API key
The SDK authenticates with an [API key](/administration/apikeys). Generate one for the environment you are targeting:
- [Production](https://app.dimescheduler.com/keys)
- [Sandbox](https://sandbox.app.dimescheduler.com/keys)
---
## Using the SDK(Js)
## Creating a client
`DimeSchedulerClient` is the single entry point. The constructor takes an options object:
```ts
import DimeSchedulerClient, { Environment } from "@dimescheduler/sdk";
const client = new DimeSchedulerClient({ apiKey: "MY_API_KEY" });
// Sandbox
const sandbox = new DimeSchedulerClient({
apiKey: "MY_API_KEY",
environment: Environment.Sandbox,
});
```
The default environment is production. The `Environment` enum is a string enum where each value **is** the base URL, so you can pass any URL to point at a mock or a self-hosted instance:
```ts
const local = new DimeSchedulerClient({
apiKey: "test",
environment: "http://127.0.0.1:4010" as Environment,
});
```
Re-use a single client across your app - it owns a fetch instance and a retry policy.
## Domain accessors
Every entity hangs off a typed accessor on the client. Names mirror the [.NET](../dotnet/usage.md) and [Python](../python/usage.md) SDKs 1:1, in idiomatic `camelCase`:
```ts
client.categories // /category
client.appointments // /appointment
client.jobs // /job
client.tasks // /task
client.resources // /resource
client.filterGroups // /filterGroup
client.filterValues // /filterValue
client.messages // /message
client.notifications // /notification
```
CRUD-shaped entities expose `create`, `update`, `delete`, and `getAll`. Write methods accept either a single object or an array:
```ts
await client.categories.create(category);
await client.categories.create([cat1, cat2, cat3]);
await client.categories.update(category);
await client.categories.delete(category);
const { data } = await client.categories.getAll();
```
Endpoints with their own shape get purpose-built methods:
```ts
await client.appointments.get(startDate, endDate, ["R1", "R2"]);
await client.notifications.get(1, 50, { sort: "createdAt:desc" });
await client.messages.send({ text: "Heads up!", severity: "Warning" });
await client.geocoding.geocodeText("221B Baker Street", "GB");
await client.optimization.fieldService(request);
```
For the full accessor list, see [`client.ts`](https://github.com/dime-scheduler/sdk/blob/main/packages/js/src/client.ts).
## Models and types
Body and response types live under `@dimescheduler/sdk/models`. Write entities (`Category`, `Resource`, `Appointment`) match POST/PUT bodies; read DTOs (`CategoryDto`, `ResourceDto`) match GET responses:
```ts
import type { Category, CategoryDto } from "@dimescheduler/sdk/models";
```
For schemas without a re-export, every type is reachable via the generated `components["schemas"]` map exported from the package root:
```ts
import type { components } from "@dimescheduler/sdk";
type StoredProcedureData = components["schemas"]["Models.StoredProcedureData"];
```
## Results
Every call returns `{ data, error, response }` - the same shape as [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/), which the SDK is built on:
```ts
const { data, error, response } = await client.categories.getAll();
response.ok // boolean - HTTP 2xx
response.status // status code
data // typed body on success
error // typed error body on failure
```
The SDK never throws on a non-2xx - branch on `response.ok` (or `error`) and decide how to react:
```ts
const { data, error } = await client.categories.create(category);
if (error) {
logger.warn("Create failed", error);
return;
}
```
## Retries
Transient failures retry by default - network errors and HTTP `408 / 429 / 500 / 502 / 503 / 504`. Backoff is exponential with jitter and honours `Retry-After`.
Override the policy via `retry`:
```ts
const client = new DimeSchedulerClient({
apiKey: "MY_API_KEY",
retry: { maxRetries: 5, initialDelayMs: 1000, maxDelayMs: 60000 },
});
```
Pass `retry: false` to disable retries entirely.
## Middleware
The client is built on `openapi-fetch`, so you can intercept requests and responses:
```ts
client.use({
onRequest({ request }) {
request.headers.set("X-Trace-Id", crypto.randomUUID());
},
onResponse({ response }) {
metrics.observe(response.status);
},
});
```
Use `client.eject(middleware)` to remove a middleware.
## Custom `fetch`
For runtimes that don't ship `fetch`, or when you want to plug in a wrapped fetch (instrumentation, retries, edge runtime helpers), pass `fetch`:
```ts
import nodeFetch from "node-fetch";
const client = new DimeSchedulerClient({
apiKey: "MY_API_KEY",
fetch: nodeFetch as unknown as typeof fetch,
});
```
---
## Examples(Python)
These examples show how to create categories, resources, tasks and appointments with the Python SDK. The flow is the same as in the [.NET](../dotnet/example.md) and [JavaScript](../js/example.md) SDKs:
1. Build a dict (or a list of dicts) for the entity you want to send.
2. Call the matching accessor on the client.
3. Branch on `result.ok` or call `result.raise_for_error()`.
## Create a category
A category drives the background colour of appointments on the planning board.
```python
from dimescheduler import DimeSchedulerClient, Environment
with DimeSchedulerClient(api_key="MY_API_KEY", environment=Environment.Sandbox) as client:
result = client.categories.create({
"name": "INSTALL",
"displayName": "Install",
"color": "#22d3ee",
})
result.raise_for_error()
```
## Update the live location of a resource
After this call, resource `GEOFFREY` shows up in Amsterdam on the map.
```python
result = client.resource_gps_trackings.create({
"resourceNo": "GEOFFREY",
"latitude": 52.372746,
"longitude": 4.893086,
})
```
## Create a job and its first task
A task must belong to a job, so send them in order:
```python
source_app, source_type, job_no = "CRM", "SERVICECASE", "CASE_123"
client.jobs.create({
"sourceApp": source_app,
"sourceType": source_type,
"jobNo": job_no,
"shortDescription": "Repair HVAC in Berlin office",
})
client.tasks.create({
"sourceApp": source_app,
"sourceType": source_type,
"jobNo": job_no,
"taskNo": "CASE_123_ACTIVITY001",
"shortDescription": "Repair HVAC",
})
```
Need to send many at once? Pass a list. Most CRUD endpoints accept either a single entity or a sequence:
```python
client.tasks.create([task_1, task_2, task_3])
```
## Read appointments for a date range
```python
from datetime import datetime, timezone
result = client.appointments.get(
start_date=datetime(2026, 5, 1, tzinfo=timezone.utc),
end_date=datetime(2026, 5, 31, tzinfo=timezone.utc),
resources=["GEOFFREY", "JOHNNY"],
)
for appointment in result.data or []:
print(appointment["appointmentNo"], appointment["startDate"])
```
## Send a fly-in message to online users
```python
result = client.messages.send({
"text": "Heads up - planning board reload in 5 minutes.",
"severity": "Warning",
})
```
## Handle rate limits explicitly
```python
import time
from dimescheduler import RateLimitError
for attempt in range(5):
result = client.categories.create(category)
if result.ok:
break
err = result.to_error()
if isinstance(err, RateLimitError):
time.sleep(err.retry_after or 2 ** attempt)
continue
result.raise_for_error() # any other failure: bail
```
More examples live alongside the source in the [`dime-scheduler/sdk`](https://github.com/dime-scheduler/sdk) monorepo.
---
## Installing the SDK(Python)
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
[](https://pypi.org/project/dimescheduler/)
Add the Python SDK to your project to call the Dime.Scheduler API from Python. It ships on PyPI as [`dimescheduler`](https://pypi.org/project/dimescheduler/), needs Python 3.9 or newer, and depends only on [`httpx`](https://www.python-httpx.org). Pick your package manager below and run the command.
```bash
pip install dimescheduler
```
```bash
uv add dimescheduler
```
```bash
poetry add dimescheduler
```
:::warning[Status: alpha]
The SDK is currently in alpha. Builds tagged `0.1.x` are prereleases, so package managers won't pick them up unless you opt in:
```bash
pip install --pre dimescheduler
```
:::
With the package installed, the last thing you need is an [API key](/administration/apikeys):
- [Production](https://app.dimescheduler.com/keys)
- [Sandbox](https://sandbox.app.dimescheduler.com/keys)
---
## Using the SDK(Python)
## Creating a client
`DimeSchedulerClient` is the single entry point. It needs an API key and, optionally, an environment:
```python
from dimescheduler import DimeSchedulerClient, Environment
client = DimeSchedulerClient(api_key="MY_API_KEY") # production
client = DimeSchedulerClient(api_key="MY_API_KEY", environment=Environment.Sandbox)
```
The client owns an `httpx.Client` under the hood and should be closed when you're done. Use it as a context manager so the connection pool is released for you automatically:
```python
with DimeSchedulerClient(api_key="MY_API_KEY") as client:
client.categories.create({"name": "INSTALL", "displayName": "Install", "color": "#22d3ee"})
```
For long-lived processes (web apps, workers) instantiate the client once at startup and reuse it across requests.
## Domain-grouped accessors
Every entity hangs off a typed accessor on the client. The naming mirrors the [.NET](../dotnet/usage.md) and [JavaScript](../js/usage.md) SDKs 1:1, with idiomatic `snake_case`:
```python
client.categories # /category
client.appointments # /appointment
client.jobs # /job
client.tasks # /task
client.resources # /resource
client.filter_groups # /filterGroup
client.filter_values # /filterValue
client.messages # /message
client.notifications # /notification
```
CRUD-shaped entities (most of them) expose `create` / `update` / `delete` / `get_all`:
```python
client.categories.create(category)
client.categories.update(category)
client.categories.delete(category)
client.categories.get_all()
```
Endpoints with their own shape get purpose-built methods:
```python
client.appointments.get(start_date, end_date, resources=["R1", "R2"])
client.notifications.get(page=1, limit=50, sort="createdAt:desc")
client.geocoding.geocode_text("221B Baker Street", "GB")
client.optimization.field_service(request)
client.messages.send({"text": "Heads up!", "severity": "Warning"})
```
For the full list of accessors, see the [API reference](/develop/api) or the [client source](https://github.com/dime-scheduler/sdk/blob/main/packages/python/src/client.py).
## Results, not exceptions
Every call returns a `Result`:
```python
result = client.categories.get_all()
result.ok # True on 2xx
result.data # parsed JSON body on success, else None
result.error # parsed JSON error body on 4xx/5xx, else None
result.response # underlying httpx.Response - headers, status code, raw bytes
```
The dispatcher never raises on non-2xx - you decide how to react. If you'd rather work with exceptions, call `raise_for_error()`:
```python
from dimescheduler import RateLimitError, ValidationError
result = client.categories.create(category)
try:
result.raise_for_error()
except RateLimitError as e:
time.sleep(e.retry_after or 1)
except ValidationError as e:
log.warning("bad payload: %s", e.body)
```
The typed exception hierarchy covers `ValidationError` (400/422), `AuthenticationError` (401), `AuthorizationError` (403), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `ServerError` (5xx), and `NetworkError` (transport failures).
## Retries
The SDK retries transient failures by default: connection errors, timeouts, and HTTP `408 / 429 / 500 / 502 / 503 / 504`. Backoff is exponential with full jitter, capped at 30 seconds, and `Retry-After` is honoured.
Override the policy via `RetryConfig`:
```python
from dimescheduler import DimeSchedulerClient, RetryConfig
client = DimeSchedulerClient(
api_key="MY_API_KEY",
retry=RetryConfig(max_retries=5, initial_delay_s=1.0, max_delay_s=60.0),
)
```
Disable retries entirely with `retry=False`.
## Timeouts and cancellation
The client-level timeout (default 30 s) applies to every request. Override it for a single call:
```python
client.imports.run(payload, timeout=120.0) # 120 s
client.imports.run(payload, timeout=httpx.Timeout(60.0, connect=5.0))
client.imports.run(payload, timeout=None) # wait indefinitely
```
Omit `timeout` to inherit the client-level default.
---
## Back Office Connector
## Appointment status
The back office connector pushes appointments from Dime.Scheduler to the back office, and plenty can go wrong along the way: a server might be down or unreachable, blocked by a firewall, or running an incompatible software version. To make diagnosis easier, the Dime.Scheduler Back Office connector tags each appointment with a status code that points to the kind of problem it hit:
| Status | Description |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Success | The record has been sent successfully to the back office |
| MBOC Publish Disabled | The connector is disabled - all records with this connector will be ignored. |
| MBOC Not Provided | The appointment is not linked to a task and job (which links to the MBOC setting) |
| Invalid MBOC Back Office System | The back office system type (e.g. CRM, NAV, BC) is not set. |
| Invalid MBOC Back Office Type | The back office system type is not recognized or valid. |
| Invalid MBOC URI | The connector's web service URI is empty. |
| Skipped | Set when an appointment is valid but not processed by the back office plugin, for example when an appointment has been processed with a later timestamp. |
| Failed | An unhandled exception occurred. |
Most of these errors come from the MBOC setup. To resolve them, make sure the data is configured correctly. See the [connector setup guide](administration/connectors.md) for details.
Once the setup is correct, the only status you should expect to see is "**Failed**".
The exception's stack trace is recorded in the logs and in the details view of the web application, so you (and later, Dime.Scheduler's support team) can analyze the problem. For example, an error that starts with `System.Web.Services.Protocols.SoapException: DifferentTableIdsMessage` already hints at the cause: here it points to an invalid mapping of a code unit.
One caveat about the "**Success**" status: it only means the back office received the record. The record may still be invalid - only the business logic inside the back office system can determine that.
## Administration portal
The Dime.Scheduler Back Office Plugin website lets you monitor all communication between Dime.Scheduler and the Dime.Scheduler Back Office Plugin (connector).
It is usually installed on the same web server as the Dime.Scheduler Client application, but on a different port number. Check with your system administrator for the correct URL.

The website sits as a façade, or management application, between Dime.Scheduler and the back office, and shows the status of the connection between the two. It gives the administrator an overview of the appointments sent to the back office and a way to step in when an error occurs.
### Authentication
This plugin uses a simple authentication and authorization mechanism. Local forms accounts secure the system.
### Views
The website helps administrators monitor the connection between Dime.Scheduler and the back office. It is a management and reporting tool that supports diagnosing issues and provides the means to resolve them.
#### Appointments
The appointments view lists the appointments processed by the back office plugin. Here you can review their properties along with the status of each transaction.

By default, records are sorted in descending order by the date of the last transaction. To change the sort order, click any column header.
To find a specific value (for example, all transactions for a given job), click the filter icon and enter your filter criteria. Click the filter icon again to clear it.
Click the magnify button in the first column to open the details section.

The details section shows change tracking information and identification values for the back office application.
Below this, the 'last activity' tab lists the transactions and their status. The MBOC Settings tab shows the connection details used to reach the back office application.
#### Transactions
The transactions view is similar to the appointments view, except it shows a flat list rather than a table grouped per appointment.
#### Error Tracking
Error Tracking shows transactions that were not sent successfully to the back office application. You can sort and filter the records as in the History list. In the details section, you can inspect the reason for the error and, if necessary, change the MBOC Settings before resending the transaction to the back office application.

To act on many records at once, select them and use the "Archive All" or "Retry All" buttons to archive or resend the transactions to the back office application.
#### Logs
The logging view shows every message logged by the Dime.Scheduler Back Office application, which helps you spot issues.

#### Status
The Status screen, reachable via the link at the bottom-right of any page, indicates the state of the connection with the Dime.Scheduler Back Office Plugin Windows Service.

### Automatic retry
When a transaction fails for any reason, it appears in the error list automatically. From there, you can retry it manually and adjust the MBOC settings if needed. Since Dime.Scheduler 2020.1.0, a timer job can do this for you: it queries every transaction with the 'Failed' status and runs it again.
This is an opt-in feature, so the CRON expression is empty by default. To enable it, open the `appsettings.json` file of the back office website and find the `RetryJobCronExpression` configuration item. Its value is a CRON expression. For example, `"*/15 * * * *"` means "[At every 15th minute](https://crontab.guru/every-15-minutes)". Any value that follows the CRON syntax works here.
### The Back Office website is optional
The back office plugin has two parts:
- The component that sends messages from Dime.Scheduler to the back office.
- The reporting dashboard.
The first is the Back Office Service, implemented as a Windows Service that continuously watches for new transactions in Dime.Scheduler's client application. The second is the Back Office Website.
To integrate Dime.Scheduler with the back office, you only need the Windows Service. The website is an optional feature, free of charge, for the benefit of Dime.Scheduler's administrators.
---
## Appointments(Bc)
When a planner drops a job onto a resource in Dime.Scheduler, that decision has to make its way back into Business Central so the rest of the business (invoicing, service, project posting) can act on it. The Appointments table is where those appointments land. Every time a planner schedules work, the appointment is posted here as a staging record, which is why this table sits at the heart of the Business Central integration: it is the inbox where planning turns into BC data.

The list carries a single action, "Process", and most of the time you can ignore it. Built-in event listeners pick up new records and process them automatically, so reach for the button only when you need to retry an appointment that failed to process the first time. For the mechanics of how that automatic processing is wired up, see [Source types](/integrations/bc/sourcetypes).
## Card
Open any record from the list to see its card, which lays out the full detail of a single appointment:

The fields are organized into groups so you can read a record top to bottom: the **Appointment** group holds the identity and content of the scheduled item, **Planning Details** captures how the work translates into quantities and durations, **Back Office** records where the appointment came from and whether it originated in BC, and **Change Tracking** is the audit trail. The full list:
| Group | Field |
| ---------------- | ------------------------- |
| Appointment | Entry Number |
| Appointment | Source Type |
| Appointment | Source App |
| Appointment | Database Action |
| Appointment | Job No |
| Appointment | Task No |
| Appointment | Appointment ID |
| Appointment | Subject |
| Appointment | Body |
| Appointment | Start Date |
| Appointment | End Date |
| Appointment | Time Marker |
| Appointment | Category |
| Appointment | Importance |
| Appointment | Is All Day Event |
| Planning Details | Duration (Sec.) |
| Planning Details | Non Working Time (Sec.) |
| Planning Details | Planning Quantity |
| Planning Details | Planning Unit Of Measure |
| Planning Details | Planning UoM Conversion |
| Planning Details | Use Fix Planning Quantity |
| Planning Details | Round to Unit Of Measure |
| Back Office | Back Office Id |
| Back Office | Back Office Parent Id |
| Back Office | Sent from Back Office |
| Change Tracking | Last Modified Date |
| Change Tracking | Last Modified User |
| Change Tracking | Created Date |
| Change Tracking | Created user |
Two subgrids round out the card: one lists the **resources** assigned to the appointment, the other lists the **field values** attached to it. Together they mirror exactly what you would see on the appointment card in Dime.Scheduler, so the card is a faithful BC-side view of the planned work rather than a stripped-down copy.
## Resources
An appointment resource (also known as an assignment) is simply a resource assigned to an appointment. When a planner books two technicians onto the same job, you see two rows here. This subgrid is where that relationship is spelled out:

The following fields are available:
- Entry No
- Line No
- Source Type
- Resource Type
- Resource No
- Resource Name
- Display Name
- Alternative Resources
## Field Values
An appointment field value holds the value of the custom fields and templates you have defined in Dime.Scheduler, so this is where any extra, configuration-specific data on an appointment surfaces in BC. The field values subgrid covers that relationship:

The following fields are available:
- Entry No
- Line No
- Field code
- Field Type
- Field Value
---
## Configuring the connector
Installation answers *which* Dime.Scheduler this Business Central talks to. Modules answer *what* travels between them: jobs, service orders, production, and the rest. This section answers a third question that sits underneath both: **how the connector behaves while it does that work.**
These settings are global. They do not belong to any one module, they apply to every piece of data the connector moves, and none of them are required to get a connection running. The FastTrack Wizard deliberately leaves them alone so that a first setup stays short. For anything running on real data, they are where you tune the integration to fit how your organization actually works.
There are three decisions to make, and they are independent of one another.
## What happens when things go wrong
The connector moves data between two systems that each have their own rules, and sometimes those rules disagree: an order is deleted in BC while planning still exists for it in Dime.Scheduler, or an appointment arrives from Dime.Scheduler that BC cannot process because a resource is missing a unit of measure.
Left alone, both situations fail quietly. Two switches on the **Dime.Scheduler Setup** page change that: one blocks deletions that would destroy planning, the other pushes BC's processing errors back to Dime.Scheduler so planners see them where they are already working. Both are covered in [safeguards and notifications](/integrations/bc/config/safeguards).
## Keeping master data fresh
The wizard pushes master data across once. After that, every resource you add, filter you rename, or category you retire in BC is invisible to Dime.Scheduler until something re-publishes it. On a real deployment that drift shows up as ghost resources and filters that no longer match.
A small codeunit re-publishes the same master data on a cadence, which you schedule through BC's job queue. [Master data synchronization](/integrations/bc/config/queue) explains how to set it up and how often to run it.
## How outbound calls are sent
By default, every change a user makes in BC opens an HTTP call to Dime.Scheduler and waits for it to return before the user's action completes. That is simple, but it ties a click to a network round trip and loses the call outright if Dime.Scheduler is briefly unreachable.
The [outbound queue](/integrations/bc/config/outbound-queue) lets you log every call for auditing, and optionally buffer those calls for background processing with automatic retries.
import DocCardList from '@theme/DocCardList';
---
## Outbound queue
:::info[Optional]
Both features on this page are switched off by default. Existing installations keep behaving exactly as before until you turn them on.
:::
Out of the box, every time someone in Business Central saves a job, moves a service order allocation, or posts a routing line, the connector opens an HTTP call to Dime.Scheduler and waits for it to come back before the user's action completes. That is simple and it works, but it ties a user's click to a network round trip: the record stays locked while the call is in flight, the user waits, and if Dime.Scheduler happens to be unreachable at that exact moment the call is simply lost.
The **outbound queue** breaks that coupling. It gives you two independent capabilities:
- **Logging** keeps the synchronous behavior but writes every call to a table first, so you have a permanent record of what BC sent, when, on whose behalf, and whether it succeeded.
- **Asynchronous processing** goes a step further and stops sending during the user's action altogether. BC writes a queue entry and returns immediately. A recurring BC job queue entry picks the entries up a few minutes later and does the actual sending, retrying anything that failed.
The result is a snappier UI, fewer table locks, and calls that survive a temporary outage instead of disappearing.
## Turning it on
Both switches live in an **Outbound Queue** group that appears in two places: the **DS Connector Setup** page and the **Dime.Scheduler FastTrack Wizard**. They hold the same four settings, so use whichever you happen to have open.
| Setting | What it does |
| --- | --- |
| **Log Outbound Messages** | Writes every outbound call to the outbound queue as a record of what was sent. Calls still go out immediately. |
| **Use Asynchronous Outbound Queue** | Stops sending during the user's action. Entries are written to the queue and sent later by the job queue. |
| **Queue Max Attempts** | How many times a failing entry is retried before the background job gives up on it. Defaults to 3. |
| **Queue Retry Delay (min.)** | How long a failed entry waits before it becomes eligible again. Defaults to 0, which means the next run picks it up straight away. |
The two toggles are not independent in one direction: asynchronous processing needs the queue to exist, so switching it on switches logging on with it and greys the logging field out, which is why **Log Outbound Messages** appears locked in the screenshot above. Logging on its own is perfectly valid, and it is the lower-risk way to start: you get the full audit trail without changing how or when anything is sent.
## The job queue entry
Queue entries do not process themselves. Something has to run the **DS Process Outbound Queue** codeunit on a cadence, and in Business Central that something is a job queue entry. This is a different entry from the one that drives [master data synchronization](/integrations/bc/config/queue); the two are unrelated and run on their own schedules.
The connector creates it for you. If you enable asynchronous processing in the FastTrack Wizard, the wizard creates the entry as you move past that step. If you enable it on the DS Connector Setup page instead, use the **Create Job Queue Entry** action. Either way you get one entry, set to run every 5 minutes, and BC offers to open it straight away:
Two things to know about that entry:
1. **It is created On Hold.** Nothing is processed until you set it to **Ready**, using the action of that name on the job queue entry card. This is deliberate, so that enabling the setting never starts pushing data at a moment you did not choose.
2. **The 5 minute interval is only a starting point.** Change **No. of Minutes between Runs** on the entry itself. Shorter intervals mean planners see BC changes sooner; longer intervals batch more work per run.

The **Open Job Queue Entry** action on the setup page takes you straight to it. If the entry was deleted at some point, that action tells you so, and **Create Job Queue Entry** makes a new one.
:::warning
With asynchronous processing enabled and the job queue entry on hold, nothing reaches Dime.Scheduler. If planners report that BC changes have stopped arriving, this is the first thing to check.
:::
## Reading the queue
Search Business Central for **Dime.Scheduler Outbound Queue** to open the log. Every row is one call, newest first, and carries both the technical detail and enough business context to recognize what it was about: the procedure that was called, the source type and record number, and where applicable the job, task, appointment and resource involved. The **User ID** column tells you whose action triggered it.

Each entry has one of four statuses:
| Status | Meaning |
| --- | --- |
| **Pending** | Written to the queue, not sent yet. |
| **Processing** | Currently being sent. |
| **Completed** | Successfully delivered to Dime.Scheduler. |
| **Error** | The call failed. The **Message** column holds the error text returned. |
Failed entries also carry **Attempts** and **Next Retry After**, which together explain what the background job will do next: it skips anything that has already used up its **Queue Max Attempts**, and anything whose retry time has not yet arrived. An entry that exhausted its attempts is not lost, it simply stops being retried automatically and waits for you.
From the page you can:
- **Process Queue** runs everything pending and failed right now, without waiting for the job queue. Run manually like this, it deliberately ignores the max attempts and retry delay limits, which makes it the natural thing to click after you have fixed whatever was causing the failures.
- **Retry** resets the selected entry back to Pending with its attempt count cleared, so the next background run treats it as new.
- **Show Json** downloads the exact payload that was or will be sent, which is usually the fastest way to understand why Dime.Scheduler rejected something.
- **Clear Completed** and **Delete Selected** clean up entries you no longer need.
## Keeping the table from growing forever
If you log every outbound call, the queue table grows with every change your users make. On an active installation that adds up quickly, so plan for cleanup rather than discovering the problem later.
Business Central already has the mechanism: [retention policies](https://learn.microsoft.com/en-us/dynamics365/business-central/admin-data-retention-policies). The connector registers the outbound queue table (**2087643**) as an allowed table when it is installed or upgraded, along with a sensible default: delete entries that are **Completed** and whose **Processed On** date is more than **6 months** old. Completed entries only, so nothing still waiting or in error is ever swept away.
Registering the table does not create the policy. To activate it, search for **Retention Policies**, add a new one, and select table 2087643. Adjust the retention period or the filter if six months is not the right answer for you.
## Where this leaves you
If you only enable logging, nothing about the integration's timing changes. You simply gain a searchable history of everything BC sent, which is worth having on its own for troubleshooting.
If you enable asynchronous processing, the shape of the integration changes: BC stops waiting on Dime.Scheduler during user actions, and delivery becomes eventual rather than immediate, bounded by your job queue interval. For most deployments that trade is clearly worth it. If some part of your process genuinely depends on Dime.Scheduler being updated the instant a record is saved, keep that in mind when choosing the interval.
---
## Master data synchronization
:::info[Optional]
This step keeps master data fresh automatically. Skip it for a quick trial; add it for any real deployment.
:::
The FastTrack Wizard moves master data into Dime.Scheduler once. From that moment onward, anything you change in Business Central - a new resource, a renamed filter, a retired category - is invisible to Dime.Scheduler until you tell it otherwise. For a real deployment, that drift adds up fast: planners see ghost resources, missing categories, and filters that no longer match what BC enforces.
The fix is a small codeunit, **`DS Scheduled Synch`**, that re-publishes the same master data the wizard did. You can run it on demand from the **DS Setup** page, but the production answer is to add it to Business Central's **job queue** so it runs on a cadence and you never have to think about it.
Each run synchronizes:
1. Filters, categories, time markers, and pins
2. Resources (if standard resources are enabled)
3. Work and machine centers (if enabled in production routing planning)
How often to run it is a judgment call - hourly is plenty for most setups, once a day is fine if your master data barely changes, and anything more frequent rarely earns its keep.
---
## Safeguards and notifications
The FastTrack wizard gets a connection running fast, but it deliberately leaves two switches off the happy path. Both live on the '**Dime.Scheduler Setup**' page, and both decide how Business Central behaves when planning data and order data fall out of sync:
- Check appointments on delete
- Notify appointment errors
This page explains what each one does and when you want it on.
:::tip
You can find the wizard by looking for `Dime.Scheduler Setup` in the search bar.
:::

## Check appointments on delete
Picture the everyday slip: someone removes an order from Business Central that still has planning sitting in Dime.Scheduler. Delete it blindly and that planning is gone with it. With this switch on, Business Central stops you instead:
> The specified job JOB12345 cannot be deleted because it is associated with existing appointments. Please remove the appointments first, or retry this command with CheckAppointments set to 0.
The message already names the lever. Behind it, Dime.Scheduler's API exposes a `CheckAppointments` flag on both the [job](/develop/api/job#delete) and [task](/develop/api/task#delete) delete endpoints, which decides whether to confirm it is safe to remove the record before any planning data is lost:
- When set to true, the message above is thrown and the job is not removed from Dime.Scheduler.
- When switched off, the job is removed along with all underlying tasks and planned appointments.
The global `Check appointments on delete` setting is what the Dime.Scheduler extension in BC reads, then passes along with every request to remove tasks and jobs. It applies globally, so you set the safe default once. When a particular flow needs to behave differently, you can override it with custom extensions built on top of Dime.Scheduler's extension in Business Central.
## Notify appointment errors
Dime.Scheduler keeps a trail of every interaction it initiates with connected backoffice systems like Business Central, Exchange, and webhook connections. This [audit trail](/administration/transactions/appointments) is available inside Dime.Scheduler itself, and it is where you go to confirm that a piece of planning actually made it across.
There is a subtlety in what "made it across" means. When Dime.Scheduler posts data to the connected system, it marks that transaction as successful the moment the data is written to the [Dime.Scheduler Appointments table](/integrations/bc/appointments), the staging table on the BC side. From there a routing table, the [Dime.Scheduler Source Types list](/integrations/bc/sourcetypes), invokes a code unit whenever a new item is added to the list. That happens on every insert, update, or removal of an appointment in Dime.Scheduler, and the code unit processes the planning information Dime.Scheduler provided.
The catch is that landing in the staging table is not the same as being processed, and processing can fail for many reasons. In the image below the assigned resource is missing a 'Unit of Measure':

So the appointment carries the status Error in BC, even though Dime.Scheduler considers it sent successfully. Without any feedback, the planner is left wondering why the appointment never showed up in the expected tables. The staging table is there for diagnostics, but reading it is a developer's job, not a planner's.
That is the gap this switch closes. With 'Notify appointment errors' set to true, any error produced in BC is sent back to Dime.Scheduler as a ['Notification'](/user/components/notifications):

The failure now surfaces where the planner is already working, instead of going silent until someone digs through the staging table. Turning the switch on trades that confusion for a clear signal.
Here is how the transaction system works in Dime.Scheduler in tandem with Business Central. Step through the happy path on the USA tenant and then the failure case on the Canadian tenant:
---
## Introduction
You have two ways to customize the Dime.Scheduler Connector Extension, depending on how far your changes need to go:
- Extend the extension, also known as "dependent extensions"
- Subscribe to the Dime.Scheduler events
Event subscriptions are the lighter option, ideal for minor customizations that don't require much code. Most of the "Send" functions to Dime.Scheduler raise events, so developers can hook into the workflow by subscribing to them. Dependent extensions take more effort and are more complex, but they give you greater flexibility and room for extensive changes to the middleware layer between Dime.Scheduler and BC.
import DocCardList from '@theme/DocCardList';
---
## Events
The connector raises events at key points so you can run your own business logic before or after data is sent to Dime.Scheduler. Subscribe to any of the events below to extend or adjust the standard behavior. The event names describe what they do, and each table lists the code unit that publishes them.
## Service Header
| Event name | Code unit |
| ----------------------------------------------------------------- | --------- |
| OnBeforeSendServiceHeaderAsJobToDimeScheduler | 2087627 |
| OnAfterSendServiceHeaderAsJobToDimeScheduler | 2087627 |
| OnBeforeSendServiceHeaderAsTaskToDimeScheduler | 2087627 |
| OnAfterSendServiceHeaderAsTaskToDimeScheduler | 2087627 |
| OnBeforeSendServiceItemLineAsTaskToDimeScheduler | 2087627 |
| OnAfterSendServiceItemLineAsTaskToDimeScheduler | 2087627 |
| OnBeforeSendServiceOrderAllocationAsAppointmentToDimeScheduler | 2087627 |
| OnAfterSendServiceOrderAllocationAsAppointmentToDimeScheduler | 2087627 |
| OnAfterDeleteServiceHeaderAsJobFromDimeScheduler | 2087627 |
| OnAfterDeleteServiceItemLineAsTaskFromDimeScheduler | 2087627 |
| OnAfterDeleteServiceOrderAllocationAsAppointmentFromDimeScheduler | 2087627 |
| OnBeginModifyNewServiceOrderAllocation | 2087645 |
| OnFinishModifyNewServiceOrderAllocation | 2087645 |
| OnBeginModifyExistingServiceOrderAllocation | 2087645 |
| OnFinishModifyExistingServiceOrderAllocation | 2087645 |
## Assembly Header
| Event name | Code unit |
| ---------------------------------------------------- | --------- |
| OnBeforeSendAssemblyHeaderAsJobToDimeScheduler | 2087628 |
| OnAfterSendAssemblyHeaderAsJobToDimeScheduler | 2087628 |
| OnBeforeSendAssemblyHeaderAsTaskToDimeScheduler | 2087628 |
| OnAfterSendAssemblyHeaderAsTaskToDimeScheduler | 2087628 |
| OnBeforeSendAssemblyLineAsAppointmentToDimeScheduler | 2087628 |
| OnAfterSendAssemblyLineAsAppointmentToDimeScheduler | 2087628 |
| OnAfterDeleteAssemblyHeaderAsJobFromDimeScheduler | 2087628 |
## Employee Absence
| Event name | Code unit |
| ------------------------------------------------------- | --------- |
| OnBeforeSendEmployeeAbsenceAsAppointmentToDimeScheduler | 2087629 |
| OnAfterSendEmployeeAbsenceAsAppointmentToDimeScheduler | 2087629 |
| OnBeforeSendCauseOfAbsenceJobToDimeScheduler | 2087629 |
| OnAfterSendCauseOfAbsenceJobToDimeScheduler | 2087629 |
| OnBeforeSendCauseOfAbsenceAsTaskToDimeScheduler | 2087629 |
| OnAfterSendCauseOfAbsenceAsTaskToDimeScheduler | 2087629 |
| OnBeforeSendDsAppointmentAsAppointmentToDimeScheduler | 2087640 |
| OnAfterDeleteCauseOfAbsenceAsTaskFromDimeScheduler | 2087629 |
| OnAfterDeleteEmployeeAbsenceFromDimeScheduler | 2087629 |
| OnBeforeProcessEmployeeAbsenceDeletion | 2087641 |
| OnBeforeProcessEmployeeAbsenceUpdate | 2087641 |
| OnBeforeSendEmployeeAbsenceToDimeScheduler | 2087629 |
| OnBeforeProcessEmployeeAbsenceDeletion | 2087641 |
| OnBeforeProcessEmployeeAbsenceUpdate | 2087641 |
## Resource
| Event name | Code unit |
| --------------------------------------------- | --------- |
| OnBeforeSendResourceAsResourceToDimeScheduler | 2087631 |
| OnAfterSendResourceAsResourceToDimeScheduler | 2087631 |
| OnBeforeSendResourceToDimeScheduler | 2087631 |
## Job
| Event name | Code unit |
| ------------------------------------------------ | --------- |
| OnBeforeSendJobAsJobToDimeScheduler | 2087637 |
| OnAfterSendJobAsJobToDimeScheduler | 2087637 |
| OnBeforeSendJobTaskAsTaskToDimeScheduler | 2087637 |
| OnAfterSendJobTaskAsTaskToDimeScheduler | 2087637 |
| OnBeforeSendJobAsTaskToDimeScheduler | 2087637 |
| OnAfterSendJobAsTaskToDimeScheduler | 2087637 |
| OnBeforeSendJobPlanningLineAsTaskToDimeScheduler | 2087637 |
| OnAfterSendJobPlanningLineAsTaskToDimeScheduler | 2087637 |
| OnAfterDeleteJobAsJobFromDimeScheduler | 2087637 |
| OnAfterDeleteJobTaskAsTaskFromDimeScheduler | 2087637 |
| OnBeforeSendJobToDimeScheduler | 2087637 |
| OnBeforeSendJobTasksToDimeScheduler | 2087637 |
| OnBeginModifyNewJobPlanningLine | 2087646 |
| OnFinishModifyNewJobPlanningLine | 2087646 |
| OnBeginModifyExistingJobPlanningLine | 2087646 |
| OnFinishModifyExistingJobPlanningLine | 2087646 |
| OnAfterPerformAllocationJobPlanning | 2087646 |
| OnAfterCreateJobPlanningLine | 2087646 |
## Sales Header
| Event name | Code unit |
| ---------------------------------------------- | --------- |
| OnBeforeSendSalesHeaderAsJobToDimeScheduler | 2087639 |
| OnAfterSendSalesHeaderAsJobToDimeScheduler | 2087639 |
| OnBeforeSendSalesHeaderAsTaskToDimeScheduler | 2087639 |
| OnAfterSendSalesHeaderAsTaskToDimeScheduler | 2087639 |
| OnBeforeSendSalesLineAsTaskToDimeScheduler | 2087639 |
| OnAfterSendSalesLineAsTaskToDimeScheduler | 2087639 |
| OnAfterDeleteSalesHeaderAsJobFromDimeScheduler | 2087639 |
| OnAfterDeleteSalesLineAsTaskFromDimeScheduler | 2087639 |
| OnBeginModifyNewResourceSalesLine | TBD |
| OnFinishModifyNewResourceSalesLine | TBD |
| OnBeginModifyExistingResourceSalesLine | TBD |
| OnFinishModifyExistingResourceSalesLine | TBD |
| OnBeforeSalesHeaderModify | TBD |
| OnAfterSalesHeaderModify | TBD |
## Res. Capacity Entry
| Event name | Code unit |
| ---------------------------------------------------------- | --------- |
| OnBeforeSendResourceCapacityEntryAsCapacityToDimeScheduler | 2087647 |
| OnAfterSendResourceCapacityEntryAsCapacityToDimeScheduler | 2087647 |
## Production order
| Event name | Code unit |
| ------------------------------------------------------------------ | --------- |
| OnBeforeSendProductionOrderAsJobToDimeScheduler | 2087649 |
| OnAfterSendProductionOrderAsJobToDimeScheduler | 2087649 |
| OnBeforeSendProductionOrderRoutingLineAsTaskToDimeScheduler | 2087649 |
| OnAfterSendProductionOrderRoutingLineAsTaskToDimeScheduler | 2087649 |
| OnBeforeSendProductionOrderRoutingLineAsAppointmentToDimeScheduler | 2087649 |
| OnAfterSendProductionOrderRoutingLineAsAppointmentToDimeScheduler | 2087649 |
| OnAfterDeleteProductionOrderAsJobFromDimeScheduler | 2087649 |
| OnAfterDeleteProductionOrderRoutingLineAsTaskFromDimeScheduler | 2087649 |
| OnBeforeSendProductionOrderToDimeScheduler | TBD |
| OnBeforeUpdateAppointmentOrderStatus | 2087649 |
`OnBeforeUpdateAppointmentOrderStatus` fires every time a production order moves from one status to another. Set `IsHandled` to bypass the connector's standard status handling and substitute your own.
## Work and machine center
| Event name | Code unit |
| -------------------------------------------------- | --------- |
| OnBeforeSendWorkCenterAsResourceToDimeScheduler | 2087650 |
| OnAfterSendWorkCenterAsResourceToDimeScheduler | 2087650 |
| OnBeforeSendMachineCenterAsResourceToDimeScheduler | 2087650 |
| OnAfterSendMachineCenterAsResourceToDimeScheduler | 2087650 |
## Calendar Entry
| Event name | Code unit |
| -------------------------------------------------- | --------- |
| OnBeforeSendCalendarEntryAsCapacityToDimeScheduler | 2087647 |
| OnAfterSendCalendarEntryAsCapacityToDimeScheduler | 2087647 |
## Synchronize to Dime.Scheduler
| Event name | Code unit |
| -------------------------------- | --------- |
| OnBeforeScheduledSynch | 2087634 |
| OnAfterScheduledSynch | 2087634 |
| OnBeforeOpenFastTrackWizard | 2087642 |
| OnAfterFinishConfigurationWizard | 2087654 |
## Synchronize from Dime.Scheduler
| Event name | Code unit |
| ---------------------------- | --------- |
| OnBeforeProcessDsAppointment | 2087635 |
| OnAfterProcessDsAppointment | 2087635 |
| OnDsAppointmentProcessed | 2087635 |
## Outbound queue
| Event name | Code unit |
| -------------------- | --------- |
| OnDispatchQueueEntry | 2087657 |
Raised just before an [outbound queue](/integrations/bc/config/outbound-queue) entry is sent to Dime.Scheduler. Set `IsHandled` to take over dispatching yourself, for example to route the payload elsewhere or to suppress it entirely.
## Notification
| Event name | Code unit |
| ------------------------------------------------------ | --------- |
| OnBeforeDeleteJobNotificationFromDimeScheduler | 2087638 |
| OnBeforeDeleteTaskNotificationFromDimeScheduler | 2087638 |
| OnBeforeDeleteAppointmentNotificationFromDimeScheduler | 2087638 |
| OnBeforeDeleteNotificationFromDimeScheduler | 2087638 |
---
## Extension
This page shows you how to build your own AL extension on top of the standard Dime.Scheduler connector for Business Central, so you can add customer-specific logic without touching the connector itself.
The pattern is like Matryoshka dolls, where each doll fits inside a larger one. You build on functionality that already exists and layer your own logic on top. The result is a clean stack where your extension holds only the business logic for one specific purpose.
The Dime.Scheduler extension for Business Central is deliberately thin. It adds just the components you need to get started quickly. The experience is batteries-included, and it was built to be extended by partners.
## Requirements
To extend the connector, create a [blank extension](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-extension-example) or reuse one you already have. You will need:
- A Business Central tenant to develop and test
- VS Code
- The [AL Language extension](https://marketplace.visualstudio.com/items?itemName=ms-dynamics-smb.al)
These two are optional, but we recommend them:
- [CRS AL Language Extension](https://marketplace.visualstudio.com/items?itemName=waldo.crs-al-language-extension)
- [AL Object Designer](https://marketplace.visualstudio.com/items?itemName=martonsagi.al-object-designer)
## Adding the dependency
Declare the standard Dime.Scheduler extension as a dependency in your `app.json` file. Add Dime.Scheduler to the `dependencies` array:
```json
"dependencies": [{
"id": "f15bbe6d-c2a3-4828-aa98-5a77cff4cd41",
"publisher": "Dime",
"name": "Dime.Scheduler",
"version": "2.3.0.0"
}
]
```
Here is a typical example of a complete `app.json` that we create for our customers:
```json
{
"id": "d9c75381-bb78-4de7-81d3-7db784177465",
"name": "Customer XYZ Dime.Scheduler Customization",
"publisher": "Dime Software",
"version": "1.0.0.0",
"brief": "This extension adds custom behavior for Customer XYZ to the standard Business Central Dime.Scheduler connector",
"description": "This extension adds custom behavior for Customer XYZ to the standard Business Central Dime.Scheduler connector",
"privacyStatement": "https://www.dimescheduler.com/privacy",
"EULA": "https://docs.dimescheduler.com/",
"help": "https://docs.dimescheduler.com/",
"url": "https://www.dimescheduler.com/",
"logo": "./assets/logo.png",
"dependencies": [{
"id": "f15bbe6d-c2a3-4828-aa98-5a77cff4cd41",
"publisher": "Dime",
"name": "Dime.Scheduler",
"version": "2.3.0.0"
}
],
"screenshots": [],
"platform": "21.0.0.0",
"application": "21.0.0.0",
"idRanges": [
{
"from": 50000,
"to": 50200
}
],
"contextSensitiveHelpUrl": "https://docs.dimescheduler.com/",
"resourceExposurePolicy": {
"allowDebugging": true,
"allowDownloadingSource": false,
"includeSourceInSymbolFile": false
},
"runtime":"10.0"
}
```
## Custom development
What you build next is up to you. You might add new tables, extend existing ones, or add your own code unit to handle incoming appointments from Dime.Scheduler. The sections below walk through a few examples to show what extending the connector looks like.
### Extend Dime.Scheduler artifacts
The standard connector adds only a handful of new objects to Business Central. One essential object is the **setup page**, which exposes the functionality to link BC tables to Dime.Scheduler. You can add your own fields to this table. Extending Dime.Scheduler's own artifacts is optional, but you are free to do so as you see fit.
Here is an example that adds a few fields to the service order module on the setup page:
```al
tableextension 50161 "MYCUSTOMER DS Setup" extends "Dime DS Setup"
{
fields
{
field(50000; "MYCUSTOMER DS Initial Repair Status"; Code[10])
{
Caption = 'Initial Repair Status Code';
TableRelation = "Repair Status".Code;
ValidateTableRelation = true;
}
field(50001; "MYCUSTOMER DS Planned Repair Status"; Code[10])
{
Caption = 'Planned Repair Status Code';
TableRelation = "Repair Status".Code;
ValidateTableRelation = true;
}
field(50002; "MYCUSTOMER DS Replan Repair Status"; Code[10])
{
Caption = 'Replan Repair Status Code';
TableRelation = "Repair Status".Code;
ValidateTableRelation = true;
}
}
}
pageextension 50161 "MYCUSTOMER DS Setup" extends "Dime DS Setup"
{
layout
{
addafter(Testing)
{
group(MYCUSTOMER)
{
Caption = 'MYCUSTOMER';
field("MYCUSTOMER DS Initial Repair Status"; Rec."MYCUSTOMER DS Initial Repair Status")
{
ApplicationArea = All;
}
field("MYCUSTOMER DS Planned Repair Status"; Rec."MYCUSTOMER DS Planned Repair Status")
{
ApplicationArea = All;
}
field("MYCUSTOMER DS Replan Repair Status"; Rec."MYCUSTOMER DS Replan Repair Status")
{
ApplicationArea = All;
}
}
}
}
}
```
On its own, this does not change the message flow from BC to Dime.Scheduler. To send the extra information across, you tap into the right events. See the [list of events](/integrations/bc/dev/events) and the [event listener example below](#subscribe-to-events) for how to send additional information to Dime.Scheduler.
### Extend standard tables
A common pattern is to add additional configuration to standard objects like the `Resource Card`.
```al
pageextension 50160 "MYCUSTOMER DS Resource Card" extends "Resource Card"
{
layout
{
addafter("Dime DS Enable Exchange")
{
field("MYCUSTOMER DS Planning Duration"; Rec."MYCUSTOMER DS Planning Duration")
{
ApplicationArea = All;
}
}
}
}
```
### Subscribe to events
Here is a code unit that listens to a few events that Dime.Scheduler raises:
- `OnBeforeSendResourceAsResourceToDimeScheduler` adds logic to control which appointments are to be sent to Dime.Scheduler.
- `OnBeforeSendServiceHeaderAsJobToDimeScheduler` maps custom fields to Dime.Scheduler free fields.
- `OnBeforeSendServiceItemLineAsTaskToDimeScheduler` adds a field to one of Dime.Scheduler's free fields.
```al
codeunit 50305 "DS MYCUSTOMER Events Subscriber"
{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Dime DS Events", 'OnBeforeSendResourceAsResourceToDimeScheduler', '', false, false)]
local procedure FilterOnResourceGroup(var Resource: Record Resource; var DsWebServMgt: Codeunit "Dime DS Web Service Management")
var
ResourceGroup: Record "Resource Group";
Employee: Record "Employee";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
NotSendResourceGroup: Boolean;
begin
if ResourceGroup.Get(Resource."Resource Group No.") then
NotSendResourceGroup := not (ResourceGroup."DS MYCUSTOMER Send To DS")
else
NotSendResourceGroup := true;
DsWebServMgt.ReplaceParameter('DoNotShow', DSWebServMgt.HandleBool((Resource.Blocked) or (Resource."Dime DS Hide In Planning Board") or (NotSendResourceGroup)));
// If there's no employee, use the resource information
Employee.SetRange("Resource No.", Resource."No.");
if not Employee.FindFirst() then begin
DSWebServMgt.ReplaceParameter('HomeAddress', Resource.Address + ', ' + Resource."Address 2" + ', ' + Resource."Post Code" + ', ' + Resource.City);
DSWebServMgt.ReplaceParameter('HomeCountry', DimeDSDimeSchedulerMgt.GetCountryCode(Resource."Country/Region Code"));
DSWebServMgt.ReplaceParameter('InServiceFrom', DSWebServMgt.HandleDate(Resource."Employment Date"));
DsWebServMgt.ReplaceParameter('Email', Resource."DS MYCUSTOMER Exchange Email");
DsWebServMgt.ReplaceParameter('ExchangeIntegrationEnabled', DSWebServMgt.HandleBool(Resource."DS MYCUSTOMER Enable Exchange"));
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Dime DS Events", 'OnBeforeSendServiceHeaderAsJobToDimeScheduler', '', false, false)]
local procedure SendServiceHeader(var DsWebServMgt: Codeunit "Dime DS Web Service Management"; var ServiceHeader: Record "Service Header")
begin
DsWebServMgt.ReplaceParameter('FreeText1', ServiceHeader.SomeCustomField);
DsWebServMgt.ReplaceParameter('FreeDate1', DsWebServMgt.HandleDate(ServiceHeader."Some Random Date"));
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Dime DS Events", 'OnBeforeSendServiceItemLineAsTaskToDimeScheduler', '', false, false)]
local procedure SendServiceItemLine(var DsWebServMgt: Codeunit "Dime DS Web Service Management"; var ServiceItemLine: Record "Service Item Line")
begin
DsWebServMgt.ReplaceParameter('FreeText1', ServiceItemLine."Service Item Group Code");
end;
}
```
### Handling incoming appointments
Getting data from Dime.Scheduler back into BC takes very little Dime.Scheduler knowledge. When the planner changes the schedule, Dime.Scheduler posts the appointment to the `Dime.Scheduler Appointments` table. Event listeners are ready to pick up that data and process it into the [right table](/integrations/bc/sourcetypes).
Here is skeleton code that outlines the first stages of a code unit that processes incoming appointments from Dime.Scheduler. This one processes appointments as service order allocation lines, but you can do whatever you need with it.
```al
codeunit 50094 "MYCUSTOMER DS Functions"
{
TableNo = "Dime DS Appointment";
trigger OnRun()
begin
PerformAllocation(Rec);
end;
local procedure PerformAllocation(DimeDSAppointment: Record "Dime DS Appointment")
var
DimeDSAppointmentResource: Record "Dime DS Appointment Resource";
DimeDSServOrderAllocLnk: Record "Dime DS Serv. Order Alloc. Lnk";
DimeDSSetup: Record "Dime DS Setup";
ServHeader: Record "Service Header";
ServItemLine: Record "Service Item Line";
ServOrdAllocation: Record "Service Order Allocation";
ServAllocMgt: Codeunit ServAllocationManagement;
DimeDSHandleTimeSheet: Codeunit "Dime DS Handle Time Sheet";
LineNo: Integer;
OrderNotFoundErr: Label 'Service Order "(%1)" could not be found.', Comment = '%1 = Service Order No.';
OrderStatusErr: Label 'You cannot allocate a resource to the service order %1 because it is %2.', Comment = '%1 = Service Order Number ; %2 = Status';
AllocationExists: Boolean;
begin
// YOUR LOGIC GOES HERE
// TYPICAL STRUCTURE
// 1. VALIDATE ITEM IN BC
// 2. CHECK WHAT KIND OF PLANNING WAS DONE: DimeDSAppointment."Database Action" CAN BE "I" (insert), "U" (update), or "D" (delete)
// 3. PROCESS ACCORDINGLY SUCH AS CREATING AN ALLOCATION LINE, OR WHEN THAT ALREADY EXISTS, ENSURE A LINK IS ESTABLISHED BETWEEN THE ITEM IN BC AND DIME.SCHEDULER (e.g. "Dime DS Job Planning Line Link","Dime DS Serv. Order Alloc. Lnk")
end;
}
```
You may want to pair this code unit with a permission set:
```al
permissionset 50002 "MYCUSTOMER DS ALL"
{
Assignable = true;
Caption = 'Dime.Scheduler MYCUSTOMER customizations', Locked = true;
Permissions = codeunit "MYCUSTOMER DS Functions" = X;
}
```
## The Dime.Scheduler Web Service code unit
Every call to the Dime.Scheduler API goes through the `Dime DS Web Service Management` code unit, as you have seen in the earlier samples. When you use the default workflow and only need minor customizations, you can end up with tiny solutions like this:
```al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Dime DS Events", 'OnBeforeSendServiceItemLineAsTaskToDimeScheduler', '', false, false)]
local procedure SendServiceItemLine(var DsWebServMgt: Codeunit "Dime DS Web Service Management"; var ServiceItemLine: Record "Service Item Line")
begin
// Replace
DsWebServMgt.ReplaceParameter('FreeText1', ServiceItemLine."Service Item Group Code");
// Add
DSWebservMgt.AddParameter('BillNo', ServiceHeader."Bill-to Customer No.");
end;
```
Subscribe to the right event, find the field you want to modify, and make the change in code.
The approach is the same if you want to deviate from the standard connector altogether. Fetch the data from BC and use the `Dime DS Dime.Scheduler Mgt.` code unit. Each call to `AddParameter` adds a key/value pair to the API call and passes it to the import procedure you specify in the `CallDimeSchedulerWS` method call.
Below is a realistic example that overrides the standard logic. It maps custom fields to Dime.Scheduler's free fields (see the highlighted rows) and plans at the service item level instead of the default header level used in service orders.
```al {96,132-152} showLineNumbers
procedure SendServiceItemLineAsTask(var ServiceItemLine: Record "Service Item Line")
var
ServiceHeader: Record "Service Header";
ServContractHdr: Record "Service Contract Header";
ServItem: Record "Service Item";
Customer: Record Customer;
DimeDSDocFilterValueMgt: Codeunit "Dime DS Doc. Filter Value Mgt.";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DSMYCUSTOMERMaterialStatusMgt: Codeunit "DS MYCUSTOMER Material Status Mgt.";
RecRef: RecordRef;
ProjectObject: Record "MYCUSTOMER Project Object";
ContactName: Text[100];
ContactPhone: Text[30];
ContactEmail: Text[80];
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();
if not ServiceHeader.Get(ServiceItemLine."Document Type", ServiceItemLine."Document No.") then
exit;
if not ServContractHdr.Get(ServContractHdr."Contract Type"::Contract, ServiceItemLine."Contract No.") then
ServContractHdr.Init();
if not ServItem.Get(ServiceItemLine."Service Item No.") then
ServItem.Init();
// Transfer the Filter Values for the Task - uses the DS Doc. Filter Value Sources setup
RecRef.GetTable(ServiceItemLine);
RecRef.SetRecFilter();
DimeDSDocFilterValueMgt.TransferDocFilterValues(RecRef);
DSWebservMgt.InitParameters();
// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::"Service Header"));
DSWebservMgt.AddParameter('TaskNo', Format(ServiceItemLine."Line No.", 0, 9));
DSWebservMgt.AddParameter('JobNo', ServiceItemLine."Document No." + '_' + format(ServiceItemLine."Line No."));
DSWebservMgt.AddParameter('ShortDescription', '');
// Map the category visual indicator
case DimeDSSetup."Service - Default Category" of
DimeDSSetup."Service - Default Category"::" ":
DSWebservMgt.AddParameter('Category', '');
DimeDSSetup."Service - Default Category"::"Repair Status":
DSWebservMgt.AddParameter('Category', ServiceItemLine."Repair Status Code");
DimeDSSetup."Service - Default Category"::"Service Order Type":
DSWebservMgt.AddParameter('Category', ServiceHeader."Service Order Type");
DimeDSSetup."Service - Default Category"::"Service Zone":
DSWebservMgt.AddParameter('Category', ServiceHeader."Service Zone Code");
end;
// Map the time marker visual indicator
case DimeDSSetup."Service - Default Timemarker" of
DimeDSSetup."Service - Default Timemarker"::" ":
DSWebservMgt.AddParameter('TimeMarker', '');
DimeDSSetup."Service - Default Timemarker"::"Repair Status":
DSWebservMgt.AddParameter('TimeMarker', ServiceItemLine."Repair Status Code");
DimeDSSetup."Service - Default Timemarker"::"Service Order Type":
DSWebservMgt.AddParameter('TimeMarker', ServiceHeader."Service Order Type");
DimeDSSetup."Service - Default Timemarker"::"Service Zone":
DSWebservMgt.AddParameter('TimeMarker', ServiceHeader."Service Zone Code");
end;
DSWebservMgt.AddParameter('Subject', '');
DSWebservMgt.AddParameter('Body', '');
DSWebservMgt.AddParameter('AppointmentTemplate', DimeDSDimeSchedulerMgt.GetAppointmentTemplate(DATABASE::"Service Header"));
// Fields that affect the color of the pin on the map when displaying the address
DSWebservMgt.AddParameter('Pin', '');
DSWebservMgt.AddParameter('Description', ServiceItemLine.Description);
DSWebservMgt.AddParameter('Type', ServiceItemLine."Service Item Group Code");
DSWebservMgt.AddParameter('ServiceNo', ServiceItemLine."Service Item No.");
DSWebservMgt.AddParameter('ServiceGroup', ServItem."Service Item Group Code");
DSWebservMgt.AddParameter('ServiceClass', ServItem."Service Price Group Code");
DSWebservMgt.AddParameter('ServiceSerialNo', ServiceItemLine."Serial No.");
DSWebservMgt.AddParameter('ServiceName', ServItem."Item Description");
DSWebservMgt.AddParameter('IRISFault', ServiceItemLine."Fault Code");
DSWebservMgt.AddParameter('IRISSymptom', ServiceItemLine."Symptom Code");
DSWebservMgt.AddParameter('IRISArea', ServiceItemLine."Fault Area Code");
DSWebservMgt.AddParameter('IRISReason', ServiceItemLine."Fault Reason Code");
DSWebservMgt.AddParameter('IRISResolution', ServiceItemLine."Resolution Code");
DSWebservMgt.AddParameter('ContractNo', ServiceItemLine."Contract No.");
DSWebservMgt.AddParameter('ContractType', ServContractHdr."Serv. Contract Acc. Gr. Code");
DSWebservMgt.AddParameter('ContractDescription', ServContractHdr.Description);
DSWebservMgt.AddParameter('ContractStartDate', DSWebservMgt.HandleDate(ServContractHdr."Starting Date"));
DSWebservMgt.AddParameter('ContractEndDate', DSWebservMgt.HandleDate(ServContractHdr."Expiration Date"));
DSWebservMgt.AddParameter('PartsWarrantyStartDate', DSWebservMgt.HandleDate(ServiceItemLine."Warranty Starting Date (Parts)"));
DSWebservMgt.AddParameter('PartsWarrantyEndDate', DSWebservMgt.HandleDate(ServiceItemLine."Warranty Ending Date (Parts)"));
DSWebservMgt.AddParameter('LaborWarrantyStartDate', DSWebservMgt.HandleDate(ServiceItemLine."Warranty Starting Date (Labor)"));
DSWebservMgt.AddParameter('LaborWarrantyEndDate', DSWebservMgt.HandleDate(ServiceItemLine."Warranty Ending Date (Labor)"));
// Map importance Indicator to custom "Material Status" field
DsWebServMgt.AddParameter('Importance', Format(DSMYCUSTOMERMaterialStatusMgt.ConvertMaterialStatusIntoImportance(ServiceItemLine."Material Status")));
DSWebservMgt.AddParameter('Status', ServiceItemLine."Repair Status Code");
DSWebservMgt.AddParameter('ExpectedResponseDateTime', DSWebservMgt.HandleDateTime(CreateDateTime(ServiceItemLine."Response Date", 0T)));
DSWebservMgt.AddParameter('ActualResponseDateTime', DSWebservMgt.HandleDateTime(CreateDateTime(ServiceItemLine."Starting Date", 0T)));
DSWebservMgt.AddParameter('RequestedStartDate', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('RequestedEndDate', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('ConfirmedStartDate', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('ConfirmedEndDate', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('ActualStartDate', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('ActualEndDate', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('LocationDescription', ServItem."Location of Service Item");
// Fields that affect default duration and capacity
DSWebservMgt.AddParameter('DurationInSeconds', DimeDSDimeSchedulerMgt.ConvertDecimaltoSeconds(DimeDSSetup."Default Duration Service Line"));
DSWebservMgt.AddParameter('PlanningUOM', '');
DSWebservMgt.AddParameter('PlanningUOMConversion', Format(0, 0, 9));
DSWebservMgt.AddParameter('PlanningQty', Format(0, 0, 9));
DSWebservMgt.AddParameter('UseFixPlanningQty', DSWebservMgt.HandleBool(false));
DSWebservMgt.AddParameter('RoundToUOM', DSWebservMgt.HandleBool(false));
// Fields that control if the task is an open task
DSWebservMgt.AddParameter('IsComplete', DSWebservMgt.HandleBool(false));
DSWebservMgt.AddParameter('InfiniteTask', DSWebservMgt.HandleBool(false));
DSWebservMgt.AddParameter('TaskOpenAsOf', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('TaskOpenTill', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('RequiredTotalDurationInSeconds', Format(0, 0, 9));
DSWebservMgt.AddParameter('RequiredNoResources', Format(0, 0, 9));
DSWebservMgt.AddParameter('DoNotCountAppointmentResource', DSWebservMgt.HandleBool(false));
// Fields to define Resource Certificates
DSWebservMgt.AddParameter('CertificateNo', '');
DSWebservMgt.AddParameter('AppointmentEarliestAllowed', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('AppointmentLatestAllowed', DSWebservMgt.HandleDateTime(0DT));
DSWebservMgt.AddParameter('FreeText1', ServiceItemLine."MYCUSTOMER Project Object No.");
DSWebservMgt.AddParameter('FreeText2', format(ServiceItemLine."MYCUSTOMER Project Object Type"));
DSWebservMgt.AddParameter('FreeText3', ServiceItemLine."Item No.");
DSWebservMgt.AddParameter('FreeText4', ServiceItemLine."MYCUSTOMER VIN");
ContactName := ServiceHeader."Ship-to Contact";
ContactPhone := ServiceHeader."Ship-to Phone";
ContactEmail := ServiceHeader."Ship-to E-Mail";
if ServiceItemLine."MYCUSTOMER Project Object No." <> '' then begin
ProjectObject.Get(ServiceItemLine."MYCUSTOMER Project Object No.");
if ProjectObject."Contact Name" <> '' then
ContactName := ProjectObject."Contact Name";
if ProjectObject."Contact Phone No." <> '' then
ContactPhone := ProjectObject."Contact Phone No.";
if ProjectObject."Contact E-mail" <> '' then
ContactEmail := ProjectObject."Contact E-mail";
end;
DSWebservMgt.AddParameter('FreeText6', ContactName);
DSWebservMgt.AddParameter('FreeText7', ContactPhone);
DSWebservMgt.AddParameter('FreeText8', ContactEmail);
// Sending Links with the Task - up to 3 are available in mboc_upsertTask, use mboc_upsertTaskUrl for more
RecRef.GetTable(ServiceHeader);
DSWebservMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5900));
DSWebservMgt.AddParameter('urldesc1', 'Service Order');
// DSWebservMgt.AddParameter('url2', DimeDSDimeSchedulerMgt.GeneratePageUrlWeb(RecRef,5900));
// DSWebservMgt.AddParameter('urldesc2', 'Service Order (Web)');
if Customer.Get(ServiceItemLine."Customer No.") then begin
RecRef.GetTable(Customer);
DSWebservMgt.AddParameter('url3', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 21));
DSWebservMgt.AddParameter('urldesc3', 'Customer card');
end;
// Call the Dime.Scheduler web service will insert or update the Task
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertTask');
end;
```
The data already lives in Business Central, Dime.Scheduler can hold it, and the connector gives you the tools to move it across. Your job is to identify and map the data so your customer can schedule with the information they need.
This is the signature of the code unit:
```mermaid
classDiagram
class DS["Dime DS Dime.Scheduler Mgt."]
DS : JWT Token
DS : InitParameters()
DS : AddParameter(parameterName, parameterValue)
DS : ReplaceParameter(parameterName, parameterValue)
DS : RemoveParameter(index)
DS : CallDimeSchedulerWS(procedureName)
```
The code unit handles authentication (by default, a JSON Web Token is created and renewed when you run the FastTrack wizard), composes the request body, and posts the request to Dime.Scheduler's API. You only specify the right parameters and call the right procedure name. The code unit takes care of the rest.
---
## Use dimensions for filter groups
import { FaUserCog } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
Use **[dimensions](https://learn.microsoft.com/en-us/dynamics365/business-central/finance-dimensions)** to give resources qualifications and tasks requirements, so Dime.Scheduler can match the right people to the right work. In this guide you build filter groups and values from Business Central dimensions, then assign them to tasks and resources to automate the matching process [^1].
By the end, you will know how to:
- Create a filter group
- Link filter group values to dimensions in Business Central
- Assign dimension values as requirements and qualifications to tasks and resources
## Prerequisites
This guide assumes you have already set up a [dimension](https://learn.microsoft.com/en-us/dynamics365/business-central/finance-dimensions) with at least one value.
## Step 1: Create filter group
### Group
Start by creating a filter group. In Business Central, hit `ALT + Q` and open the 'Dime.Scheduler Filter Groups' list. Create a new record of type 'FilterGroup' and enter a name. This example uses 'Department' as the filter group.

## Step 2: Create filter values
### Sources
With the new filter group record in place, go to the action bar of this list, click the ellipsis button, hover over the 'Related' submenu, and click 'Sources'. A **filter group source** is a reference to another table in Business Central. It dynamically creates filter values for this group from the data in the linked table(s).

Create a new record with the following properties:
- Table No.: 349
- Field No.: 3
- Field Filter: Where(Dimension Code=FILTER(`DEPARTMENT`))
:::tip
You may have to replace the value inside the filter to the right dimension code.
:::
[Table 349](https://learn.microsoft.com/en-us/dynamics365/business-central/design-details-table-structure#table-349-dimension-value) is the dimension values table, and Field No. 3 is the Name field. In other words, this record uses the 'Department' dimension value names as the source for this group's filter values.

### Filter Values
Finish the filter group configuration by creating the _initial_ values. In the 'Dime.Scheduler Filter Value Sources' list, select the row you just created, go to the actions bar, and click 'Create Filter Values'. Every value that matches the criteria, that is, every value of the department dimension, is retrieved and creates a corresponding entry in the filter values list.

To check the result, return to the filter groups list, select the filter group, click the ellipsis button, go to 'Related', and click 'Filter Values'. The values of the department dimension appear here too.
:::gotcha[The values list does not refresh itself]
This step creates the initial values list. Changes to the linked tables aren't synchronized or updated automatically, so rerun this step whenever you need to refresh the filter value list (and then synchronize with Dime.Scheduler).
:::
## Step 3: Link the filter group to resources and tasks
With the filter groups set up, assign them to resources and tasks. The table that handles the routing is 'Dime.Scheduler Doc. Filter Value Sources'.
:::tip
For detailed information on this table, see [filter value sources](/integrations/bc/modules/docfiltervaluesources).
:::
### Resource
Open the 'Dime.Scheduler Doc. Filter Value Sources' table and select the line with table name 'Resource'. It should already be there if you ran the Dime.Scheduler FastTrack Wizard. Click 'Tables' in the actions menu.

In this table, add two extra sources to hook the Department dimension up to your resources. Each record is a child of the record above it.
| Link To Table No. | Link to Table Name | Method |
| ----------------- | ------------------ | ------ |
| 352 | Default Dimension | All |
| 349 | Dimension Value | Match |
When done correctly, you end up with the following hierarchy:
```text
.
├── 156 - Match - Resource
│ ├── 352 - All - Default Dimension
│ ├── 349 - Match - Dimension Value
```
This graph shows that you navigate from the resource to the dimensions and then to the dimension values. For BC to do this, specify the **fields**, **links**, and **filters** for both records. Configure each by clicking the corresponding button in the ribbon.
:::tip
To learn about fields, links, filters, conditions, and more, see [filter value sources](/integrations/bc/modules/docfiltervaluesources).
:::
#### Default Dimension
##### Links
As the [docs](/integrations/bc/modules/docfiltervaluesources#tables) explain, links are the field(s) that connect the selected table to its parent table. The 'Default Value' table sits under the 'Resource' table in the graph, so link it to that table:
| Link From | Link To | Field |
| --------- | ------- | ----------- |
| 1 | 2 | Resource No |
##### Filters
As the [docs](/integrations/bc/modules/docfiltervaluesources#tables) explain, filters apply to the selected table to limit the number of records.
| Link to | Value | Comment |
| ------------------ | ---------- | ---------------------------------------------------------------- |
| 1 (Table ID) | 156 | Table 156 refers to the resources table. |
| 3 (Dimension code) | DEPARTMENT | You may have to replace the value with the right dimension code. |
This data set means the filter applies only to _resources_ that have the '_Department_' dimension. In other words, resources without a department will not be returned in the set. After all, this is the default dimension.
##### Fields
No action required here as there are no fields to map.
#### Dimension Value
The regular dimension value works much the same way. Define the links and fields. Filters aren't necessary here, since the default dimension already provides a fallback.
##### Links
As the [docs](/integrations/bc/modules/docfiltervaluesources#tables) explain, links are the field(s) that connect the selected table to its parent table. The 'Dimension Value' table sits under the 'Default Dimension' table in the graph, so link it to that table.
| Link From | Link To | Field |
| --------- | ------- | -------------------- |
| 3 | 1 | Dimension code |
| 4 | 2 | Dimension value code |
##### Filters
No action required here.
##### Fields
As the [docs](/integrations/bc/modules/docfiltervaluesources#tables) explain, fields specify which fields are the source of a filter value.
| Filter Group | Field No |
| ------------ | ------------------ |
| Department | 3 (Dimension Name) |
### Task
The process is similar for tasks, but a task can originate from many different sources, such as a service order, a sales order, and more. Each module also lets you plan at different levels. For a sales order, for example, you could plan at the sales header level or create a task for each sales line. As a result, you may need to repeat this step for every table you want to schedule.
This example uses service items. In the 'Dime.Scheduler Doc. Filter Value Sources' table, make sure there is a line with the following specifications:
| Column | Value |
| --------------------- | ----------------- |
| Table no | 5901 |
| Table name | Service Item line |
| Entity type | Task |
| Resource Key Field No | 0 |
| Job Key Field No | 1 |
| Job Key Field Name | Document No |
| Task Key Field No | 2 |
| Task Key Field Name | Line No. |

:::note
If you've enabled the service module in the Dime.Scheduler FastTrack Wizard, this record should already be there.
:::
Once the record exists, go to the Tables and make sure there is a line for table 5901 that applies the method 'Match' and links to the Service Item Line table.
Then select this record, click the Fields button in the ribbon, and add the following record:
- Filter Group: Department
- Field No.: 0
- Fixed Value: Service
- Fixed Value Behavior: Overrule
:::tip
You may have to replace the value inside the filter to the right dimension code and value.
:::
When you send a service order to Dime.Scheduler, it adds this extra requirement along with the order, and the filter button in the open task list becomes enabled. At least one filter is set: `Department = Service`
Do the same for a job task (table no. 1001) with the following field, and job tasks will carry a filter for `Department = Job`:
- Filter Group: Department
- Field No.: 0
- Fixed Value: Job
- Fixed Value Behavior: Overrule
Here is how it looks for a service order line:

## Step 4: Synchronize
With the setup complete, send the groups, values, and assignments to Dime.Scheduler.
### Filter Group
In Business Central, hit `ALT + Q` and open the 'Dime.Scheduler Setup' administration page. In the ribbon, click the 'Run Scheduled Synch.' button.
### Resources
Send resources to Dime.Scheduler. If you use the standard 'Resources' table, the resource data is sent when you run 'Run Scheduled Synch' on the 'Dime.Scheduler Setup' page. Otherwise, send the resources to Dime.Scheduler through the functionality you built yourself.
### Tasks
Send tasks to Dime.Scheduler. As you send each task, the setup configured in DS Doc. Filter Value Sources determines which filter values to include. You can send existing or new tasks. For existing tasks, this action is idempotent: filter values are added or updated in Dime.Scheduler.
## Bonus: configure user security
You can use filter groups to set up data-driven user security in Dime.Scheduler.

In Dime.Scheduler, go to > Users and select the user you want to apply a security rule to. In the pane at the bottom, find the filter group and check the values that apply to that user. From now on, the user sees only tasks and resources of the selected departments. Planned tasks stay visible on the planning board, but the user can't edit or delete them.
## Read more
- [Filter groups](/integrations/bc/modules/filtergroups)
- [Filter values](/integrations/bc/modules/filtervalues)
- [Filter value sources](/integrations/bc/modules/docfiltervaluesources)
[^1]:
This diagram describes the high-level data model covered in this guide. A **group** is a container of related **values**. A department (group) can refer to sales, support, reception, accounting, and so on (values). In Business Central, those values may originate from another table. This guide uses a dimension as the **source** for the filter values table. That relationship isn't mandatory, but it is powerful and keeps master data management simple: with the sources table, you reuse information that already exists in Business Central. In the end, the sources table generates a set of values, which you then assign to tasks and resources.
---
## Running a container-based development environment
import ReactPlayer from 'react-player'
Running Business Central on Docker gives you a disposable local instance, which is handy for development, testing, and trials. This guide walks you through deploying one with a sample script, then connecting it to Dime.Scheduler in both directions.
Prefer to watch? This video tutorial covers the entire process:
## Prerequisites
Download and install Docker Desktop, then switch it to Windows containers.
We have published an opinionated [PowerShell script](https://www.powershellgallery.com/packages/DimeScheduler.BCContainer) that wraps the `BcContainerHelper` module. It makes a number of assumptions that cut down the configuration needed to get the container running. Under the hood it simply calls `New-BcContainer`, so feel free to adapt it to your needs.
To use the script as is, meet these requirements first:
- Create the directory tree `C:\docker\DS`. This is the drive shared between Docker and Windows.
- Download the `.bclicense` license file and store it in `C:\docker\DS\` as `BC.bclicense`.
## Deploying the container
Open a PowerShell terminal as administrator and install the script from the [PowerShell Gallery](https://www.powershellgallery.com/packages/DimeScheduler.BCContainer).
```powershell
Install-Script -Name DimeScheduler.BCContainer
```
Move to the location where the script was installed:
```powershell
cd (Get-InstalledScript -Name "DimeScheduler.BCContainer").InstalledLocation
```
Run the script:
```powershell
.\DimeScheduler.BCContainer.ps1
```
If you don't pass any arguments, these default parameters are used:
| Parameter | Default |
| --------------------- | ------------------------- |
| `-version` | 24.0 |
| `-name` | BC24 |
| `-isNav` | false |
| `-sharedDrive` | C:\docker\DS |
| `-licenseFile` | C:\docker\DS\BC.bclicense |
| `-user` | admin |
| `-admin` | admin |
| `-useShortcuts` | true |
| `-includeTestToolkit` | false |
| `-memoryLimit` | 8G |
| `-dns` | 8.8.8.8 |
Pulling the base image and deploying it to the NAV container can take a few minutes. Once it finishes, you have a running instance accessible at http://bc24/BC, and the output looks like this:
```bash
Container IP Address: 172.24.17.49
Container Hostname : bc24
Container Dns Name : bc24
Web Client : http://bc24/BC/
Dev. Server : http://bc24
Dev. ServerInstance : BC
Setting bc24 to 172.24.17.49 in host hosts file
Files:
http://bc24:8080/ALLanguage.vsix
WARNING: You are running a container which is 138 days old.
Microsoft recommends that you always run the latest version of our containers.
Initialization took 85 seconds
Ready for connections!
Reading CustomSettings.config from bc24
Creating Desktop Shortcuts for bc24
```
The credentials are printed in the script output too. By default you get the very safe admin/admin combination.
## Deploy extension
The full procedure is already [documented](/integrations/bc/install/extension); this section recaps it. When the provisioning scripts finish, you will find a few shortcuts on your desktop:
- BC24 PowerShell Prompt
- BC24 Web Client
- BC24 Command Prompt
Install the extension with our installation script `DimeScheduler.InstallBCExtension.ps1`, available on the [PowerShell Gallery](https://www.powershellgallery.com/packages/DimeScheduler.InstallBCExtension):
```powershell
Install-Script -Name DimeScheduler.InstallBCExtension
```
Move your terminal to where the script was installed:
```powershell
cd (Get-InstalledScript -Name "DimeScheduler.InstallBCExtension").InstalledLocation
```
Now run the following command, which deploys the extension for BC24 on server instance BC:
```powershell
.\DimeScheduler.InstallBCExtension.ps1 -serverInstance BC -version 24
```
## Connection with Dime.Scheduler
The extension becomes available after a few moments. Once it does, press `ALT Q` in Business Central and look for `Dime.Scheduler FastTrack Wizard`. This wizard establishes a connection with Dime.Scheduler and helps you configure the modules you care about.
Follow the instructions in [connecting BC to Dime.Scheduler](/integrations/bc/install/bc-to-ds), keeping these points in mind:
- Connection:
- `Scheduler environment`: use the `Sandbox` option.
- `User`: create a user in Dime.Scheduler of type 'Forms'.
- `Password`: when a new user is created in Dime.Scheduler, a random initial password is assigned and shown in the browser. Copy this value and paste it into the `Scheduler Password` field.
- Resources: enable the resources module. The default settings on this tab can stay as they are.
- Modules: enable at least one of the modules on the next tabs. Service orders and projects work right away, since this BC instance already contains a small data set. When you enable them, keep the default values and finish the wizard.
Once the wizard completes, several resources are added to Dime.Scheduler, and the 'Send to Dime.Scheduler' buttons in the ribbon under 'Actions' become available in the modules you activated.
## Connection with Business Central
Follow the instructions in [connecting Dime.Scheduler to BC](/integrations/bc/install/ds-to-bc), keeping the following in mind.
The path `http://bc24/BC` only works on your workstation. The SOAP URI `http://bc24:7047/BC/WS/CRONUS%20International%20Ltd./Page/Appointment` works fine locally, but Dime.Scheduler, which is hosted in the cloud, does not know this address, so planning a task there results in errors.
For Dime.Scheduler to send data back to your local BC Docker container, run a tunnel such as [**ngrok**](https://ngrok.com/):
```bash
ngrok http http://bc24:7047
```
This forwards requests from URLs such as 'https://00fe-90-168-252-235.ngrok-free.app' to 'http://bc24:7047'.
In the connector setup in Dime.Scheduler, replace the host part ('bc24:7047') of the original SOAP address with the output ngrok generated. The result should look like this sample URL:
https://7539-90-168-252-235.ngrok-free.app/BC/WS/CRONUS%20International%20Ltd./Page/Appointment.
---
## Extending the Dime.Scheduler extension
import ReactPlayer from 'react-player'
:::info[Don't want to read?]
Head straight for the [repo](https://github.com/dime-scheduler/bc-connector-examples) and try it out now!
:::
Build your own Business Central extension and connect it to Dime.Scheduler. In this tutorial you'll create an extension from scratch and work through a small example that sends salespersons as resources and opportunities as planning items.
## Prerequisites
The Dime.Scheduler [base app must be installed](/integrations/bc/install/intro) and configured. You also need a running instance of Dime.Scheduler itself, since Dime.Scheduler does not run inside Business Central. The extension mostly maps data between the two systems, as you'll see.
## Step 1: Create a new project
Create a new AL project the way you normally would, for example with the AL:Go command. Then add a dependency on Dime.Scheduler in the `app.json` file:
```json
{
"id": "dimescheduler",
"name": "Dime.Scheduler",
"publisher": "Dime.Scheduler",
"version": "2.6.0.0"
}
```
Download the symbols by opening the command palette (`CTRL + SHIFT + P`) and running `AL: Download Symbols`. As noted in the prerequisites, this only works when the Dime.Scheduler [base app is installed](/integrations/bc/install/intro) in the BC instance configured in your `launch.json` file.
Your finished `app.json` file might look something like this:
```json
{
"id": "mydimeschedulerextension",
"name": "My Dime.Scheduler Extension",
"publisher": "Your Company",
"version": "1.0.0.0",
"brief": "Integration with Dime.Scheduler",
"description": "Extension for integrating Business Central with Dime.Scheduler",
"privacyStatement": "",
"EULA": "",
"help": "",
"url": "",
"logo": "",
"dependencies": [
{
"id": "dimescheduler",
"name": "Dime.Scheduler",
"publisher": "Dime.Scheduler",
"version": "2.6.0.0"
}
],
"screenshots": [],
"platform": "16.0.0.0",
"application": "16.0.0.0",
"idRanges": [
{
"from": 2088000,
"to": 2088999
}
],
"resourceExposurePolicy": {
"allowDebugging": true,
"allowDownloadingSource": false,
"includeSourceInSymbolFile": false
},
"runtime": "14.0"
}
```
## Step 2: Registering source types
To plan additional BC tables in Dime.Scheduler, register them in the `Dime.Scheduler Source Types` table. This source type metadata travels with every record you send to Dime.Scheduler. When planning data comes back from Dime.Scheduler to Business Central (in the `Dime.Scheduler Appointments` table), the source type tells you which table the entry belongs to, so you can process the planning data correctly.
In this example, we add two tables:
- Salespersons
- Opportunities
This setup only needs to happen once, so you can add the entries manually while developing. For production, use an installation code unit instead.

Create `Install.codeunit.al` to set up your source types:
```al
codeunit 2088007 "DS Install Demo"
{
Subtype = Install;
trigger OnInstallAppPerCompany()
begin
InitializeDSSourceTypes();
end;
local procedure InitializeDSSourceTypes()
var
DSSourceType: Record "Dime DS Source Type";
DimeDSSetup: Record "Dime DS Setup";
begin
// Check if setup exist
if not DimeDSSetup.Get() then
exit;
// Initialize SalesPerson source type
if not DSSourceType.Get(DATABASE::"Salesperson/Purchaser") then begin
DSSourceType.Init();
DSSourceType."Table No." := DATABASE::"Salesperson/Purchaser";
DSSourceType."Source Type" := 'REP';
DSSourceType.Insert();
end;
// Initialize Opportunity source type
if not DSSourceType.Get(DATABASE::Opportunity) then begin
DSSourceType.Init();
DSSourceType."Table No." := DATABASE::Opportunity;
DSSourceType."Source Type" := 'OPP';
DSSourceType."Processing Codeunit No." := Codeunit::"DS Handle Opportunity Demo";
DSSourceType.Insert();
end;
end;
}
```
This code unit registers the two tables. Salespersons are marked with the type "REP" and opportunities with "OPP". The exact value doesn't matter, as long as it doesn't clash with other supported tables and stays under 10 characters.
The opportunities table has an extra property: `Processing Codeunit No.`. As you'll see shortly, the Dime.Scheduler extension mostly consists of "Sending" and "Handling":
- **Sending**: making planning data available in Dime.Scheduler (outgoing)
- **Handling**: processing planning received from Dime.Scheduling (incoming)
When BC receives appointments with the "OPP" source type, it invokes `Codeunit::"DS Handle Opportunity Demo"` to process the planning data in the right place.

## Step 3: Create the salesperson integration
To plan opportunities, you first need the salespersons list available in Dime.Scheduler. The typical pattern is to add a button to both the list and the card.
### Sending salespersons
Start with the logic that creates resources in Dime.Scheduler from the salespersons list. Create `Salesperson/SendSalesperson.codeunit.al`:
```al
codeunit 2088003 "DS Send Sales Person Demo"
{
TableNo = "Salesperson/Purchaser";
trigger OnRun()
var
DimeDSSetup: Record "Dime DS Setup";
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSDocFilterValueMgt: Codeunit "Dime DS Doc. Filter Value Mgt.";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DSSendOpportunityDemo: Codeunit "DS Send Opportunity Demo";
RecRef: RecordRef;
begin
// Ensure setup records exist
if not DimeDSConnectorSetup.Get() then
exit;
if not DimeDSSetup.Get() then
exit;
// Transfer the filter values for the resource - uses the DS Doc. Filter Value Sources setup
RecRef.GetTable(Rec);
RecRef.SetRecFilter();
DimeDSDocFilterValueMgt.TransferDocFilterValues(RecRef);
DSWebServMgt.InitParameters();
// Mandatory fields
DSWebServMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebServMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::"Salesperson/Purchaser"));
DSWebServMgt.AddParameter('ResourceNo', Rec.Code);
DSWebServMgt.AddParameter('ResourceName', Rec.Name);
DSWebServMgt.AddParameter('DisplayName', Rec.Name);
DSWebServMgt.AddParameter('ResourceType', 'Salesperson');
// Setting DoNotShow to TRUE hides the Resource from all users
DSWebServMgt.AddParameter('DoNotShow', DSWebServMgt.HandleBool((Rec.Blocked)));
DSWebServMgt.AddParameter('Email', Rec."E-Mail");
DSWebServMgt.AddParameter('Phone', Rec."Phone No.");
DSWebServMgt.AddParameter('MobilePhone', Rec."Phone No.");
DSWebServMgt.AddParameter('FieldServiceEmail', Rec."E-Mail");
// Sending Links with the Resource - up to 3 are available in mboc_upsertResource, use mboc_upsertResourceUrl for more
RecRef.GetTable(Rec);
DSWebServMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5116));
DSWebServMgt.AddParameter('urldesc1', 'Salesperson Card');
DSWebServMgt.CallDimeSchedulerWS('mboc_upsertResource');
end;
var
DSWebServMgt: Codeunit "Dime DS Web Service Management";
procedure SyncSalespersons(var Salesperson: Record "Salesperson/Purchaser")
begin
if Salesperson.FindSet() then
repeat
CODEUNIT.Run(CODEUNIT::"DS Send Sales Person Demo", Salesperson);
until Salesperson.Next() = 0;
end;
}
```
This is a good illustration of what most of the Dime.Scheduler base app does: fetch BC data, map it to the Dime.Scheduler data model, and invoke the API through the DSWebServMgt code unit.
### Adding buttons
#### List
Create `SalespersonExt.PageExt.al`:
```al
pageextension 2088005 "DS Salesperson Ext Demo" extends "Salespersons/Purchasers"
{
layout
{
}
actions
{
addlast(Processing)
{
group("DS Demo Actions")
{
Caption = 'Dime.Scheduler Demo';
Image = Planning;
Action("DS Send Selection Demo")
{
Caption = 'Send Selection';
ApplicationArea = All;
Image = RefreshLines;
ToolTip = 'Sends the selected record to Dime.Scheduler.';
trigger OnAction()
var
SalesPerson: Record "Salesperson/Purchaser";
DsSendResource: codeunit "DS Send Sales Person Demo";
begin
CurrPage.SETSELECTIONFILTER(SalesPerson);
DsSendResource.SyncSalespersons(SalesPerson);
end;
}
Action("DS Send All Demo")
{
Caption = 'Send All';
ApplicationArea = All;
Image = RefreshPlanningLine;
Tooltip = 'Sends all records to Dime.Scheduler.';
trigger OnAction()
var
SalesPerson: Record "Salesperson/Purchaser";
DsSendResource: codeunit "DS Send Sales Person Demo";
begin
DsSendResource.SyncSalespersons(SalesPerson);
end;
}
}
}
}
}
```
#### Card
Create `SalespersonCardExt.PageExt.al`
```al
pageextension 2088004 "DS Salesperson Card Ext Demo" extends "Salesperson/Purchaser Card"
{
layout
{
}
actions
{
addlast(processing)
{
group("DS Demo Actions")
{
Caption = 'Dime.Scheduler Demo';
Action("DS Send Salesperson Demo")
{
Caption = 'Send Salesperson';
ApplicationArea = All;
Image = RefreshPlanningLine;
ToolTip = 'Sends the selected record to Dime.Scheduler.';
trigger OnAction()
begin
Codeunit.Run(codeunit::"DS Send Sales Person Demo", Rec);
end;
}
}
}
}
}
```
## Step 4: Create the opportunity integration
This follows the same pattern as the previous section: map the data and add the buttons that make opportunities available in Dime.Scheduler.
### Sending opportunities
There are two planning levels worth supporting: planning an opportunity as a single task, or planning its individual stages. To choose between them, we add a configuration flag, `Rec."DS Send Opportunity Header"`, covered right after this.
Create `Opportunity/SendOpportunity.codeunit.al`:
```al
codeunit 2088000 "DS Send Opportunity Demo"
{
TableNo = Opportunity;
trigger OnRun()
var
Contact: Record Contact;
OpportunityStages: Record "Opportunity Entry";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
// Ensure setup records exist
if not DimeDSConnectorSetup.Get() then
exit;
if not DimeDSSetup.Get() then
exit;
if not Contact.get(Rec."Contact No.") then
exit;
DSWebservMgt.InitParameters();
// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('JobNo', Rec."No.");
DSWebservMgt.AddParameter('ShortDescription', Rec.Description);
DSWebservMgt.AddParameter('Description', Rec.Description);
DSWebservMgt.AddParameter('Type', Rec."Sales Cycle Code");
DSWebservMgt.AddParameter('CustomerNo', Rec."Contact No.");
DSWebservMgt.AddParameter('CustomerName', Rec."Contact Name");
DSWebservMgt.AddParameter('CustomerAddress', Contact.Address + ', ' + Contact."Address 2" + ', ' + Contact."Post Code" + ', ' + Contact.City);
DSWebservMgt.AddParameter('CustomerPhone', Contact."Phone No.");
DSWebservMgt.AddParameter('CustomerEmail', Contact."E-Mail");
DSWebservMgt.AddParameter('ContactNo', Contact."No.");
DSWebservMgt.AddParameter('ContactName', Contact.Name);
// The address shown on the map in Dime.Scheduler
DSWebservMgt.AddParameter('SiteAddress', Contact.Address + ', ' + Contact."Address 2" + ', ' + Contact."Post Code" + ', ' + Contact.City);
DSWebservMgt.AddParameter('SitePostcode', Contact."Post Code");
DSWebservMgt.AddParameter('SiteCity', Contact."City");
DSWebservMgt.AddParameter('SiteCounty', Contact."County");
DSWebservMgt.AddParameter('SiteCountry', DimeDSDimeSchedulerMgt.GetCountryCode(Contact."Country/Region Code"));
DSWebServMgt.AddParameter('CreationDateTime', DSWebServMgt.HandleDateTime(CREATEDATETIME(Rec."Creation Date", 0T)));
DSWebservMgt.AddParameter('Creator', CopyStr(UserId(), 1, 1024));
DSWebservMgt.AddParameter('FreeText1', format(Rec.Status));
// Call the Dime.Scheduler web service will insert or update the Job
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertJob');
// Check if opportunity header should be sent as task
if Rec."DS Send Opportunity Header" then
this.SendOpportunityAsTask(Rec)
else begin
Clear(OpportunityStages);
OpportunityStages.SetRange("Opportunity No.", Rec."No.");
OpportunityStages.SetFilter("Sales Cycle Stage Description", '<>%1', '');
if OpportunityStages.FindSet() then
repeat
this.SendOpportunityCycleStages(OpportunityStages);
until OpportunityStages.Next() = 0;
end;
end;
local procedure SendOpportunityCycleStages(var OpportunityStages: Record "Opportunity Entry")
var
Opportunity: Record Opportunity;
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
RecRef: RecordRef;
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();
Opportunity.Get(OpportunityStages."Opportunity No.");
DSWebservMgt.InitParameters();
// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('TaskNo', format(OpportunityStages."Sales Cycle Stage"));
DSWebservMgt.AddParameter('JobNo', Opportunity."No.");
DSWebservMgt.AddParameter('ShortDescription', OpportunityStages."Sales Cycle Stage Description");
DSWebservMgt.AddParameter('Description', OpportunityStages."Sales Cycle Stage Description");
// Fields that affect default Duration and Capacity
DSWebservMgt.AddParameter('DurationInSeconds', DimeDSDimeSchedulerMgt.ConvertDecimaltoSeconds(4)); // Fixed 4h
// Sending Links with the Task - up to 3 are available in mboc_upsertTask, use mboc_upsertTaskUrl for more
RecRef.GetTable(Opportunity);
DSWebservMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5124));
DSWebservMgt.AddParameter('urldesc1', 'Opportunity Card');
// Call the Dime.Scheduler web service will insert or update the Task
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertTask');
end;
local procedure SendOpportunityAsTask(var Opportunity: Record Opportunity)
var
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
RecRef: RecordRef;
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();
DSWebservMgt.InitParameters();
// Mandatory fields
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('TaskNo', Opportunity."No.");
DSWebservMgt.AddParameter('JobNo', Opportunity."No.");
DSWebservMgt.AddParameter('ShortDescription', Opportunity.Description);
DSWebservMgt.AddParameter('Description', Opportunity.Description);
// Fields that affect default Duration and Capacity
DSWebservMgt.AddParameter('DurationInSeconds', DimeDSDimeSchedulerMgt.ConvertDecimaltoSeconds(4)); // Fixed 4h
// Sending Links with the Task - up to 3 are available in mboc_upsertTask, use mboc_upsertTaskUrl for more
RecRef.GetTable(Opportunity);
DSWebservMgt.AddParameter('url1', DimeDSDimeSchedulerMgt.GeneratePageUrl(RecRef, 5124));
DSWebservMgt.AddParameter('urldesc1', 'Opportunity Card');
// Call the Dime.Scheduler web service will insert or update the Task
DSWebservMgt.CallDimeSchedulerWS('mboc_upsertTask');
end;
procedure DeleteOpportunity(Opportunity: Record Opportunity)
var
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
DimeDSConnectorSetup: Record "Dime DS Connector Setup";
DimeDSSetup: Record "Dime DS Setup";
DSWebservMgt: Codeunit "Dime DS Web Service Management";
begin
DimeDSConnectorSetup.Get();
DimeDSSetup.Get();
if Opportunity."No." <> '' then begin
DSWebservMgt.InitParameters();
DSWebservMgt.AddParameter('SourceApp', DimeDSConnectorSetup."Source App");
DSWebservMgt.AddParameter('SourceType', DimeDSDimeSchedulerMgt.GetSourceType(DATABASE::Opportunity));
DSWebservMgt.AddParameter('JobNo', Opportunity."No.");
DSWebservMgt.AddParameter('CheckAppointments', DSWebservMgt.HandleBool(DimeDSSetup."Check Appointment on Delete"));
DSWebservMgt.CallDimeSchedulerWS('mboc_deleteJob');
end;
end;
}
```
### Extend the opportunity
To make the planning level configurable, add a flag to the opportunity table and card. Follow these steps:
#### Table
Create `OpportunityExt.TableExt.al`:
```al
tableextension 2088008 "DS Opportunity Ext Demo" extends Opportunity
{
fields
{
field(2088000; "DS Send Opportunity Header"; Boolean)
{
Caption = 'Send opportunity header as task';
DataClassification = CustomerContent;
InitValue = true;
}
}
}
```
#### Card
Create `Opportunity/OpportunityCardExt.pageext.al`:
```al
pageextension 2088001 "DS OpportunityCardExt Demo" extends "Opportunity Card"
{
layout
{
addlast(Content)
{
group("DS Dime.Scheduler Tab")
{
Caption = 'Dime.Scheduler';
field("DS Send Opportunity Header Tab"; Rec."DS Send Opportunity Header")
{
ApplicationArea = All;
ToolTip = 'Specifies whether to send the opportunity header to Dime.Scheduler.';
}
}
}
}
actions
{
addlast(processing)
{
group("DS Demo Actions")
{
Caption = 'Dime.Scheduler Demo';
Image = Planning;
action("DS Send to Dime.Scheduler Demo")
{
Caption = 'Send to Dime.Scheduler';
Image = Planning;
ApplicationArea = All;
trigger OnAction()
var
DsSendOpportunity: Codeunit "DS Send Opportunity Demo";
begin
DsSendOpportunity.Run(Rec);
end;
}
action("DS DeleteFromDimeScheduler Demo")
{
Caption = 'Delete from Dime.Scheduler';
Image = RemoveLine;
ApplicationArea = All;
trigger OnAction()
var
DsSendOpportunity: Codeunit "DS Send Opportunity Demo";
begin
DsSendOpportunity.DeleteOpportunity(Rec);
end;
}
}
}
}
}
```
Along with the configuration flag, this code adds the buttons to send and delete the opportunity in Dime.Scheduler.
### Create the handle opportunity codeunit
To write planning data received from Dime.Scheduler back into Business Central, you need a code unit that handles it. What you do with that data is up to you; here we create a task for each planned appointment.
Create `Opportunity/HandleOpportunity.codeunit.al`:
```al
codeunit 2088002 "DS Handle Opportunity Demo"
{
TableNo = "Dime DS Appointment";
trigger OnRun()
begin
PerformAllocation(Rec);
end;
local procedure PerformAllocation(DimeDSAppointment: Record "Dime DS Appointment")
var
DimeDSAppointmentResource: Record "Dime DS Appointment Resource";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
DimeDSSetup: Record "Dime DS Setup";
Opportunity: Record Opportunity;
ToDo: Record "To-do";
LineNo: Text;
begin
if not Opportunity.Get(DimeDSAppointment."Job No.") then
exit; // No Opportunity found, nothing to do
Evaluate(LineNo, DimeDSAppointment."Task No.");
DimeDSSetup.Get();
// New appointment: create new task + create link entry between Dime.Scheduler appointment and BC task
if DimeDSAppointment."Database Action" = 'I' then begin
DimeDSAppointmentResource.Reset();
DimeDSAppointmentResource.SetRange("Entry No.", DimeDSAppointment."Entry No.");
if DimeDSAppointmentResource.FindSet() then
repeat
if (DimeDSAppointment."Sent From Backoffice") then
InsertDSTodoLink(DimeDSAppointment, DimeDSAppointmentResource)
else
CreateToDo(DimeDSAppointment, DimeDSAppointmentResource, Opportunity);
until DimeDSAppointmentResource.Next() = 0;
end;
// Updated appointment: update task (lookup in link table)
if DimeDSAppointment."Database Action" = 'U' then begin
// First check if Resources planned in Dime.Scheduler are allocated in BC
DimeDSAppointmentResource.Reset();
DimeDSAppointmentResource.SetRange("Entry No.", DimeDSAppointment."Entry No.");
if DimeDSAppointmentResource.FindSet() then
repeat
if (DimeDSAppointment."Sent From Backoffice") then
UpdateDSTodoLink(DimeDSAppointment, DimeDSAppointmentResource)
else begin
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
DimeDSOrderLineLink.SetRange("Resource No.", DimeDSAppointmentResource."Resource No.");
if DimeDSOrderLineLink.FindFirst() then begin
UpdateTodo(DimeDSAppointment, DimeDSAppointmentResource, DimeDSOrderLineLink);
end else begin
CreateToDo(DimeDSAppointment, DimeDSAppointmentResource, Opportunity);
end;
end;
until DimeDSAppointmentResource.Next() = 0;
// Then check if resources already allocated in BC have been deleted in Dime.Scheduler
DimeDSOrderLineLink.Reset();
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
if DimeDSOrderLineLink.FindSet() then
repeat
DimeDSAppointmentResource.Reset();
DimeDSAppointmentResource.SetRange("Entry No.", DimeDSAppointment."Entry No.");
DimeDSAppointmentResource.SetRange("Resource No.", DimeDSOrderLineLink."Resource No.");
if DimeDSAppointmentResource.IsEmpty() then begin // Resource has been removed in Dime.Scheduler
if ToDo.Get(DimeDSOrderLineLink."Document No.") then
ToDo.Delete(true);
DimeDSOrderLineLink.Delete(true);
end;
until DimeDSOrderLineLink.Next() = 0;
end;
// Delete task and link entry between Dime.Scheduler appointment and BC task
if DimeDSAppointment."Database Action" = 'D' then begin
DimeDSOrderLineLink.Reset();
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
if DimeDSOrderLineLink.FindSet() then
repeat
if Todo.Get(DimeDSOrderLineLink."Document No.") then
Todo.Delete(true);
DimeDSOrderLineLink.Delete(true);
until DimeDSOrderLineLink.Next() = 0;
end;
end;
local procedure CreateToDo(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource"; Opportunity: Record Opportunity)
var
Salesperson: Record "Salesperson/Purchaser";
Todo: Record "To-do";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
DimeDSEvents: Codeunit "Dime DS Events";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
begin
if DimeDSAppointmentResource."Resource No." <> '' then
Salesperson.Get(DimeDSAppointmentResource."Resource No.");
Todo.Init();
Todo."Opportunity No." := Opportunity."No.";
Todo.Description := DimeDSAppointment.Subject;
Todo.Validate("Contact No.", Opportunity."Contact No.");
Todo.Insert(true);
Todo.Date := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment.Start);
Todo."Ending Date" := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment."End");
Todo."Salesperson Code" := Salesperson.Code;
Todo."Ending Time" := 0T;
Todo."Start Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment.Start);
Todo."Ending Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment."End");
Todo.Duration := DimeDSAppointment."End" - DimeDSAppointment.Start;
Todo.Modify(true);
DimeDSOrderLineLink.Init();
DimeDSOrderLineLink."Appointment Id" := DimeDSAppointment."Appointment Id";
DimeDSOrderLineLink."Resource No." := Salesperson.Code;
DimeDSOrderLineLink."Document No." := Todo."No.";
DimeDSOrderLineLink.Insert(true);
end;
local procedure UpdateTodo(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource"; DimeDSOrderLineLink: Record "Dime DS Order Line Link")
var
Todo: Record "To-do";
DimeDSEvents: Codeunit "Dime DS Events";
DimeDSDimeSchedulerMgt: Codeunit "Dime DS Dime.Scheduler Mgt.";
begin
if Todo.Get(DimeDSOrderLineLink."Document No.") then begin
Todo.Date := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment.Start);
Todo."Ending Date" := DimeDSDimeSchedulerMgt.DateTime2Date(DimeDSAppointment."End");
Todo."Salesperson Code" := DimeDSAppointmentResource."Resource No.";
Todo."Ending Time" := 0T;
Todo."Start Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment.Start);
Todo."Ending Time" := DimeDSDimeSchedulerMgt.DateTime2Time(DimeDSAppointment."End");
Todo.Duration := DimeDSAppointment."End" - DimeDSAppointment.Start;
Todo.Modify(true);
end;
end;
local procedure InsertDSTodoLink(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource")
var
Todo: Record "To-do";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
EntryNo: Integer;
begin
Todo.Get(DimeDSAppointment."Backoffice Id");
Evaluate(EntryNo, Todo."No.");
DimeDSOrderLineLink.Init();
DimeDSOrderLineLink."Appointment Id" := DimeDSAppointment."Appointment Id";
DimeDSOrderLineLink."Resource No." := CopyStr(DimeDSAppointmentResource."Resource No.", 1, 20);
DimeDSOrderLineLink."Document No." := DimeDSAppointment."Backoffice Id";
DimeDSOrderLineLink.Insert(true);
end;
local procedure UpdateDSTodoLink(DimeDSAppointment: Record "Dime DS Appointment"; DimeDSAppointmentResource: Record "Dime DS Appointment Resource")
var
Todo: Record "To-do";
DimeDSOrderLineLink: Record "Dime DS Order Line Link";
EntryNo: Integer;
begin
if not Todo.Get(DimeDSAppointment."Backoffice Id") then
exit;
Evaluate(EntryNo, DimeDSAppointment."Backoffice Id");
DimeDSOrderLineLink.SetRange("Appointment Id", DimeDSAppointment."Appointment Id");
DimeDSOrderLineLink.SetRange("Document No.", Todo."No.");
DimeDSOrderLineLink.FindFirst();
DimeDSOrderLineLink.Rename(DimeDSAppointment."Appointment Id", DimeDSAppointmentResource."Resource No.");
end;
}
```
Most of this is boilerplate. A task is created and then linked to the Dime.Scheduler appointment through the "Dime DS Order Line Link" table. Thanks to this staging table, later updates in either Dime.Scheduler or Business Central are reflected in the other system.
## Step 5: Build and run
Run the extension. Confirm that the source types are registered for both salespersons and opportunities.
### Send salespersons
- Hit ALT + Q and look for **Salespersons/Purchasers**
- Select one or more salespersons
- Navigate to the ribbon -> Actions -> Send to Dime.Scheduler
In Dime.Scheduler, you'll now have a list of resources with a new resource type of Salesperson.

### Send opportunity
1. Hit ALT + Q and look for **Opportunities** (list)
2. Open an opportunity
3. Click **Send to Dime.Scheduler** action
4. Check Dime.Scheduler to see the new task in the open tasks list

### Plan
- Drag and drop the new opportunity to a salesperson.
- Select the new appointment on the planning board, hit "L" on your keyboard, and open the opportunity card
- In the opportunity, go to the tasks list.
- You should see a new task that corresponds with the appointment you just created in Dime.Scheduler.

## Troubleshooting
See the [troubleshooting page](/integrations/bc/troubleshooting) for common issues with the integration to and from Business Central.
---
## Set up and test OAuth2
:::tip
This guide is a continuation of [the connector setup in Dime.Scheduler](/administration/connectors#oauth2) and its implementation in [Business Central](/integrations/bc/install/ds-to-bc#oauth2).
:::
This guide walks you through setting up a BC test environment with OAuth2 enabled, so you can use these authentication and authorization flows with Dime.Scheduler. It covers four parts:
## Prerequisites
- **Docker**
We wrote this guide using **Business Central on Docker** (see the [dedicated guide](/integrations/bc/guides/docker) to start a new instance). Running an entire ERP system in a Docker container is convenient, but it introduces a few challenges that we call out throughout this guide.
Feel free to use your own internal testing environment instead.
- **ngrok**
Because Business Central runs on Docker on our local machine, we need a way to expose the BC instance to Dime.Scheduler, which runs in the cloud. For that, we use a popular tunneling solution named [ngrok](https://ngrok.com/). ngrok's free tier gives you one static domain.
If you already have DNS set up, you don't need ngrok.
- **Extension for Dime.Scheduler in Business Central**
The extension for Dime.Scheduler in Business Central must be [installed](/integrations/bc/install/intro) prior to registering the MS Entra ID app in Business Central.
## Step 1: Create an MS Entra ID app on Azure
Create an MS Entra ID app on Azure with the following specifications:
- **Name**: Dime.Scheduler for MS Dynamics 365 Business Central
- **Redirect URI**: `https://{PublicWebBaseURL}/{INSTANCENAME}/OAuthLanding.htm`
- **Permissions** for Microsoft Dynamics 365 Business Central:
- `API.ReadWrite.All`
- `app_access`
- Create a **client secret** and copy its value along with the **client id** and **tenant id**. We'll be using this information in the next steps.
:::gotcha[BC on Docker]
The redirect URI must be reachable by Microsoft to complete the app registration consent. BC on your local Docker Desktop with an address like 'http://bc25/BC' is invalid (no SSL) and inaccessible (local).
For this guide, create a one-time tunnel using ngrok for the web client.
For example, this command starts a static domain (use your own) and forwards it to our local BC25 on Docker:
```bash
ngrok http --domain=sleeping-dolphin.ngrok-free.app http://bc25
```
ngrok returns a lot of information, but the value you need is in the 'Forwarding' field.
As long as you keep your terminal open, it forwards requests from https://sleeping-dolphin.ngrok-free.app to http://bc25:80.
Use your own free static domain that is assigned to your account on ngrok, and use the following template `https://yourstaticngrokdomain.ngrok-free.app/{INSTANCENAME}/OAuthLanding.htm` to populate the redirect URI field in the app registration.
:::
## Step 2: Register the MS Entra ID app in Business Central
In BC, create a matching record for the MS Entra ID app you just registered:
- **Client ID**: paste the client ID of the MS Entra ID app that you just created
- **Description**: Dime.Scheduler
- **State**: enabled
Select the following user permission sets:
- **DIME DS ADMIN**
- **D365 BASIC ISV**
Select 'Grant Consent' and approve the consent dialog.
:::gotcha[BC on Docker]
When running BC on Docker, you should grant consent on the same domain that you specified in the redirect URI in the MS Entra ID app.
Instead of granting consent on 'http://bc25/BC', you must open the same page on 'https://yourstaticngrokdomain.ngrok-free.app/bc'. Failure to do so will result in an error, indicating that the 'http://bc25/BC' URI is not available in the redirect URI list.
:::
## Step 3: Enable S2S authentication using OAuth2 in BC
On-prem instances of BC need some tweaking to enable server-to-server (S2S) authentication. See the [official documentation](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/automation-apis-using-s2s-authentication) for more information.
:::warning
This step is opinionated and illustrative: it gets you to a working solution. Proceed with caution if you're not using a Docker container.
:::
The PowerShell script below runs in the BC PowerShell Prompt. We based it on [this blog post](https://www.kauffmann.nl/2022/02/22/configuring-business-central-for-azure-active-directory-authentication-and-oauth-1/) and updated it for Business Central 25. Change the variable values, then run the script in the BC administration shell:
```powershell
# Provide some basic info about your BC environment:
$BcBaseUrl = 'https://your_url_to_bc/'
$BCServerInstanceName = 'your_bc_instance'
# Provide the info about the MS Entra ID app that you created on Azure:
# 1. Application (client) ID
$ClientId = 'your_ms_entra_id_app_client_id'
# 2. Directory (tenant) ID
$TenantId = 'your_azure_tenant_id'
# 3. Application ID URI
$AppIdUri = 'your_ms_entra_id_app_id_uri'
# ---------------------------------------------
# NO MORE VARIABLES TO MODIFY BEYOND THIS POINT
# ---------------------------------------------
# Configuration for the WSFederationLoginEndpoint
$BCServerInstanceUrl = $BcBaseUrl + $BCServerInstanceName
$LandingUrl = $BCServerInstanceUrl + "/OAuthLanding.htm"
$WSFedEndpoint = "https://login.microsoftonline.com/$TenantId/wsfed?wa=wsignin1.0&wtrealm=$AppIdUri&wreply=$LandingUrl"
# Configure BC Server
$BCConfig = @{
"PublicWebBaseURL" = $BCServerInstanceUrl
"ValidAudiences" = "$ClientId;https://api.businesscentral.dynamics.com"
"ADOpenIdMetadataLocation" = "https://login.microsoftonline.com/$TenantId/.well-known/openid-configuration"
"ClientServicesCredentialType" = "NavUserPassword"
"WSFederationLoginEndpoint" = $WSFedEndpoint
}
foreach ($key in $BCConfig.Keys) {
Set-NAVServerConfiguration -ServerInstance $BCServerInstanceName -KeyName $key -KeyValue $BCConfig[$key]
}
# Configure web server
$WebConfig = @{
"AadApplicationId" = $ClientId
"AadAuthorityUri" = "https://login.microsoftonline.com/$TenantId"
}
foreach ($key in $WebConfig.Keys) {
Set-NAVWebServerInstanceConfiguration -WebServerInstance $BCServerInstanceName -KeyName $key -KeyValue $WebConfig[$key]
}
Restart-NAVServerInstance -ServerInstance $BCServerInstanceName
```
With this configuration in place, BC now supports S2S authentication.
## Step 4: Create a connector entry in Dime.Scheduler
:::note
This step is standard Dime.Scheduler functionality, and is documented in [configure the connector](/integrations/bc/install/ds-to-bc#step-3-setting-up-the-connector-dimescheduler).
:::
The last task is to [create a connector in Dime.Scheduler](/administration/connectors).
In the Authentication section of the form, fill out the following fields:
| Field | Value | How to find it in MS Entra ID |
| ----------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| **Authentication type** | MS Entra ID | |
| **Tenant ID** | The Azure tenant ID | Field **Directory (tenant) ID** in the overview page of the app registration. |
| **Client ID** | The app registration's client ID | Field **Application (client) ID** in the overview page of the app registration. |
| **Client Secret** | The app registration's client secret | Create a secret in the **'Certificates and secrets'** blade. Copy the secret **Value** field, _not_ the client secret ID! |
Save the record. You've now set up the connector for Microsoft Dynamics 365 Business Central using OAuth2.

Don't forget to match the source app with the configuration in BC. The source app must be the same as the value that's specified in the Dime.Scheduler FastTrack Wizard in Business Central. The source app identifies which back-office system resources, tasks and appointments belong to.

Dime.Scheduler and Business Central are now able to communicate back and forth securely with OAuth2.
:::tip[BC on Docker]
To test whether the OAuth2 authentication flow works, create a tunnel **for the API** (not the web client) if you're running BC locally on Docker.
For example, this command starts a static domain (use your own) and forwards it to our local BC25's API port on Docker:
```bash
ngrok http --domain=sleeping-dolphin.ngrok-free.app http://bc25:7048
```
Note how this time we target port 7048 rather than port 80. As a result, the API pages of Dime.Scheduler in BC will now be available publicly on `https://YOURTUNNEL/BC/api/dimeSoftware/dimeScheduler/v1.0/companies(YOURCOMPANYID)/appointments`.
:::
---
## Overview
import DocCardList from '@theme/DocCardList';
Guides for partners integrating Business Central with Dime.Scheduler. The reference pages
under [Installation](/integrations/bc/install/intro) and [Configuration](/integrations/bc/config/intro)
tell you what each setting does; these guides show you the order to do things in and what
tends to go wrong.
If you have never connected the two systems before, the
[quick start guide](/integrations/bc/guides/docker-quickstart) is a slide deck that takes
you from an empty BC container to your first appointment. It is the fastest way to see the
whole shape of the integration before you commit to a customer environment.
From there:
- **Building a sandbox.** [Running a container-based development environment](/integrations/bc/guides/docker)
sets up a local BC instance on Docker that you can break safely.
- **Authentication.** [Set up and test OAuth2](/integrations/bc/guides/oauth2) continues where
the connector setup leaves off, and is worth doing in a sandbox first.
- **Matching resources to work.** [Use dimensions for filter groups](/integrations/bc/guides/use-dimensions-for-filtergroups)
turns BC dimensions into the qualifications and requirements Dime.Scheduler filters on.
- **Going further than the standard extension.** [Extending the Dime.Scheduler extension](/integrations/bc/guides/extend-the-extension)
is the deep end: events, custom tables and your own AL code.
---
## Quick start guide to Dime.Scheduler for Business Central
Connect Business Central and Dime.Scheduler from start to finish. This guided slide deck takes you from spinning up a BC Docker container all the way to creating your first appointment.
Open the slide deck →
Watch the video
## What you'll learn
- Create a new instance of Business Central running on a Docker container
- Create an instance of Dime.Scheduler cloud
- Connect BC to Dime.Scheduler
- Connect Dime.Scheduler back to BC
- Plan your first appointment
## Who this guide is for
This guide suits anyone comfortable with applications like Business Central and Dime.Scheduler, and with tools like PowerShell and Docker:
- Implementation consultants
- System and network administrators
- Solution architects
- Software developers
## Requirements
- [PowerShell Core](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4)
- [Docker Desktop (Windows containers)](https://www.docker.com/products/docker-desktop/)
- [ngrok](https://ngrok.com/docs/getting-started/?os=windows)
- A valid `.bclicense` file
- Administrative rights to your computer
For deeper background on the Docker setup, see [Running a container-based development environment](/integrations/bc/guides/docker).
---
## Establishing a connection with Dime.Scheduler
import Modules from '@site/src/components/docs/bc/Modules';
The FastTrack wizard is Business Central's way of answering one question: which Dime.Scheduler instance should this company talk to, and how should it identify itself? Once you've answered that on one page, the rest of BC's integration knows where to send its traffic, and the wizard moves on to the easy part - ticking off the modules you want to plan.
:::tip
Open the wizard by searching for `Dime.Scheduler FastTrack Wizard` in Business Central.
:::
## Identifying this BC company
A single Dime.Scheduler installation can receive data from many places at once: multiple BC companies, multiple BC tenants, even other ERPs. For that to work without anyone stepping on anyone else, every record that arrives at Dime.Scheduler is tagged with a **source application code** (`SourceApp`) - a short label that says "this came from over here, send any planning answers back to over here."
The wizard generates that code from the company name; you can change it to anything you like. The one rule is non-negotiable: **if two companies, databases, or applications share a Dime.Scheduler tenant, they must use different `SourceApp` codes**. Reuse one across two sources and Dime.Scheduler can no longer tell their records apart.
:::tip
When in doubt, keep the default.
:::
## Picking the right Dime.Scheduler
Dime.Scheduler is offered on two hosted environments: **Production** (`app.dimescheduler.com`) and **Sandbox** (`sandbox.app.dimescheduler.com`). Production is the paid environment where most customers live; Sandbox is a free environment for trials, training, and rehearsing a setup. If you're not sure which one you have, you almost certainly have Production - the **environment** field on the wizard is how you tell BC which one to talk to.
This is the *Dime.Scheduler* environment, not BC's. The two are unrelated - a sandbox BC company can talk to production Dime.Scheduler, and the reverse is just as common. Pick the wrong one here and the wizard will happily push your data into the other one, so it's worth a second look before you click on.
## How BC authenticates
Dime.Scheduler accepts two flavors of credential. Use the first one.
### API key (recommended)
API keys are the modern, scoped, revocable way for one system to authenticate against another. Create one in Dime.Scheduler, paste the value into the wizard, and you're done: no usernames, no passwords, and you can rotate or revoke the key later without disturbing any user account.
:::tip[Create your key]
[Production keys](https://app.dimescheduler.com/keys) ·
[Sandbox keys](https://sandbox.app.dimescheduler.com/keys)
:::
### User / password
:::warning[Deprecated]
Use API keys instead. This method still works but is no longer recommended.
:::
If you do go this route, the credentials must belong to a **`Forms` user** in Dime.Scheduler. Microsoft Entra ID users aren't supported by the integration services that handle this traffic.
## Seeding the return path
The wizard only sets up the *outbound* channel - BC pushing to Dime.Scheduler. For the planning to flow *back*, Dime.Scheduler needs its own connector that knows how to reach this BC instance. You'll fill in the credentials for that connector on the [next page](/integrations/bc/install/ds-to-bc), but the wizard offers to save you a few clicks by pre-creating the entry for you, already wired up to the right source app and environment.
Leave the **seed a connector entry** box ticked (recommended). On **Business Central Cloud**, also fill in the **time zone** field - Dime.Scheduler publishes appointments in UTC, and this is how it knows to translate them into your local time on the return trip.
## Trying it
Click **Next**. The wizard immediately calls Dime.Scheduler with the credentials you supplied, and tells you what happened.
- **Success** means the outbound channel is live. If you ticked seed-a-connector-entry, a half-finished connector entry is now waiting for you on the Dime.Scheduler side, ready for its return-path credentials.
- **Failure** is almost always one of three things: the API key is wrong, the wrong environment is selected, or the BC instance can't reach the Dime.Scheduler URL at all (firewall, DNS, proxy). Recheck the settings; if they look right, escalate to whoever maintains your Dime.Scheduler tenant.
## Next: configure modules
The connection is live. Now choose which modules to plan - click a module below, or head to the [modules overview](/integrations/bc/modules).
:::info
FastTrack is a quick-start utility. You can extend the standard extension to fit your vertical or custom solution - [here's an example](/integrations/bc/guides/extend-the-extension).
:::
---
## Establishing a connection with BC
The FastTrack wizard taught Business Central how to talk to Dime.Scheduler. This page is the other half of that conversation: it teaches Dime.Scheduler how to talk *back*. Concretely, you create a **connector** in Dime.Scheduler that knows two things - where to find this BC instance (a web service URL) and how to authenticate against it (a credential). With those, every appointment you draft on the planning board can be written into BC the moment you save it.
:::note[Open the connector setup]
[Production](https://app.dimescheduler.com/connectors) ·
[Sandbox](https://sandbox.app.dimescheduler.com/connectors)
:::
:::tip
This page only covers the BC-specific bits. For the rest of what connectors can do, see the [connector setup docs](/administration/connectors).
:::
## Where to reach BC
The Dime.Scheduler extension publishes its appointment endpoint in two flavors - a classic **SOAP** web service and a modern **REST** API page - and which one you use is mostly historical preference. Pick one in the connector's **`API Type`** field, then paste the matching URL below.
### SOAP
In BC, open the **Web Services** page, find the **Dime.Scheduler Appointment Card** page, and copy its SOAP URL. The shape is:
`https://{YOUR_URL_TO_BC}:7047/WS/Page/Appointment`
### API page (REST)
The REST URL follows this format:
`https://{YOUR_URL_TO_BC}/api/dimeSoftware/dimeScheduler/v1.0/companies({COMPANY_ID})/appointments`
Two placeholders: the URL to BC itself, and the **company ID** (the company GUID, which BC will show you on the company card):
Paste the finished URL into a browser to make sure BC responds before you save the connector. For **BC Cloud**, the shape is slightly different:
`https://api.businesscentral.dynamics.com/v2.0/{USER_DOMAIN_NAME}/{ENVIRONMENT_NAME}/api/dimeSoftware/dimeScheduler/v1.0/companies({COMPANY_ID})/appointments`
Here, `{USER_DOMAIN_NAME}` is the Entra ID tenant GUID, `{ENVIRONMENT_NAME}` is the BC environment's display name, and `{COMPANY_ID}` is the company GUID from above.
## How to authenticate
### Which method?
On-premises BC accepts three options; cloud BC only accepts one. The pragmatic story is short: Microsoft has deprecated everything except OAuth2, and even on-prem the REST API page won't talk to Windows authentication at all. Unless you have a hard reason otherwise, **use OAuth2**.
| Method | SOAP Web Services | API page |
| ----------------------------------- | ----------------- | -------- |
| **Basic authentication** | ✅ | ✅ |
| **Windows auth: NTLM and Kerberos** | ✅ | ❌ |
| **OAuth2** | ✅ | ✅ |
For **BC online (Cloud)**, OAuth2 is the *only* option.
### Basic / Windows (on-premises)
These two configure the same way: in the connector's Authentication section, pick the type and enter the credentials of a BC **service account** Dime.Scheduler can use.

### OAuth2 with Microsoft Entra ID {#oauth2}
OAuth2 is more involved because it doesn't rely on a single password; it relies on an **app registration** in Microsoft Entra ID that Dime.Scheduler can use to request tokens on its own behalf. The setup has three places you need to touch, in this order:
1. Register an app in Entra ID. This is the identity Dime.Scheduler will use.
2. Tell Business Central that this app is allowed in, and which permission sets it has.
3. Tell Dime.Scheduler where that app lives and how to prove it owns it.
:::info[Official docs]
Microsoft's [register-an-app guide](https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/register-app-azure) covers the Entra ID side in depth.
:::
:::tip[Try it first]
We have a [walkthrough for setting up OAuth2 between Dime.Scheduler and BC on-premises via Docker](/integrations/bc/guides/oauth2) - a safe way to rehearse before touching your test or production environment.
:::
#### Step 1: Register the app [Microsoft Entra ID]
1. Sign in to the [Azure portal](https://portal.azure.com).
2. Register an application in your Entra ID tenant:
- Give it a name, e.g. *Dime.Scheduler for Business Central*.
- Select the supported account type.
3. Add these **Application** API permissions:
- Dynamics 365 Business Central: `app_access`
- Dynamics 365 Business Central: `API.ReadWrite.All`
4. Grant admin consent. The result should look like this:

#### Step 2: Register the app in BC [Business Central]
The app exists in Entra ID, but BC won't let it through the door until it's listed on the BC side too. You'll need the app's **application (client) ID**, **directory (tenant) ID**, and a **client secret**.
1. Search for **Microsoft Entra Applications** (or *Azure AD applications* on older versions) and open it.
2. Create a new application:
- Paste the **client ID** into the Client ID field.
- Add a description, e.g. *Dime.Scheduler*.
3. Assign permission sets to the company you're connecting:
- `D365 Basic ISV`
- `Dime.Scheduler`
- plus any set needed to reach your custom objects
#### Step 3: Configure the connector [Dime.Scheduler] {#step-3-setting-up-the-connector-dimescheduler}
The final piece: the **client secret**. In the Entra ID app, go to **Certificates & secrets**, click **New client secret**, and copy the value the instant it appears - you can't read it again later.

Then [create the connector in Dime.Scheduler](/administration/connectors) and fill in the Authentication section:
- **Authentication type**: MS Entra ID
- **Tenant ID**: the app's **Directory (tenant) ID**
- **Client ID**: the app's client ID
- **Client Secret**: the secret you just copied
Save it, and the OAuth2 connector is live.

## Source App
One last field ties everything together. The **source app** on this connector must match the value you set in the FastTrack Wizard back in Business Central - it's how Dime.Scheduler knows that an appointment headed *out* through this connector belongs to the same back-office system whose records came *in* under that source app.
If those two values don't match, the integration runs fine in isolation but the loop never closes: appointments leave Dime.Scheduler and end up nowhere BC can see them.

---
## Installing the extension
The Dime.Scheduler extension is a BC app that adds everything a stock Business Central installation is missing in order to talk to Dime.Scheduler: a setup page, the codeunits that publish and consume data, the appointment pages, and the FastTrack Wizard. Until it's installed, the rest of the integration has nothing to attach to.
How you install it depends on where BC runs:
- **On-premises**: an `.app` file you publish to the BC server with PowerShell. You're in charge of versions, and you can hold back upgrades to match your release schedule.
- **Cloud**: an AppSource listing. Microsoft handles delivery, and updates roll out with the rest of your BC environment.
Jump to your scenario below.
## On-premises (PowerShell)
You have two routes: our installation script (the easy path) or plain PowerShell commands (the explicit one). Either way, you run them in the **Business Central Administration Shell**.
### Option 1: the installation script (recommended)
The script does three things for you: downloads the right `.app` for the BC wave you specify, verifies its signature, and runs `Publish`, `Sync` and `Install` in the right order. We maintain it on the [PowerShell Gallery](https://www.powershellgallery.com/packages/DimeScheduler.InstallBCExtension) as `DimeScheduler.InstallBCExtension.ps1`.
**Install it:**
```powershell
Install-Script -Name DimeScheduler.InstallBCExtension
```
**Find where it landed:**
```powershell
(Get-InstalledScript -Name "DimeScheduler.InstallBCExtension").InstalledLocation
```
:::tip
Prepend `cd` to drop your terminal straight into that folder, and chain the install in one line:
```powershell
cd (Get-InstalledScript -Name "DimeScheduler.InstallBCExtension").InstalledLocation; & .\DimeScheduler.InstallBCExtension.ps1 -serverInstance BC -version 24
```
:::
**Run it.** The script takes these parameters:
| Parameter | Description | Required |
| ---------------- | -------------------------------------------------------------------------------------------- | ---------------------------- |
| serverInstance | The BC instance's name | ✅ |
| version | The major version of the BC wave (e.g. 14, 22, 23, 24) | Either `-version` or `-path` |
| path | The full path of the Dime.Scheduler .app file | Either `-version` or `-path` |
| skipVerification | Switch `-skipVerification` to skip verification. Dev/testing only. | ❌ |
Use **`-version`** when you want the latest extension for that BC wave - the script downloads it for you:
```powershell
.\DimeScheduler.InstallBCExtension.ps1 -serverInstance BC -version 24
```
Use **`-path`** when you've already got the `.app` (a specific version, a private build, or an offline server):
```powershell
.\DimeScheduler.InstallBCExtension.ps1 -serverInstance BC -path C:\dsfiles\Dime_Dime.Scheduler_2.3.0.3.app
```
### Option 2: plain PowerShell
The script is a wrapper around three native BC cmdlets - so if you'd rather see exactly what runs, here it is:
```powershell
Publish-NAVApp -ServerInstance BC -Path ".\Dime_Dime.Scheduler_x.x.x.x.app"
Sync-NavApp -ServerInstance BC -Name "Dime.Scheduler"
Install-NAVApp -ServerInstance BC -Name "Dime.Scheduler"
```
Replace `x.x.x.x` with the version you're installing. On a multi-tenant server instance, add a `-Tenant` parameter to the second and third commands. The third command is optional: you can also [install the app from the client](https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-how-publish-and-install-an-extension-v2#to-install-an-extension-by-using-the-client) instead.
Within moments the Dime.Scheduler extension appears in BC's extension list:
## Cloud (AppSource)
Cloud installations don't run PowerShell - everything goes through [AppSource](https://appsource.microsoft.com/en-us/product/dynamics-365-business-central/PUBID.dimenics-3435744%7CAID.dime-scheduler%7CPAPPID.f15bbe6d-c2a3-4828-aa98-5a77cff4cd41), Microsoft's marketplace for Business Central apps.
### Find it
Open the [AppSource listing](https://appsource.microsoft.com/en-us/product/dynamics-365-business-central/PUBID.dimenics-3435744%7CAID.dime-scheduler%7CPAPPID.f15bbe6d-c2a3-4828-aa98-5a77cff4cd41) directly, or search [AppSource](https://appsource.microsoft.com/) for *Dime.Scheduler*:
Open the card, click **Get it now**, tick the checkbox, and continue:
### Install it
AppSource shows you every Business Central environment your account can reach. Pick the one that should host the connector:

Choose a language, and AppSource hands the deployment off to BC:

The actual install runs in the background. Track it from the Extension Management page - it usually finishes in a minute:

## Next
The extension is installed, but it isn't connected to anything yet. The next step is the FastTrack Wizard, which uses the pages and codeunits you just deployed to tell BC *which* Dime.Scheduler instance to talk to. [Connect Business Central to Dime.Scheduler](/integrations/bc/install/bc-to-ds).
---
## Microsoft Dynamics 365 Business Central Connector
Business Central holds the work and the resources. Dime.Scheduler holds the plan. Connecting the two is the moment those two pictures stop being separate: jobs, tasks and resources from BC become *plannable things* inside Dime.Scheduler, and the planning you do there flows back so dispatchers, accountants and the resources themselves all see one consistent reality.
Because both sides need to push information to each other, the integration runs as **two independent channels**, one in each direction. You set each one up once and the loop closes.
## The three required steps
A working two-way integration takes three moves:
1. **Install the extension** in Business Central. This is the codeunit and pages that know how to speak Dime.Scheduler's language - without it, BC has nothing to say. [How](/integrations/bc/install/extension)
2. **Connect BC → Dime.Scheduler** with the FastTrack Wizard. You point BC at a Dime.Scheduler tenant, authenticate, and pick which modules (jobs, service, assembly, manufacturing, ...) should travel. [How](/integrations/bc/install/bc-to-ds)
3. **Connect Dime.Scheduler → BC** by adding a connector that knows where to reach this BC instance and how to authenticate against it. This is what lets the planning come back. [How](/integrations/bc/install/ds-to-bc)
## What real deployments add
A trial works with the three steps above. Anything you intend to run on real data goes on to tune how the connector behaves, which is what the [configuration](/integrations/bc/config/intro) section covers: safeguards against deleting planned work, a [scheduled job](/integrations/bc/config/queue) that keeps master data fresh, and an [outbound queue](/integrations/bc/config/outbound-queue) that logs and optionally buffers everything BC sends. None of it is required to get running, and all of it is off by default.
Beyond the connector itself, budget for **permission tuning** so the service accounts on either side have exactly the access they need, and no more.
:::partner[Check this first]
The connector works on every supported BC version, but specifics differ between waves. Confirm yours against the [requirements](/integrations/bc/requirements) before you start - anything version-specific is flagged inline.
:::
import DocCardList from '@theme/DocCardList';
---
## Overview(Bc)
import Modules from '@site/src/components/docs/bc/Modules';
import ReactPlayer from 'react-player'

## How it works
Your orders and resources already live in Business Central. What Business Central does not give you is a planning surface: a place to see who is available, drag work onto a timeline, and commit to a schedule. That is the gap the back office connector closes.
The connector keeps Microsoft Dynamics 365 Business Central and Dime.Scheduler in sync, both ways. Business Central stays the system of record and feeds the data (resources, orders); Dime.Scheduler does the planning and sends the resulting appointments straight back to Business Central. The planner never re-keys an order, and Business Central never falls out of step with what was actually scheduled.
## Installation
Once your Dime.Scheduler instance is up and running, four pieces have to meet: the extension inside Business Central, the link from BC to DS, the link back from DS to BC, and (optionally) the security and background processing that make the connection production-ready. Work through them in order:
1. Install the extension in Microsoft Dynamics 365 Business Central.
2. Connect BC to DS by running the FastTrack Wizard in Microsoft Dynamics 365 Business Central.
3. Connect DS to BC by adding a connector in the multi-back-office setup in Dime.Scheduler that points to the desired BC instance.
The last two steps are optional, but skip them and you are running without authentication and pushing data synchronously. For anything beyond a quick trial, configure both:
4. Configure security in Microsoft Dynamics 365 Business Central.
5. Configure the Job Queue in Microsoft Dynamics 365 Business Central.
The result is the following workflow:
## Modules
Dime.Scheduler taps into many sources in Business Central rather than forcing your data through one narrow pipe. The extension is versatile and extensive without compromising on usability, and it builds on standard Business Central solutions wherever possible. That keeps the connector lightweight and extensible, and means it does not break existing functionality you already rely on.
Each module covers one corner of Business Central you can plan from. Click a card to learn how that module maps into Dime.Scheduler and to start planning:
## Quick start
The fastest way to see the two-way connection for yourself is to run Business Central locally. The [Docker quickstart guide](/integrations/bc/guides/docker-quickstart) walks you through running Business Central in a Docker container and establishing a two-way connection with Dime.Scheduler, so you can watch data move both ways before touching a real environment.
---
## Lifecycle policy
Use this page to check which versions of Microsoft Dynamics 365 Business Central the Dime.Scheduler connector supports. The extension follows the same lifecycle as [Microsoft Dynamics 365 Business Central](https://learn.microsoft.com/en-us/lifecycle/products/dynamics-365-business-central-onpremises-modern-policy).
## On-prem
The table below lists the supported on-prem versions of Microsoft Dynamics 365 Business Central, along with whether each version is still under active development.
| Version | Supported | Active development | End Date |
| -------------------------------------- | --------- | ------------------ | ---------------- |
| 2025 release wave 2, version **27**.x | ✅ | ✅ | Apr 6, 2027 |
| 2025 release wave 1, version **26**.x | ✅ | ✅ | October 13, 2026 |
| 2024 release wave 2, version **25**.x | ✅ | ✅ | Apr 4, 2026 |
| 2024 release wave 1, version **24**.x | ✅ | ✅ | Oct 7, 2025 |
| 2023 release wave 2, version **23**.x | ✅ | ❌ | Apr 2, 2025 |
| 2023 release wave 1, version **22**.x | ✅ | ❌ | |
| 2022 release wave 2, version **21**.x | ✅ | ❌ | |
| 2022 release wave 1, version **20**.x | ✅ | ❌ | |
| 2021 release wave 2, version **19**.x | ✅ | ❌ | |
| 2021 release wave 1, version **18**.x | ✅ | ❌ | |
| 2020 release wave 2, version **17**.x | ✅ | ❌ | |
| 2020 release wave 1, version **16**.x | ✅ | ❌ | |
| 2019 release wave 2, version **15**.x | ✅ | ❌ | |
| 2019 Spring release, version **14**.x | ✅ | ❌ | |
| 2018 October release, version **13**.x | ✅ | ❌ | |
## Cloud
Microsoft Dynamics 365 Business Central cloud is supported via the published extension on AppSource.
## Read more
- [Requirements](/integrations/bc/requirements)
- [Installation](/integrations/bc/install/intro)
- [The Business Central connector](/integrations/bc/intro)
---
## Common actions
Nothing in Dime.Scheduler can be planned until it exists in Dime.Scheduler, and getting it there is the whole point of the back-office connector. In Microsoft Dynamics 365 Business Central, that handoff happens through a small set of actions wired onto entity cards and onto the lists those entities live in. Learn these once and they behave the same everywhere they appear.
On a **list**, you work in bulk with two actions: **Send selection** (push only the rows you have selected) and **Send all** (push the entire list). Both are available on the following pages:
- [Resources](/integrations/bc/modules/resource)
- [Sales orders](/integrations/bc/modules/salesorder)
- [Service orders](/integrations/bc/modules/serviceorder)
- [Jobs](/integrations/bc/modules/job)
- [Assembly orders](/integrations/bc/modules/assemblyorder)
- [Work centers](/integrations/bc/modules/workcenter)
- [Absences](/integrations/bc/modules/hr)
- [Production orders](/integrations/bc/modules/production)
On a **card**, you work with a single record through two actions: **Send entity** and **Delete entity**.
## Send record
You can send a record to Dime.Scheduler at any time, and you do not have to track whether it is new. If the record does not exist yet it is created; if it already does, it is updated. That makes the send action safe to repeat whenever something changes in BC.

With the default behavior, each record in the tasks grid becomes a task in Dime.Scheduler.
## Send selected or all records
When you would rather push many records at once than open each card, work from the list. Click **More Options**, then **Actions**, and in the Dime.Scheduler menu choose to send the selected records or send all of them.

## Delete record
Sending is reversible: you can pull an item back out of Dime.Scheduler even after it was sent earlier. Dime.Scheduler finds the right item to remove from the combination of `SourceApp`, `SourceType`, and the entity's `No.` field.
What happens next depends on whether anything has been planned against it. If no appointments exist, the action runs cleanly and removes the item and its associated tasks from Dime.Scheduler. If appointments have been planned, you may get an error telling you a planning exists, which is a deliberate guard against deleting work out from under a planner. To override that guard, switch off the `Check Appointment on Delete` flag on the Dime.Scheduler Setup page in BC.

Keep two things straight about what delete does and does not touch. Removing an item from Dime.Scheduler does not remove it from BC. The reverse is also true: removing an item in BC does not remove the corresponding item in Dime.Scheduler. That second case is the one to watch, because deleting an item in BC without also removing it from Dime.Scheduler leaves behind phantom jobs, and Dime.Scheduler cannot write planning data back to a job that no longer exists in BC.
---
## Assembly Order
import ReactPlayer from 'react-player'
An assembly order in Business Central tells you *what* to build, but says nothing about *who* builds it or *when* they are free to do it. The FastTrack solution for assembly order planning closes that gap: you schedule the resources that build the order on the Dime.Scheduler planning board, and Business Central stays in sync the whole way.
That sync runs in both directions, which is the part planners care about. Drag a task onto a resource and the matching resource line appears on the assembly order in BC; as the schedule fills in, the starting, ending and due dates on the header follow the first and last planned task automatically, so the consultant never has to retype a date that was already decided visually. For assemble-to-order, that even reaches the linked sales order, where the shipment date is updated to match. And because the link is two-way, resource lines you create on the assembly order in BC *before* sending it over arrive in Dime.Scheduler as planned tasks, so you can pre-assign resources up front and let the board take it from there. A single order can carry as many resources as the work needs, spread across whatever dates and times suit them.
## Setup

### Enable assembly orders solution
Select `Enable Assembly Order planning` to turn on the FastTrack solution.
### Configure standard assembly orders
Three settings tune the default behavior:
- `Default Duration Assembly Ord.` sets how long a planned task runs when you drag it from open tasks onto a resource on the planning board. Leave it and a task defaults to 1 hour; enter a different value to change that.
- `Update Dates` writes the schedule back to BC. With it on, the starting, ending and due date on the assembly header are derived from the first and last date of the planned tasks in Dime.Scheduler. When the order is linked to a sales order (assemble-to-order), the shipment date on the corresponding sales line is updated too.
- `Send Resources with Order` turns resource lines that already exist on the assembly order in BC into planned tasks in Dime.Scheduler when you send the order over.
## Usage
Watch the video below for a short introduction of this module in Dime.Scheduler:
### Actions
The default actions on this page have been extended with the items below.
For the list actions on this type, see [common actions](/integrations/bc/modules/actions).
On the assembly order card itself, two actions are added:
- Send order
- Delete order

### Creating assembly orders
The mechanism is straightforward: create an assembly order, run the send to Dime.Scheduler action, and the connector gathers the relevant data about the order and its work items and ships it over. The following entities feed into the graph:
- Assembly header
- Assembly lines
- Sales header
- Assemble to order
- Customer (sell and bill)
The two sources play different roles. The sales order populates the parts of the job that describe the destination, such as the ship-to fields; the remaining fields come from the assembly header. Once the order arrives, the tasks are created.

The module can also send resources to Dime.Scheduler as appointments, provided the `Send resource with order` setup flag is switched on. With it on, Dime.Scheduler automatically creates appointments (planned tasks) for the resource lines already on the assembly order, so the work you pre-assigned in BC lands on the board ready to go.
Global setup tunes the rest, including the `default duration assembly order` that sets the default duration of an appointment when you drag the task to the planning board.
### Planning assembly orders
The open task grid lists the work that still needs a slot. Planning an assembly item is a single gesture: drag the task onto the right timeslot for the right resource, and the assembly order is registered.
Behind the scenes, a **resource line** for the assembly order is created automatically in Business Central. This record holds the information of the appointment you just planned, and the two stay linked, so any change to the sales order appointments is reflected immediately in Business Central.

Switch on the `Update Dates` setup flag and the dates on the assembly header follow the schedule as well. The fields that are updated are:
- Starting date
- Ending date
- Due date
If the record is assembled to order, the sales order is reopened and the shipment date is updated with the latest date.
This way, Dime.Scheduler builds on standard Business Central functionality and adds visual scheduling on top of it.
---
## Filter value sources
A filter value only does its job if it actually reaches Dime.Scheduler. Document filter value sources are how you get it there: they tell Business Central where to look, and how to read, the filter values for every task and resource the connector sends. Without them, a filter group like 'Skills' or 'Department' is just an empty container. With them, BC walks the data graph behind each document and fills that container automatically.
:::note
Tasks and resources are generic terms within Dime.Scheduler and do not reflect any specific table in BC. As such, you can have multiple sources for both resources and tasks.
:::
The setup is deliberately flexible about what happens when data is missing. You can set a default value and then override it once data is retrieved from tables further down in the hierarchy, which lets you set a value when nothing is defined in BC and a different value when something is. For instance, you can return "No" as the default when no skills are found for a task or resource, and "Yes" when skills are found.
## Declare resource and task filter value sources
In Business Central, hit `ALT + Q` and open the 'Dime.Scheduler Doc. Filter Value Sources' administration page. Each row is a master source: it names a BC table and tells the connector whether that table feeds a task or a resource, and which fields identify it.
| Field, tab or group | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Table No. | Specifies the table number that is the source of the task or resource. |
| Comment | Free text for internal use. |
| Entity Type | Specifies whether the table refers to a task or resource in Dime.Scheduler. |
| Resource Key Field | Specifies the field that identifies the resource when `Entity Type = Resource`. This is the field sent to Dime.Scheduler as `ResourceNo`. |
| Job Key Field | Specifies the field that identifies the job when `Entity Type = Task`. This is the field sent to Dime.Scheduler as `JobNo`. |
| Task Key Field | Specifies the field that identifies the task when `Entity Type = Task`. This is the field sent to Dime.Scheduler as `TaskNo`. |
Because a task or resource is not tied to one BC table, you can declare as many sources as you need. The example below is part of the setup that ships with the standard connector: resources are pulled from the resources table as well as the work and machine centers, while planning is done for service item lines, sales lines, and so on.

Declaring the master source is only the starting point. For every filter group the entity has values for, you then specify the **source(s)** and **rules** that fetch those values. You can link to any number of tables at any hierarchical depth, and for each table link you define how to retrieve its records and which filters and conditions apply.
## Tables
In the ribbon of the 'Dime.Scheduler Doc. Filter Value Sources' list, select the 'Tables' menu item. This is a hierarchical list. The top-level item is the entity's base table and is created automatically. Each nested level underneath digs deeper into the entity's graph to declare more specific filter value sources, and Dime.Scheduler explores the entire graph when it builds filters for resources and tasks.
For each table you declare which table comes next and how its records are read:
| Field, tab or group | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Link to Table No. | Specifies the table number of the next table in the hierarchy. |
| Method | Specifies how the table is to be read: **Match**: Retrieves the record with an exact match based on the links and filters. **First**: Retrieves the first record matching the criteria of the links and filters. **Last**: Retrieves the last record matching the criteria of the links and filters.**All**: Retrieves all records matching the criteria of the links and filters. |
Each table in the hierarchy is then shaped by four building blocks, covered in their own sections below:
- **Links** are the field(s) that connect the selected table with its parent. This is essentially a declarative relationship builder that defines how to navigate across the graph. For instance, you'll typically link the `Document No.` field of a _line_ with the `No.` field of a _header_.
- **Filters** limit the records retrieved from the selected table. For instance, you can generate a filter that only retrieves records that are not blocked.
- **Conditions** are filters on the parent table that decide whether the link applies at all. For instance, only link the line table (the parent) with the item table (the child) when the line type is of type item.
- **Fields** are the fields that supply a filter value. Here you can also specify default values and override behavior.
:::info
Links, filters and conditions cannot be defined for base tables.
:::
## Links
On this page, hit `ALT + Q` and look for 'Links', or select the 'Links' item in the ribbon. A link pairs a field on the parent with a field on the child so BC knows how to join the two; you can specify any number of links between them.
| Field, tab or group | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Link from Field No. | Specify the field from the parent table. |
| Link to Field No. | Specify the field from the child table to connect it with the field from the parent table (link from field no.). |
## Filter
On this page, hit `ALT + Q` and look for 'Filters', or select the 'Filters' item in the ribbon. A filter narrows the records retrieved from the child table. You can specify any number of filters; be aware that all of them are applied at once.
| Field, tab or group | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Link from Field No. | Specify the field from the selected table, the child table, on which to apply the filter. |
| Value | Enter the filter value; this can be any value which is recognized as a filter by BC. Option values are recognized and autocompleted. |
## Conditions
On this page, hit `ALT + Q` and look for 'Conditions', or select the 'Conditions' item in the ribbon. Conditions look almost identical to filters, but they act on the parent table and gate whether the link applies. You can specify any number of conditions, and again all of them are applied at once.
| Field, tab or group | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Link from Field No. | Specify the field from the parent table on which to apply the filter. |
| Value | Enter the filter value; this can be any value which is recognised as a filter by BC. Option values are recognised and auto-completed. |
## Fields
On this page, hit `ALT + Q` and look for 'Fields', or select the 'Fields' item in the ribbon. This is where a table actually yields a filter value. The sections below cover each column in more detail.
### Filter group
Specify the filter group for which to set a filter value.
### Field number
Specify the field that contains the filter value. This is an optional field, which makes it possible to use any kind of value or source. For instance, a custom dimension can be used as a field; see the [use dimensions for filter groups](/integrations/bc/guides/use-dimensions-for-filtergroups) guide for more info.
### Fixed value
Where the field number reads a value out of the data, a fixed value lets you supply one yourself, either as a default or as an outright override.
#### Value
A free text field where you can enter a fixed or default value. For example, for a sales order you might use a fixed value 'Sales' for the 'Department' filter group. Each sales order, and only a sales order, then applies this filter.
#### Behavior
The behavior options work in relation to the field number above and determine how, and if, the fixed value is used as the filter value:
- **None**: The fixed value is used as the filter value if no field number is set.
- **Overrule**: The fixed value always overrules the value from the field number; you can use this to set a specific value when a record exists for field number, regardless of the value of the field number
- **Overrule when empty**: The fixed value overrules the value from field number only when the field number is empty.
#### Value when not found
A free text field where you can specify a value for the filter value if no record exists for the field number. Combined with the overrule behavior of the fixed value, this is what lets you return a Yes/No or true/false (or any other value pair) depending on whether a record exists.
### Hierarchy behavior
The fixed value settled disagreements within a single level; hierarchy behavior settles them between levels. If you have set a value for the same filter group higher up in the hierarchy, these options decide whether the current level overrides it.
They only come into play when the current level actually returns a filter value. If a field number is defined but no fixed value is entered and no record is found at the current level, then this level has no filter value to contribute and is simply ignored.
- **None**: The current level does not override a filter value that was set at a higher level in the hierarchy. The current level does set the filter value if no filter value was set for the filter group.
- **Overrule**: The current level always overrules a filter value that was set at a higher level in the hierarchy.
- **Overrule when empty**: The current level overrules an empty filter value that was set at a higher level in the hierarchy.
## Example: Skills
Theory only goes so far, so walk through the skills entity that ships preconfigured with the standard connector. For a service item line, the hierarchy is set up like this:
```text
.
├── Service Item Line
│ ├── Resource Skills
│ ├── Skill Code
```
Building this hierarchy is a matter of telling Business Central three things at each step: how the tables are linked (link), what data to retrieve (filter), and which fields to compare against the filter value list of the 'Skills' filter group (field). That filter group uses table 5955 as the source of its filter values, so the service item line graph needs a path to reach that same table, which here means going two levels deep.
To navigate from the service item line to the resource skills, the `Links` tell Business Central to use the 'Service Item No.' field and join it with the Resource Skill's 'No.' field. The `Filter` indicates that this level should only yield records of type 'Service Item'. No `Fields` are mapped here, since you'll use the field from the next level.
To navigate from the resource skill to the skill code, the `Links` section tells Business Central to use the Resource Skill's 'Skill Code' and join it with the Skill Code's 'Code' field. No filters are applied here, since there's already one set at the level above. To use the right filter value reference, add a `Field` entry and link it to the Skill's 'Description'.
In essence, the filter group generates values from a source table (table 5955 - skills), and you navigate the path of a service item to link the skills required for that work item to a corresponding value in the filter values list.
For instance, in Business Central table 5955 could contain the following list:
```text
.
├── Skills
│ ├── NETWORKING
│ ├── DATABASE
│ ├── PROGRAMMING
│ ├── DEVOPS
```
A simple filter group uses the entire source, without any filters, to create filter values:
```text
.
├── Filter Group: Skill
│ ├── NETWORKING
│ ├── DATABASE
│ ├── PROGRAMMING
│ ├── DEVOPS
```
Service item lines allow you to specify required skills:
```text
.
├── Service Item Line: "Upgrade SQL Server"
│ ├── Resource Skills
│ ├── Skill Code: DATABASE
│ ├── Skill Code: NETWORKING
```
Without the setup described above, Dime.Scheduler can't connect the dots. With this configuration, it knows how to map and compare these tables and values, so it appends the two requirements from the filter group needed to carry out the work to service order item "Upgrade SQL Server".
This is about as simple as it gets. You might also split the skills table into several filter groups. For example, you could have a filter group called "System Admin Skills" and another named "Developer Skills", and allocate each skill to the right group. There is no right or wrong here; it is up to the customer to decide how to set up the filter groups.
## Read more
- [Using dimensions for filter groups](/integrations/bc/guides/use-dimensions-for-filtergroups)
---
## Filter Groups(Modules)
A planner staring at a board of unassigned jobs needs to know more than who is free. They need to know who is *qualified*: which technician holds the certification this job demands, who speaks the customer's language, who is allowed to drive the van. Filter groups are how Dime.Scheduler answers that question. You use them to capture requirements on work items (qualifications, region, certifications, and so on) and the matching attributes on resources (a driver's license, languages spoken, and so on), then let the planner pair the two. Managing those groups and their values from Business Central is the most convenient way to set this up, because the data you need usually already lives in BC tables.
:::info
The 'Dime.Scheduler Filter Groups' setup is not limited to filter groups. The same configuration also defines **categories**, **time markers** and **pins**, the visual indicators on the planning board. Everything described below applies to those entities as well.
:::
Whichever of these entities you create, the workflow is the same four steps: create the item, point it at a source, generate the available values (filter groups only), and send the record to Dime.Scheduler. That last step can be done by hand through a ribbon action, or left to run on its own through the scheduled job functionality on the 'Dime.Scheduler Setup' page.
## Create the filter group
Open the Dime.Scheduler Filter Groups list in BC. Each row is one entity, defined by two fields:
| Field, tab or group | Description |
| ------------------- | ----------------------------------------------------------------------------- |
| Type | Specify whether this record represents a `filter group`, `category`, `time marker` and `pin`. |
| Group | The entity's name. This is also the name that is displayed in Dime.Scheduler. |
## Specify the source for the filter values
Rather than typing values one by one, you tell Dime.Scheduler where to read them from. The source list lives in the ribbon under the ellipsis button, in the 'Related' submenu:
A source is a BC table and field, optionally narrowed by a filter:
| Field, tab or group | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Table No. | Specify the table number that is the source for the filter values |
| Field No. | Specify the field number of the field that contains the filter values. |
| Field Filter | Enter an optional filter to limit the available values, for example `WHERE(Type=CONST(Resource),Skill Code=FILTER(_DS_))` |
## Generate the available values
With the sources in place, let BC do the work: click the 'Create Filter Values' button and it reads the distinct values straight out of the source table. In the screenshot below, Dime.Scheduler generates filter values for the 'Zone' filter group based on the data in table 5957 (service zones).
If a value does not come from a table, you can also enter filter values manually.
## Send the filter values to Dime.Scheduler
Open the 'Dime.Scheduler Filter Values' list in BC, then either select the entities you want to synchronize or select none to send them all.
:::tip
You can mark certain filter values to be excluded from Dime.Scheduler. This is an alternative to the **field filter** in the filter values sources: instead of filtering at the source, you keep the value in BC but enable its `Do Not Synch` field so it never reaches the planning board.
:::
Whether you are sending individual filter values or whole filter groups, the same two ribbon actions do the job: `Actions -> Synch. All` pushes everything, and `Actions -> Synch. Selection` pushes only the rows you highlighted. The selection action for filter groups is available from the filter groups page only.
To take this off your hands entirely, automate it. The supplied codeunit `DS Scheduled Sync` sends filter groups, categories and time markers to Dime.Scheduler without anyone clicking a button.
:::tip
Use a job queue to run DS Scheduled Sync every night.
:::
## Standard connector
You do not always have to build this from scratch. The standard connector ships with the most common scenarios already wired up. Skills and zones, for instance, come out of the box in Business Central, with setup available in the [FastTrack Wizard](/integrations/bc/modules/resource#resource-filters). Enable it, and the connector adds the filter groups and configures their sources so Dime.Scheduler uses resources' skills and zones to match the task requirements, no manual configuration required.
It is also the clearest illustration of what the filter group and values setup can do once you start defining your own.
:::tip
For an even more flexible approach, consider using **dimensions**. The [guide on using dimensions for filter groups](/integrations/bc/guides/use-dimensions-for-filtergroups) walks you through the setup.
:::
## Read more
- [Common actions](/integrations/bc/modules/actions)
- [FastTrack for resource filters](/integrations/bc/modules/resource#resource-filters)
- [Using dimensions for filter groups](/integrations/bc/guides/use-dimensions-for-filtergroups)
---
## Filter Values
Filter values setup is where the available values for each filter group live. If a filter group answers the question "what dimension can I plan against?", the filter values are the concrete answers to it: the individual skills, the specific zones, the actual departments a planner can pick from.
In practice you will rarely open this table and type into it. As covered in the previous article, a filter group can be sourced from another table in Business Central, such as skills, zones, and others. When you link a filter group to one of those tables, the filter groups page manages this list for you, so the values stay in step with their source instead of drifting out of sync because someone forgot to copy a new skill across.
:::info
This list is also where you define **categories**, **time markers** and **pins** inside BC, and where you assign a color to each. Color is what makes these elements readable at a glance on the planning board, so it is worth setting deliberately. On the DS filter value page, click the `Color` field to open the color picker:
:::
## Filter values vs doc filter values
It is easy to confuse this table with its close relative. Filter values list every value that is *available* for a filter group. [Document filter values](/integrations/bc/modules/docfiltervaluesources), covered in the next article, are the values actually *assigned* to a specific task or resource, and they are usually a subset of the available values.
Put differently:
> A document filter value - where "document" refers to a task or resource in Dime.Scheduler - is an assignment of a filter value to one of those entities.
The distinction matters because the same value means two different things depending on which side it sits on.
On a **task**, document filter values describe the characteristics **required** of a resource to perform the work. Think of them as the planner asking:
- Which skills are required from a person to carry out a task?
- What is the minimal pressure or power required from a machine?
- What minimum capacity is required for a conference room?
On a **resource**, document filter values describe the **characteristics** the resource actually has:
- What are the skills of a person?
- What is the minimal pressure or power delivered by a machine?
- What is the capacity of a conference room?
So a **task filter value** is a requirement a resource must fulfill to carry out the work, and a **resource filter value** is a characteristic that says what that resource is able to do. Dime.Scheduler matches the two, which is how the planner sees at a glance which resources are a fit for a job instead of cross-checking it by hand.
## Read more
- [Common actions](/integrations/bc/modules/actions)
- [FastTrack for resource filters](/integrations/bc/modules/resource#resource-filters)
- [Using dimensions for filter groups](/integrations/bc/guides/use-dimensions-for-filtergroups)
---
## HR
import ReactPlayer from 'react-player'
A schedule that only shows the work is a schedule that lies. If a resource is on leave, in training, or out sick, those hours are gone whether or not the planning board knows it, and a planner who books against them is setting up a conflict. This module keeps that knowledge in sync: employee absences flow both ways between Microsoft Dynamics 365 Business Central HR and Dime.Scheduler, so the people who plan the work and the people who own the HR records are always looking at the same availability. This page walks you through the setup and the three ways planners can register an absence.
## Setup
You configure the module in the Fast track wizard.

Turn the module on by selecting `Enable Absences planning`. Everything below only applies once it is enabled.
From there, a handful of settings decide how an absence looks and behaves once it lands on the planning board:
- **`Job No. for Absences`** anchors the absences. An absence has no header the way a sales or assembly order does, so the wizard creates a default job to hold them. Enter a descriptive code here; planners then use it to sort, group, and filter tasks in the open and planned Tasks grid in Dime.Scheduler.
- **`Create Tasks from Cause of Absence`** generates the schedulable tasks in Dime.Scheduler from the values in the cause of absence table, so the reasons defined in BC become the things a planner can drag.
- **`Show Cause of Absence as`** controls whether the reason for an absence is visible at a glance on a planned task, rather than something the planner has to open the task to discover:
- **Blank**: no visual indicator used
- **Category**: the color of the planned task shows the cause of absence
- **Time marker**: the time marker of the planned task shows the cause of absence
The video below demonstrates this functionality. It uses service orders, but the behavior is the same for this module:
- **`Create Appointment as`** decides the shape of the planned task when a user sends an absence from BC or creates one through the action shortcut. Choose **Working Time** to give the absence a start and end time as specified in `Starting Time` and `Ending Time`, or **Full Day** to create it as a 24h planned task, in which case the `Starting Time` and `Ending Time` fields are not used.
- **`Create New Absence Action`** exposes the action shortcut so planners can create a new absence from inside Dime.Scheduler. Enter the caption shown to the user there (see the screenshot below).

## Causes of absence
The causes of absence are the raw material a planner schedules against, so they have to reach Dime.Scheduler first. To make that possible, the default actions on the causes of absence page have been extended with two items:
- Send absence (selected range or all)
- Delete selected item
When you send the causes of absence, Dime.Scheduler creates a special job that holds the selected (current and subsequent) causes of absence, and turns each cause of absence into an individual, infinite task.

Because these tasks are infinite, they never disappear from the open tasks list. That is deliberate: an "out sick" or "vacation" task is a reusable building block, always there and ready to be dragged whenever a planner needs it.

## Absence registration
Dime.Scheduler builds on standard Business Central functionality and adds visual scheduling on top of it, and it offers three routes to register an absence so each role can work where it makes sense:
- Drag and drop, using the absence tasks inside Dime.Scheduler
- The action shortcut (right-click on the planning board for the selected resource and date) to create an absence in BC
- Entering an absence in BC and sending it as a planned task to Dime.Scheduler
### Creating absences
#### Drag and drop
Drag and drop is the fastest option for planners who prefer to stay in Dime.Scheduler. Because the task always sits in the open tasks list, registering an absence takes just a few clicks: pick the cause of absence, drag the task onto the right timeslot for the right resource, and the absence is registered.
#### Business Central
Because the sync runs both ways, the HR side is just as valid a starting point. On the absence registration page in Business Central, create an absence and send it to (or remove it from) Dime.Scheduler. The planner never has to re-enter it; it simply appears on the board.

---
## Jobs(Modules)
import ReactPlayer from 'react-player'
A job in Business Central knows what work has to happen and what it may cost, but not who does it or when. That is the gap this module closes. It sets up a bidirectional flow between the jobs/projects module in Microsoft Dynamics 365 Business Central and Dime.Scheduler, so the same job a project manager budgets in BC becomes something a planner can drag onto a timeline, and every scheduling decision flows straight back as job planning lines. This page covers enabling the module, deciding how jobs turn into tasks, and planning them on the planning board or the Gantt chart.
The two videos below show the two ends of that spectrum: quick ad-hoc planning, and long-term project planning with dependencies.
Ad-hoc planning
Long-term project planning
## Setup
The FastTrack Wizard is where you set the defaults for how jobs cross over into Dime.Scheduler. These are starting points: most can be overridden per project later, so you are configuring what happens when nobody says otherwise, not locking anything in.

### Enable jobs solution
Nothing flows until the module is on. Tick the `Enable Job Planning` switch to turn it on.
### Creating tasks
The first decision is how granular the planning should be: is a job one thing to schedule, or a collection of smaller pieces? Two checkboxes set the default source in BC for creating tasks in Dime.Scheduler:
- To create a **single task** in Dime.Scheduler for the job: select `Send Job as Task`.
- To create a task for each **job task line** or **job planning line**: select `Send Tasks with Job`.
These are defaults only. You can override them per project when a project calls for different settings (see [Creating jobs](#creating-jobs) below).
### Default duration
When a planner drags a task from open tasks onto a resource on the planning board, the planned task has to start somewhere, so it defaults to a duration of 1 hour. To use a different default, enter it in `Default Duration Job Task`.
### Indicators
Color is how a planner reads a board at a glance, and the `Default Category` field decides which BC field drives the color of a planned task when it is dragged from open tasks onto a resource:
- Select a job posting group.
- Leave blank to use the default color (blue).
The video below shows this in action. It uses service orders, but the behavior is identical for jobs:
### Filters
To let planners narrow jobs down to the person accountable for them, select `Create Person Responsible Filter` and enter the name users will see in Dime.Scheduler. The FastTrack Wizard creates the configuration for the `Person Responsible Filter`, and BC then sends the person responsible for each job to Dime.Scheduler.
### Gantt planning
Some jobs are simple enough for the board; others are projects with dependencies and milestones that belong in the Gantt chart. Select `Plan In Gantt Chart` to plan jobs there. The [scheduling mode](user/components/gantt.md#scheduling-mode) can be one of:
- Normal
- Fixed Duration
- Effort Driven
- Dynamic Assignment
### Timesheets
Select `Update Time Sheet from Planning Board` to have BC's time sheet kept in step with the plan automatically: the resource's time sheet is created or updated whenever a task is allocated to a resource or a planned task is modified in Dime.Scheduler, so nobody re-enters hours by hand.
## Usage
### Actions
The default actions on this page are extended with the following items.
#### List Actions
See [common actions](integrations/bc/modules/actions.md) for an overview of the list actions for this type.
#### Card actions
Two actions are added to the job card, **Send job** and **Delete job**, so a project manager can push or pull a single job straight from the card they are already looking at.

### Creating jobs
The mechanism is straightforward: create a job, invoke the send to Dime.Scheduler action, and the connector gathers everything relevant about the job and its job tasks and sends it over. The entities it pulls together are the **Job**, its **Job tasks** and **Job planning lines**, plus the **Customer** and **Contact** behind it, so the planner sees the work and who it is for in one place.
As with the other modules, the **general setup** lets you connect a field of the job or item to an appointment's visual indicators. For example, a category can reflect the job's posting group. That is the default, but you can BYOBL (Bring Your Own Business Logic) to Dime.Scheduler and use other fields as the source for an appointment's color-coded indicator. Further settings tune the defaults, such as `default duration job task`, which sets an appointment's duration when the task is dragged onto the planning board.
Beyond that, the connector adds a handful of settings that shape how a **job behaves** once it is in Dime.Scheduler:

#### Planning levels
Dime.Scheduler keeps the relationship between a job and its job tasks intact: the job itself is captured at the `Job` level, while job tasks map to `Task` records. When a job has multiple lines, those lines all link back to the same job, so once planned they trace back to where they came from.
That relationship is configurable through three **planning levels**:
- Job
- Job task
- Job planning line
Choose **Job** for a 1:1 relationship, where the task simply is the job. Choose either of the others to use the job tasks or the job planning lines as the tasks in Dime.Scheduler, which is what you want when a single job needs to be planned in pieces.
#### Budget level
A job is rarely an open-ended commitment, and the budget is how that limit is enforced. It works much like the planning level, but its job is to cap the **budget**: the maximum time the task is allowed to be scheduled for.

The budget is expressed in time, and a task is not meant to be scheduled beyond it. Every time an appointment for the task is created, updated, or deleted, the remaining budget is recalculated. When it hits zero the task is considered closed and drops off the open tasks list, so planners stop seeing work that is already fully scheduled.
There are four levels, each reading the budget from a different place:
- **Job**: the `Planning Budget` field is used.

- **Job task**: the planning budget column in the job tasks subsection is used.

- **Job planning line**: the quantity field determines the task's budget.

- **Infinite**: there is no limit, so the task never disappears from the open task list until the project is completed.
#### Planning status
The `Planning status` controls whether a job is even open for planning, and has three states:
- Open
- Planning
- Completed
An `Open` job is unconfirmed; a `Completed` job has been fully scheduled and finished. Only jobs set to `Planning` show up in the Dime.Scheduler open tasks list, which keeps unconfirmed and finished work out of the planner's way.
#### Planning modes
Jobs can be scheduled in two components that serve very different purposes. Ad-hoc planning usually happens on the planning board, while long, complex projects with dependencies and milestones belong in the Gantt chart. The planning mode picks which one applies.
The two modes are exclusive. Set the mode to the planning board and the job is not available in the Gantt chart; set it to the Gantt chart and the planning board is off-limits for that task, so resource planning has to happen in the Gantt chart. Either way the records still appear in the open tasks list, but drag and drop is disabled when Gantt mode is in force.
#### Gantt Scheduling Mode
When Gantt mode is selected, an advanced setting controls how the project schedules itself:
- Normal
- Fixed duration
- Effort-driven
- Dynamic assignment
Consult the [Gantt chart docs](user/components/gantt.md#scheduling-mode) to learn what each mode does.
#### Planning behavior
Scheduling a task in Dime.Scheduler has to leave a trace in Business Central. The planning behavior decides what that trace is:
- Create a budget job planning line
- Create a billable job planning line
- Create a budget and billable job planning line
- Do nothing
### Planning jobs
There are two ways to schedule jobs, the planning board and the Gantt chart, each targeting a different planning type. Once planned, both are processed in exactly the same way:
- The open task grid holds the work waiting to be planned. To plan a task, drag it onto the right timeslot for the right resource on the planning board. That single drag registers a task for the job.
- Gantt chart project tasks also appear in the open task list but cannot be dragged to the board. The planner opens the project in the Gantt chart and assigns resources there. On save, Dime.Scheduler generates the appointments and sends them to Business Central.
Behind the scenes, a **job planning line** for the job, job task, or job planning line is created automatically in Business Central according to the job's planning settings. That line carries the details of the appointment you just planned, and the two stay tied together: any change to the appointment is reflected in Business Central automatically and immediately.

:::info
When the budget is set on the job planning line, the **budget planning line**'s quantity drops by the same amount as the appointment scheduled on the planning board. In the example above, the original budget was 16 hours; after scheduling a task, 8 hours remain.
:::
You can also tie the planning to the time sheets module in Business Central. With the `Update Time Sheet Service` flag active, the connector inserts, updates, or removes a time sheet entry on its own.
In short, this module builds on standard Business Central jobs and adds the visual, drag-and-drop scheduling layer they lack.
---
## Machine Centers
A machine center in Business Central is the unit you actually load: the machine, line, or station that work is booked against. Send it to Dime.Scheduler and it becomes a schedulable resource on the planning board, so the planner can see at a glance which machine is free and which is already committed, and drag jobs onto capacity instead of guessing at it from a spreadsheet.
## Setup

### Enable machine center solution
Machine center planning is off until you switch it on in the FastTrack wizard. Select `Enable Machine Center Planning` to turn on the solution.
## Usage
### Actions
Once the solution is enabled, the integration extends the standard machine center pages with the actions and field below.
#### List Actions
See [common actions](/integrations/bc/modules/actions) for an overview of the list actions for this type.
#### Card actions
The machine center card gains one action:
- Send machine center

### Fields
Alongside the standard fields, the integration adds one field for Dime.Scheduler:
- Hide in planning board
This is your escape hatch for a machine center you have sent but do not want cluttering the board, for example one that is decommissioned or planned elsewhere. The record stays in Dime.Scheduler, but the planner no longer sees it as a row.
---
## Overview(Modules)
When you install the connector, Business Central gains the machinery it needs to talk to Dime.Scheduler, but the footprint is deliberately small. The extension adds its own tables, pages and actions, and where it touches standard BC objects it only bolts on a button or a field. It never rewrites how a standard table behaves, so your existing setup keeps working exactly as before.
The pages that follow walk through what the connector brings. Most of these additions are plumbing the connector uses internally: you rarely interact with them directly, and unless an earlier section called one out, you don't need to know how it works to plan day to day.
## New artifacts
These are the objects the connector creates from scratch. The bulk of them are tables that stage the data flowing between BC and Dime.Scheduler (appointments and their field values and resources, the source types, and the filter machinery), plus two actions you trigger to push capacity out.
| Name | Type |
| ----------------------------- | ------ |
| Appointments | Table |
| Appointment card | Card |
| Appointment field values | Table |
| Appointment resources | Table |
| Source types | Table |
| Document filter value sources | Table |
| Filter groups | Table |
| Filter values | Table |
| Send resource capacity | Action |
| Send resource group capacity | Action |
## Updated artifacts
Where the connector extends standard BC, it stays minimal: the **Send record** button is how you push a record across, while **Repair status** and **Job planning line budget** are fields the connector adds to track state.
| Name | Type |
| ------------------------ | ------ |
| Send record | Button |
| Repair status | Field |
| Job planning line budget | Field |
## Read more
- [Actions](/integrations/bc/modules/actions)
- [Filter groups](/integrations/bc/modules/filtergroups) and [filter values](/integrations/bc/modules/filtervalues)
- [Resources](/integrations/bc/modules/resource) and [resource capacity](/integrations/bc/modules/resourcecapacity)
- [Installation](/integrations/bc/install/intro)
---
## Production Orders
import ReactPlayer from 'react-player'
Production orders already carry their own logic in Business Central: routing lines, work and machine center allocations, and the calculations that tie every step together. What BC does not give the planner is a picture. The **FastTrack solution for production orders** supplies that picture, putting routing on the planning board where it can be seen, dragged, and rescheduled while every BC rule still holds. In practice it does three things:
- Visualize and plan routing lines via drag and drop, using and respecting all the BC business logic.
- Create resources in Dime.Scheduler from the standard BC work center and machine center tables.
- Update the category or time marker of the planned task in Dime.Scheduler automatically when the production order status changes, so a glance at the board tells you where each order stands.
## Setup

The wizard turns the solution on and decides how status is surfaced.
### Enable production orders solution
Select `Enable Production Routing planning` to turn on the FastTrack solution.
### Configure production orders
In `Show Order Status as`, choose how Dime.Scheduler should reflect a production order status change made in BC on the planned task(s). You can drive either the category or the time marker, or leave it blank if you don't want the order status shown at all.
The video below shows this setting in action. It uses service orders, but the capability is the same for this module:
## Usage
Watch the video below for a short introduction to this module in Dime.Scheduler:
### Actions
The default actions on this page have been extended.
#### List Actions
See [common actions](/integrations/bc/modules/actions) for an overview of the list actions for this type.
#### Card actions
Two actions are added to the production order card: **Send order** and **Delete order**.

### Creating production orders
The flow is straightforward. Create a production order, run the send to Dime.Scheduler action, and the connector gathers everything about the order and its work items and ships it across. Three entities make the trip:
- Production orders
- Production order lines
- Production order routing lines
Dime.Scheduler keeps the relationship between an order and its lines intact, mapping each level onto its own record type. The production order is captured at the `Job` level, the production order line at the `Task` level, and each production order routing line becomes an `Appointment`. When an order has several lines, those lines all link back to the same job, so once they are planned they still trace to the production order they came from.
Here is where this module parts ways with the others. Sending the order creates the tasks, but the appointments are not yours to place by hand: they are created automatically from within Business Central. Dime.Scheduler leans on the production and routing rules in BC to build the planning, so the moment you send a production order, the appointments are already on the board.

### Planning production orders
For the same reason, there are no open tasks waiting in the open tasks grid for you to drag onto the board. Business Central already knows which machine and work centers are allocated to each task, so Dime.Scheduler simply visualizes the planning that BC produced rather than asking you to assemble it.
That link runs both ways. Because each production order routing line corresponds directly to an appointment, moving an appointment is handed straight back to the production routing engine in Business Central, which validates the change and recalculates the rest of the order routing when it needs to. In the example below, shifting one appointment ripples through the other steps in the production order.

In short, Dime.Scheduler keeps standard Business Central functionality in charge and adds visual scheduling on top of it.
---
## Resources(Modules)
Resources are the people and assets a planner schedules, so this is the solution every other FastTrack solution leans on. It turns the standard Business Central Resource table into resources in Dime.Scheduler. The one exception is production routing planning, which schedules work centers and machine centers instead.
## Setup
How a resource is categorized and grouped on the planning board is decided here, not in Dime.Scheduler. Use the options in DS Setup and the FastTrack Wizard (shown below) to define how resources, groups and teams are created.

The resource setup has five subsections:
- Type
- Department
- Team
- Pin
- Filter
### The source pattern: Type, Department, Team and Pin
The first four subsections (Type, Department, Team and Pin) all answer the same question in the same way: where should this attribute's value come from? Rather than typing values by hand in Dime.Scheduler, you point each one at a source in Business Central, and it stays in sync from there. The choices are consistent across all four:
- **Blank** (called **Default** for resource type, where the value becomes "Resource" for every resource): no grouping value is derived.
- **Resource Group**: the value follows the resource group assigned to the resource in Business Central.
- **Field**: the value follows any field on the resource table, including custom fields you have added. You pick the field in the matching setting (for example "Resource Type Field", "Department Field", "Team Field" or "Pin Field").
- **Dimension**: the value follows a default dimension on the resource. You pick the dimension in the matching setting (for example "Resource Type Dimension", "Department Dimension", "Team Dimension" or "Pin Dimension").
Because Business Central is the source of truth, you can change any of these settings whenever you like, and the change takes effect the next time you send the resources. Anything you edit on the corresponding attribute directly in Dime.Scheduler is overwritten on the next send, so make the change here. Planned data is never disturbed: each resource keeps a unique identifier independent of its type, department, team or pin.
What differs between the four is only what each attribute is *for*, covered below.
### Type
A **resource type** is any property you use to categorize resources. Rooms, vehicles and machines are all good candidates. The payoff is on the planning board: a planner can filter by resource type, and some teams go further and set up a separate planning board per type. Setting it from Business Central (as shown below) keeps that categorization driven by your back office, though defining it directly in Dime.Scheduler is also possible.

Choose the source in the `Resource Type From` selection list following the pattern above.
### Department
A **department** is a generic grouping in Dime.Scheduler. It lets a planner sort and group resources on the planning board and in other components such as the planned tasks grid, so the right rows sit together instead of scattered across a long list.

Choose the source in `Department from` following the pattern above.
### Team
**Teams** are a second way to sort and group resources in Dime.Scheduler. In Business Central teams carry more weight (you can assign a team member's role, for instance), but the FastTrack solutions use them purely for sorting and grouping, giving you a grouping axis independent of department.

Choose the source in `Team from` following the pattern above.
### Pin
**Pins** are indicators in Dime.Scheduler that give resources, jobs and tasks different pin colors on the maps. With pins set, a planner glancing at the map can tell at a colour which group a resource or task belongs to instead of clicking each marker to find out.

Choose the source in `Pin from` following the pattern above.
### Resource Filters
Filters are how a planner finds the right resource for a task, and they also drive the data-driven security model that defines segregation of roles and responsibilities. The standard resource filters are framed around service management, but they apply just as well to other areas.
You can define additional filter groups in the `DS Filter Groups` setup, outside of the FastTrack Wizard.

There are two pairs of filters: skill filters and service zone filters.
:::tip
When using service management, enable these filters. Enabling one or both has the FastTrack Wizard create the configuration in BC that sends the required filter values when service orders go from BC to Dime.Scheduler, which is what lets the user rely on the automatic filter options in Dime.Scheduler.
:::
A blocked resource is not the same as a deleted one. Resources that are `Blocked` in BC are still created in Dime.Scheduler, but they stay hidden from users. Blocking or unblocking a resource in BC sets the `Do Not Show` setting in Dime.Scheduler accordingly.
#### Skill filter
Enable "**Create Skill Filter**" to create the configuration in BC that filters resources by their skills. Enter the name users will see in Dime.Scheduler for this filter in "Skill Filter Name".
#### Service zone filters
Enable "**Create Service Zone Filter**" to create the configuration in BC that filters resources by the service zones in which they operate. Enter the name users will see in Dime.Scheduler for this filter in "Service Zone Filter Name".
## Usage
### Actions
This page extends the default actions with the following items.
#### List Actions
See [common actions](/integrations/bc/modules/actions) for an overview of the list actions for this type.
#### Card actions
The following actions have been added to the resource card:
- Send resource
- [Send capacity](/integrations/bc/modules/resourcecapacity)
### Fields
Alongside the standard fields like name, unique number, type and address, a few fields have been added specifically for Dime.Scheduler:
- Hide in planning board
- Enable Gps Tracking
- Enable Exchange Synchronisation
These fields can be found at the bottom of the resource card:

For more information about these fields, read more about it in the [resource settings manual](administration/settings/resources.md#managing-resource-master-data).
---
## Capacity
A schedule is only as trustworthy as the availability behind it. If Dime.Scheduler doesn't know that a resource works four days a week, is part-time in August, or takes Fridays off, the planner will happily book over hours that don't exist. Pushing a resource's available working hours from Business Central closes that gap: the hours show up in the [capacity component](/user/components/pivot), and the planner sees real availability instead of guessing at it.
The hours themselves already live in Business Central. Alongside the standard `Resources` table, Dime.Scheduler reads the standard Business Central `Resource Capacity` matrix to create capacity entries. Business Central exposes that matrix in more than one place, so you have a matching choice of where to send it from: one resource at a time, or many at once.
## Resource card
When you only need to push a single resource, work from its card. The Actions menu of the resource card exposes two Dime.Scheduler actions: send the resource itself, and send the resource's capacity matrix.
Choosing the capacity action asks for a start and end date, which is how Dime.Scheduler knows how many capacity entries to generate. The resource filter in this window is set for you automatically, so you only supply the date range.

## Capacity matrix
For a whole team or a department at once, the resource capacity matrix is the better starting point: it lets you generate capacity entries in bulk for several resources in a single pass. Run the `Send Capacity` button in the Dime.Scheduler submenu of the matrix.

## Read more
- [Common actions](/integrations/bc/modules/actions)
- [FastTrack for resources](/integrations/bc/modules/resource)
---
## Group Capacity
When you plan against capacity, the schedule is only as accurate as the working hours behind it. Sending capacity one resource at a time is fine for a single technician, but tedious for a crew of twenty who share the same shift pattern. Group capacity solves that: it pushes the working hours for an entire resource group in a single action, so every member shows up in the [capacity component](user/components/pivot.md) with the same setup applied.
Under the hood this works just like resource capacity. The difference is that Dime.Scheduler reads Business Central's standard `Resource Group Capacity` matrix and generates the entries in bulk, applying the same capacity setup to every resource that belongs to the group.
To send group capacity:
1. In the `Resource Group Capacity` matrix, select the resource group you want to update.
2. Click the `Send Capacity` button.

3. In the window that opens, enter a start and end date. The `Resource Group` field is already set to the group you selected in the matrix.

4. Click `OK`.
Dime.Scheduler then generates capacity entries for every resource that belongs to the group, so the whole team's availability lands in the plan board at once.
---
## Sales Orders
import ReactPlayer from 'react-player'

A sales document in Business Central tells you what was sold and when it is due, but not who is going to deliver it or when they are free. The **FastTrack solution for Sales Planning** closes that gap: it lets you schedule resources against BC sales documents on a visual planning board and keep the dates on both sides in sync, so the planner works in one place instead of reconciling two.
In practice this means you can assign multiple resources across different dates and times onto a single sales document. Plan those resources in Dime.Scheduler and the solution writes them back as resource lines on the document; let an appointment slip and the relevant date on the sales document can move with it, driven by the first and last planned task. The flow also runs the other way: if you enter resource lines on the document in BC before sending it over, those lines come across as planned tasks, so a planner can pre-assign resources up front and refine them later on the board.
## Setup
### Enable sales planning solution
Turn the FastTrack solution on by selecting `Enable Sales planning`. Everything below depends on it.
### Configure sales planning
How the document is broken down into work is the first choice. Select `Send Sales Header as Task` to create a single task for the whole document, or `Send Tasks with Sales Header` to create one task per sales line.
:::info
By design, only sales lines of type item are sent to Dime.Scheduler, however this can be modified if required.
:::
The rest of the configuration tunes how planning flows back to BC:
- When a planner drags a task from open Tasks onto a resource, the appointment defaults to a duration of 1 hour. Enter a different value in `Default Duration Sales Task` to override it.
- Select `Send Resources with Order` if you want resource lines entered on the sales document in BC to be created as planned tasks in Dime.Scheduler when you send the document.
- Select `Create Resource Lines` if you want resource lines on the sales document to be created and updated based on the planned tasks in Dime.Scheduler. This differs from assembly order planning, where resource lines are always created or updated.
- Use `Update Date` to choose which date on the sales document is updated based on the planned tasks in Dime.Scheduler. Choosing a date on the sales line is only applicable when `Create Resource Lines` is selected. The options are:
- **Blank**: no date is updated
- **Shipment Date (H)**: on the sales header (H) is updated
- **Promised Delivery Date (H)**: on the sales header (H) is updated
- **Shipment Date (L)**: on the sales line (L) is updated
- **Planned Shipment Date (L)**: on the sales line (L) is updated
- **Planned Delivery Date (L)**: on the sales line (L) is updated
:::info
Updates are validated so other dates might be updated as well.
:::
## Usage
Watch the video below for a short introduction to this module in Dime.Scheduler:
### Actions
The default actions on this page have been extended with the following items.
#### List Actions
See [common actions](integrations/bc/modules/actions.md) for an overview of the list actions for this type.
#### Card actions
Two actions are added to the sales order card: **Send order** and **Delete order**.

### Creating sales orders
When you create a sales order and run the send to Dime.Scheduler action, the connector gathers everything relevant about the order and its work items and ships it across. That payload is built from the sales header, the sales lines, the customer, the contact, and the ship to address, so the planner sees not just what to do but where and for whom.
Dime.Scheduler keeps the structure of the document intact: the sales header is captured at the `Job` level, while each sales line maps to a `Task` record. When a sales order has multiple lines, those lines all hang off the same job, so once planned they still trace back to the one order.
:::info
This is the default behavior. When switched off, the `Send Sales Header as Task` field allows you to ignore the sales lines and map the sales header to a task. Instead of a 1:N relationship, this feature flag enforces a 1:1 relationship. With this option, it's also possible to send resources as appointments to Dime.Scheduler, given that the `Send resource with order` setup flag is switched on. Otherwise, and if the `Send Lines with Sales Header` is enabled, the behavior as described above will be applied.
:::
When you send the sales order to Dime.Scheduler, these tasks are created.

Further global setup tweaks the default behavior of sales headers, such as `default duration sales line`, which sets the default duration of an appointment when you drag the task to the planning board.
### Planning sales orders
The open task grid lists the work that still needs to be planned. To schedule a sales item, drag the task onto the right timeslot for the right resource on the planning board. That single action registers the plan.
Behind the scenes a **resource line** is created automatically on the sales order in Business Central, holding the details of the appointment you just planned. The resource line and the Dime.Scheduler appointment stay linked, so any later change to the appointment is reflected in BC automatically and immediately. The planner never touches BC to keep it current.
:::info
This is the default behavior. This can be switched off by disabling the `Create resource lines` setup flag.
:::

Scheduling an appointment can also drive a date on the document, depending on the `Update Date` setting described in [Configure sales planning](#configure-sales-planning). Those dates are the shipment date or promised delivery date on the header, and the shipment date, planned shipment date, or planned delivery date on the line.
The net effect is that Dime.Scheduler builds on standard Business Central functionality and adds the visual scheduling layer on top, so the planner gets a board to work on while BC stays the system of record.
---
## Service Orders
import ReactPlayer from 'react-player'
Service is where scheduling earns its keep: a backlog of repairs, the right technician, and a customer waiting. The service orders module wires Business Central's service orders into Dime.Scheduler so a dispatcher plans them visually on the board while Business Central stays the system of record. The link is bidirectional, so what the planner does on the board and what happens on the order in BC stay in sync without anyone retyping it. This page walks you through the FastTrack configuration and shows how service orders are sent, planned, and kept aligned.
## Setup

### Enable service orders solution
Turn the solution on with `Enable Service Order Planning`. Everything below configures how the flow behaves once it is enabled.
### Creating tasks
The first decision is what a planner actually sees in the open tasks list: the whole order as one item, or every service item line on it. Two checkboxes control this.
- To create a single task in Dime.Scheduler for the service order, select `Send Service Header as Task`.
- To create a task for each service item line, select `Send Lines with Service Header`.
The two switches differ in how planning a task flows back into Business Central.
#### Send Service Header as Task
With this option, dragging and dropping the task in Dime.Scheduler creates a **resource allocation** for the service order in Business Central as soon as you do it. The new record captures the details of the appointment you just planned, and the resource allocation and the Dime.Scheduler appointment stay linked: any change to the service order appointments is reflected in Business Central automatically and immediately.

#### Send Lines with Service Header
With this switch on, dragging a task onto the planning board creates no allocation lines. Instead, Dime.Scheduler updates the starting and finishing times of the service order.
### Default duration
When a planner drags a task from the open tasks list onto a resource, it lands as a one-hour appointment by default. If your service work typically runs longer or shorter, enter a different value in the `Default Duration Service Line` field so the planner starts from a realistic block instead of resizing every appointment by hand.
### Indicators
A planned appointment on the board can carry two pieces of color-coded meaning: its main color (the category) and a time marker. Both are driven by a BC field you choose here, and both offer the same three sources to pick from, so a dispatcher reads the board at a glance instead of opening each order. The third setting decides what happens when the repair status moves on.
- The `Default Category` option selects which BC field sets the color of a planned task when you drag and drop it from the open tasks list onto a resource. Choose between service order type, repair status (from the service item line), and service zone. Leave it blank to use the default color (blue).
- The `Default Timemarker` option selects which BC field sets the color of the time marker on a planned task when you drag and drop it from the open tasks list onto a resource. Choose between service order type, repair status (from the service item line), and service zone. Leave it blank to skip time markers.
- The `Show Repair Status as` option selects how the planned task(s) in Dime.Scheduler update when the repair status on the service item line in BC changes: you can update the category or the time marker. Aligning this with the default category and default time marker settings makes sense, though it is not required. For example, you can set service order type as the default category and have the category update when the repair status changes; the color then shows the order type until the repair status changes, at which point it reflects that status.
This video shows how these fields work:
### Create appointment with resource allocation
When set to true, appointments are created, updated, or deleted as resource allocations on the service order change. This is the inbound counterpart to the board: changes made to allocations in BC drive what shows up on the planning board.
### Timesheets
Select `Update Time Sheet from Planning Board` to create or update the resource's time sheet in BC automatically whenever a task is allocated to a resource or a planned task is modified in Dime.Scheduler. The hours the planner blocks out become the hours the technician is booked for, with no double entry.
### Filters
If the `Skill Filter` and `Service Zone Filter` were enabled in the standard resource section, the FastTrack wizard creates their configuration for you automatically. BC then sends the skills a resource needs (based on the service item and the service zone of the service order) to Dime.Scheduler, so the planner can apply the automatic filter and narrow the board to resources actually qualified for the task instead of eyeballing it.
## Usage
### Actions
#### List Actions
See [common actions](integrations/bc/modules/actions.md) for an overview of the list actions for this type.
##### Card actions
The following actions are available on the service order:
- Send service order
- Delete service order

### Creating service orders
The flow is straightforward: create a service order, run the send to Dime.Scheduler action, and the connector gathers all the relevant data about the service order and its work items and sends it across. It pulls from four entities to build the graph:
- Service header
- Service item lines
- Customer
- Contact
Dime.Scheduler maintains the relationship between a service header and a service item line: the service header maps to a `Job`, and each service item line maps to a `Task` record. When a service order has multiple lines, those lines link to the same job, so once planned they trace back to the same service order.
:::info
This is the default behavior. When switched off, the `Send Service Header as Task` field lets you ignore the service item lines and map the service header to a task. Instead of a 1:N relationship, this feature flag enforces a 1:1 relationship. Otherwise, and if `Send Lines with Service Header` is enabled, the behavior described above applies.
:::
When you send the service order to Dime.Scheduler, those tasks are created.

Beyond the tasks themselves, more setup connects a field of the service header or item to an appointment's visual indicators. A category can reflect a service item's `repair status`, or a service header's `service order type` or `service zone code`. That is the default behavior, but it is easy to BYOBL (Bring Your Own Business Logic) and point an appointment's color-coded indicator at other fields instead. Other settings tweak the default, such as `default duration service line`, which sets the default duration of an appointment when you drag the task to the planning board.
### Planning service orders
The open task grid shows the work that needs to be planned. Planning a service item is the visual move the whole module is built for: drag the task onto the right timeslot for the right resource, and the service order is registered. How that appointment is processed back in Business Central depends on the task level you configured in this module, header level or service item level, and it may also create timesheets if that switch was turned on.
One caveat to keep in mind:
- When a service order is marked as 'Finished' in Business Central, Dime.Scheduler will not process any other incoming appointments.
## Repair status
The standard BC "**Repair Status Setup**" page has been extended with one new field: **Lock Appointment**. This lets you fix the planning based on the status of the order in Business Central, so once an order reaches a given status its appointments can no longer be moved.

---
## Time Sheets
Every hour a planner schedules on the board is an hour someone will eventually have to register in Business Central. Done by hand, that means the same work is entered twice: once when it is planned, and again when the resource fills in a time sheet. This module closes that gap. The time your resources spend on the planning board flows straight into BC time sheets, so the registration happens automatically off the back of the plan you already made.
It works for the following modules:
- [Service order](/integrations/bc/modules/serviceorder)
- [Projects](/integrations/bc/modules/job)
- [Absences](/integrations/bc/modules/hr)
To switch it on, open the FastTrack wizard, enable the module you want, and tick the checkbox at the bottom:

From then on, planning on the board produces the records it always did, the usual resource allocations or project planning lines, and additionally a time sheet entry for the associated employee. Anything you planned before enabling the option is left untouched by default, so turning this on only affects new planning rather than rewriting history.
For example, this planning:

... is now also registered as time sheets:

The entries land in standard BC time sheets, so everything downstream, approval, posting and reporting, behaves exactly as it would for time sheets entered any other way. This is all standard BC functionality.
---
## Work Centers
In manufacturing, the work center is where the work actually happens, so a planner who schedules production needs those work centers to exist on the planning board as something they can drop jobs onto. This integration sends your Business Central work centers to Dime.Scheduler, where they show up as resources alongside everything else you plan. Once that link is in place, scheduling against a work center is no different from scheduling against a person or a piece of equipment. This page covers how to turn work center planning on and the two small additions the integration makes to the work center pages.
## Setup

### Enable work center solution
Work center planning is off until you ask for it. In the FastTrack wizard, select `Enable Work Center Planning` to switch on the solution.
## Usage
### Actions
The integration extends the default actions on this page with the following items.
#### List Actions
The list actions match the standard pattern shared across the connector. See [common actions](/integrations/bc/modules/actions) for an overview of the list actions for this type.
#### Card actions
From the work center card you can push a single record on demand with one new action:
- Send work center

### Fields
Not every work center belongs on the board. Alongside the standard fields, the integration adds one field that lets you keep a work center in Business Central without cluttering the planner's view:
- Hide in planning board
---
## Modules
import Modules from '@site/src/components/docs/bc/Modules';
Most Business Central implementations stall in the same place: the data that needs scheduling already lives in sales orders, service items and jobs, but getting it onto a planning board means custom integration work nobody budgeted for. The Dime.Scheduler FastTrack for Microsoft Dynamics 365 Business Central removes that step. It is a fast, out-of-the-box implementation that already understands the standard areas of Business Central your customer works in every day, including sales, service and jobs.
You do not wire any of it up by hand. A wizard walks you through the setup and generates all the configuration you need, so instead of spending the first week of a project mapping fields, your customer can start planning right away.
Each module above is a self-contained starting point. Pick the card that matches the work your customer plans, jobs, service, sales and so on, to learn how that module behaves and what it puts on the planning board. The modules also share a common toolkit: the same set of [common actions](/integrations/bc/modules/actions) is available in most of the supported modules, so once you know how planning works in one, the rest follow the same patterns.
---
## Requirements
Before you install the Business Central connector, check that your customer's environment meets the requirements below.
## Supported versions
For the Microsoft Dynamics 365 Business Central versions we support and our lifecycle policy, see the [lifecycle page](lifecycle.md). Dime.Scheduler works with *any* [license type](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/deployment/licensing).
## Supported client types
These client types are supported on all versions:
- Web Client
- Mobile Client
The Windows client is also supported on versions 13 and 14.
## Connector type
Both **objects** and **extensions** are supported. The objects exist only for backwards compatibility, so we recommend switching to extensions: all new work goes into the connector extension.
---
## Source Types
import Content from '../../develop/api/global/sourceapp.md';
Every record that flows between Business Central and Dime.Scheduler carries two strings: `Source App` and `Source Type`. Together they answer a single question that the integration cannot work without: when a planner schedules something on the board, which system and which table does that appointment belong back in? You've almost certainly set up `Source App` already, since it's required just to establish the connection between BC and Dime.Scheduler. `Source Type` is the field that does the actual routing, and this page is about how that routing works. First, a quick recap of what both fields mean.
## Definitions
## How it works
In the standard connector for Dime.Scheduler in Business Central, you don't create source types by hand. The FastTrack wizard or the Dime.Scheduler setup page does it for you: whenever you enable a module, a new entry appears in the `Dime.Scheduler Source Types` list.

:::tip
Hit ALT + Q and look for `Dime.Scheduler Source Types` in the search bar to locate this table.
:::
A source type identifies a coherent area in Business Central. The standard connector defines the following types and associates each with the corresponding BC tables:
| Source Type | Table(s) used |
| ----------- | -------------------------------------------------- |
| Resource | 156 |
| Service | 5900, 5901 |
| Job | 167, 1001, 1003 |
| Assembly | 900 |
| Production | 99000754, 99000758, 5405, 5409, 99000754, 99000758 |
| Sales | 36, 37 |
| HR | 5206, 5207 |
These types ship out of the box together with the code units that handle the appointment information coming back in. Note that a single source type can map to several tables: a 'Job', for instance, spans the job header, a job task, job planning lines, and any other level you need. You're free to add your own source types too and link them to a custom table. Underneath, a source type is just a string, and that string is what drives the routing the moment a new appointment lands in the Dime.Scheduler Appointments table.
Every task that needs planning is pinned down by four pieces of identity:
- **Source App**: a unique string describing which administration or system owns the record. Dime.Scheduler uses it to find the matching backoffice systems to send appointment information back to. A single source app can have multiple connectors. A common pattern is to write data back to BC and also invoke a webhook to send emails, run Power Automate flows, and so on.
- **Source Type**: a unique string indicating which table the record came from. This is what prevents duplicate numbers from colliding. 'ORDER002' might exist in several tables; decorating a task with a source type lets the system tell them apart, so SALES ORDER002 and SERVICE ORDER002 can happily coexist.
- **Job No**: typically the `No.` field at the header level of the order.
- **Task No**: the `No.` field of the level the customer actually wants to plan. When planning at the header level, `Task No` is identical to `Job No.` in Dime.Scheduler.
To the planner, this is mostly invisible metadata, right up until the moment they schedule a task on the planning board. That's when it earns its keep: Dime.Scheduler reaches out to the systems that asked to be notified, specifically the [connectors that match the job's 'Source App'](/administration/connectors), and hands them the planning data.
Not every system cares about the 'Source Type'. The standard Exchange connector, for example, ignores it entirely, since the appointment only needs to land in the resource's Outlook calendar. The Business Central integration is the opposite case: without the source type it has no way of knowing which table to write back to. Consider a task tied to a...
- ...**service item line**, which you may want to process as a **resource allocation**.
- ...**job task**, which you may want to process as a **job planning line**.
- ...**cause of absence**, which you may want to process as an **employee absence registration**.
- ...**custom table**, which you'll probably want to process into a **custom table** as well.
The standard extension ships workflows for every supported source type listed above. For each entry in the source types table you specify a code unit to run for every incoming appointment with that source type; when Dime.Scheduler finds a match, it executes that code unit. That single hook is what leaves the door wide open for customization. You can override the handling logic while keeping the default source types, or replace both with your own source types and your own handling code units.

In the example above, the incoming appointment is routed to code unit no. 2087646, which writes the appointment information back to the job using the [default functionality](/integrations/bc/modules/job#planning-jobs) shipped in our standard extension.
## Source Types table
The table itself is small, but what you put in it decides exactly how planning data from Dime.Scheduler is processed in Business Central.
| Field, tab or group | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Source Table No. | Enter the table number or select it from the list for each table that is sent to Dime.Scheduler (e.g., 5901, Service Item Line or 1001, Job Task). |
| Source Type | Define the identifier of the source type. Use a concise and descriptive value (max. 10 characters) like 'SERVICE', 'JOB', 'TRANSPORT', etc. |
| Processing Codeunit No. | Enter the number of the codeunit that processes planning data coming from Dime.Scheduler into Business Central. |
| Appointment Template Code | In Dime.Scheduler, you can define custom fields on an appointment card. You can have different fields based on the type of task using an appointment template. Specify the appointment template code for the task, based on the source type. For more information about appointment templates refer to [this doc](/user/features/appointment-templates). |
| Action Report No. | The report to run when the user selects an action URI for this source type. Leave 0 (zero) to use the standard report DS Action Launch. The report parses the parameters passed by Dime.Scheduler as field filters and then launches the defined codeunit (see below) |
| Action New Description | Action new is the URI shown when the user opens the context menu in the planning board. The description or caption is shown to the user. |
| Action New Codeunit No. | The codeunit executed by the report defined in Action Report No. |
| Action Edit Description | Action Edit is the URI shown when the user opens the context menu of an appointment with matching source app and source type fields. The description or caption is shown to the user. |
| Action Edit Codeunit No. | The codeunit executed by the report defined in Action Report No. |
The "Send Action URI" command button generates and sends the ActionURI to Dime.Scheduler for both 'Action New' and 'Action Edit', as long as a value is entered in the respective description field.
## Diagram
The diagram below summarizes how the integration framework between Dime.Scheduler and Business Central fits together.
It starts in Business Central, where users create orders that need to be planned in Dime.Scheduler. Each record sent from BC carries a `Source App` and `Source Type` field. The `Source App` is always the same and comes from the configuration item in the FastTrack wizard, while the `Source Type` typically differs across the various tables in play.
When an appointment is planned, Dime.Scheduler sends that information back to whichever systems want to be notified, which you configure on the [Connectors page](/administration/connectors) in Dime.Scheduler. Once Dime.Scheduler has deposited the appointment info in the backoffice system, processing it is that system's job. You can lean on the standard connectors that Dime Software has developed, or supply your own logic.
The standard connector for Dime.Scheduler in Business Central supports multiple tables, and that's exactly where the `Source Type` comes in. Dime.Scheduler posts all planning lines to the Dime.Scheduler Appointments table in Business Central and triggers a workflow that looks up the matching source type and invokes the corresponding code unit to write back to the right table.
---
## Transaction Errors
When an appointment sync to Business Central fails, the failed transaction shows up on the [Errors page](/administration/transactions/errors) with a message prefixed by a stable code in square brackets, e.g. `[DSBC003] BC server too busy`. The code never changes once issued, so you can search transactions or logs for `[DSBC003]` to find every appointment that hit the same failure.
**Retried** codes are picked up automatically by the retry job (3 attempts, exponential backoff with jitter, then the row stays `Failed` for the next pass). **Fix configuration** codes need a connector or back-office setup change first - they are not retried. **Undiagnosed** failure shapes should be sent to support so a rule can be added.
Each section below pairs 💡 (what it means) with 🛠️ (what to do).
## Quick lookup
| Code | Goes to | Retry |
|---|---|---|
| `DSCFG001` | [Back-office not allowed](#back-office-not-allowed) | fix config |
| `DSCFG002` | [Missing required field](#missing-required-field) | fix config |
| `DSCFG003` | [Back-office returned no data](#back-office-returned-no-data) | fix config |
| `DSCFG004` | [Schema mismatch](#schema-mismatch) | fix config |
| `DSCFG005` | [Wrong system type](#wrong-system-type-rest-vs-soap) | fix config |
| `DSCFG006` | [OAuth credentials unparseable](#oauth-credentials-unparseable) | fix config |
| `DSCFG007` | [Malformed connector URL](#malformed-connector-url) | fix config |
| `DSCFG008` | [HTTP request unauthorized](#http-request-unauthorized) | fix config |
| `DSCFG009` | [HTTPS required for SOAP](#https-required-for-soap) | fix config |
| `DSCFG010` | [Unsupported connector configuration](#unsupported-connector-configuration) | fix config |
| `DSCFG011` | [OAuth client rejected by Entra ID](#oauth-client-rejected-by-entra-id) | fix config |
| `DSCFG012` | [HTTP endpoint not found](#http-endpoint-not-found) | fix config |
| `DSCFG013` | [Entra ID tenant not found](#entra-id-tenant-not-found) | fix config |
| `DSCFG014` | [Entra ID app not in tenant](#entra-id-app-not-in-tenant) | fix config |
| `DSCFG015` | [App registration disabled](#app-registration-disabled) | fix config |
| `DSCFG016` | [Resource not consented in tenant](#resource-not-consented-in-tenant) | fix config |
| `DSHTTP001` | [HTTP transient (429/5xx)](#http-transient-4295xx) | retried |
| `DSHTTP002` | [HTTP proxy tunnel failure](#http-proxy-tunnel-failure) | retried |
| `DSBC001` | [HTTP request unauthorized](#http-request-unauthorized) | fix config |
| `DSBC002` | [BC endpoint not found](#bc-endpoint-not-found) | fix config |
| `DSBC003` | [BC server too busy](#bc-server-too-busy) | retried |
| `DSBC004` | [Request channel timeout](#request-channel-timeout) | retried |
| `DSBC005` | [BC communication fault](#bc-communication-fault) | retried |
| `DSBC006` | [SOAP "unknown type" fault](#soap-unknown-type-fault) | fix config |
| `DSBC007` | [SOAP "service not found" fault](#soap-service-not-found-fault) | fix config |
| `DSBC008` | [BC REST API missing Location header](#bc-rest-api-missing-location-header) | fix config |
| `DSBC009` | [SOAP HTTP transient (429/5xx)](#soap-http-transient-4295xx) | retried |
| `DSBC010` | [BC insufficient permissions](#bc-insufficient-permissions) | fix config |
| `DSBC011` | [BC lock timeout](#bc-lock-timeout) | retried |
| `DSDB001` | [SQL transient](#sql-transient) | retried |
| `DSDB002` | [Database access](#database-access) | retried |
| `DSDB003` | [Concurrency conflict](#concurrency-conflict) | retried |
| `DSDB004` | [SQL timeout](#sql-timeout) | retried |
| `DSUNK001` | [Undiagnosed](#undiagnosed) | undiagnosed |
## Request channel timeout
> The request channel timed out attempting to send after 00:01:00. Increase the timeout
> value passed to the call to Request or increase the SendTimeout value on the Binding.
> The time allotted to this operation may have been a portion of a longer timeout. The
> HTTP request to 'http://[server]/[instance]/WS/[company]/Page/Appointment' has exceeded
> the allotted timeout of 00:01:00.
The WCF channel timed out before BC responded. The operation may have completed on BC's side regardless. The most common cause is that the web service is not accessible from Dime.Scheduler, typically due to firewall restrictions blocking the connection. Other causes: network latency, BC server under heavy load, large payloads, slow database queries on the BC side, or complex AL business logic.
Transient - retried automatically. If it persists:
1. **Check firewall and network accessibility:**
- Verify the Business Central web service URL is accessible from the Dime.Scheduler cloud.
- Open the required firewall ports to allow traffic from Dime.Scheduler to Business Central.
- On-premises: ensure the web service endpoint is published and accessible externally.
- Cloud: verify the service is not blocked by network security groups or Azure firewall rules.
- Test connectivity using tools like `telnet`, `curl`, or a browser from the Dime.Scheduler cloud.
2. **Verify connector URL:**
- Navigate to **Administration** > **[Connectors](/administration/connectors)** in Dime.Scheduler.
- Verify the web service URL is correct and accessible.
3. **Increase the timeout if connectivity is confirmed:**
- Edit the Business Central connector.
- Increase the **Request Timeout** value (for example, from 60 to 120 seconds or higher).
4. **Optimize Business Central performance:**
- Review and optimize custom code in the appointment processing codeunits.
- Check for database locks or slow queries.
- Review the Business Central server event logs for performance issues.
:::tip
If timeouts occur frequently during specific times of day, this often indicates server load issues. Consider scheduling heavy processing tasks outside of peak planning hours.
:::
## HTTP request unauthorized
> The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic realm=...'
Variations include `401 Unauthorized` and `Invalid credentials`.
`DSBC001` - WCF rejected the SOAP-level credentials (the configured username/password is wrong or the security mode doesn't match what BC expects). `DSCFG008` - the back-office returned HTTP 401 or 403 to the REST/OAuth path. The authentication method configured on the connector does not match what Business Central expects, or the credentials are incorrect or expired.
**Step 1 - Check the BC version**
- **Business Central Cloud (SaaS)** - use OAuth 2.0.
- **Business Central On-Premises** - use Basic Authentication, Windows/NTLM, or OAuth 2.
**Step 2 - Fix the authentication**
1. Go to **Administration** > **[Connectors](/administration/connectors)** in Dime.Scheduler.
2. Edit the Business Central connector.
3. Set authentication to the desired authentication method.
4. Fill in the required authentication fields.
:::tip[Using OAuth 2?]
Follow the [OAuth setup guide](/integrations/bc/guides/oauth2).
:::
:::tip[Using Basic auth?]
If the test fails with the wrong password, verify the account is not locked in Active Directory or Business Central.
:::
**Step 3 - If the test still fails**
Check these common issues:
| Problem | Fix |
| ------------------------------ | -------------------------------------------------------------- |
| OAuth: "Client secret expired" | Generate a new secret in Azure Portal and update the connector |
| OAuth: "Invalid tenant ID" | Verify the tenant ID matches your Azure AD tenant |
| Basic Auth: "Invalid username" | Try without the domain prefix (just `USERNAME`) |
| Basic Auth: "Account locked" | Unlock the account in Active Directory or BC |
| "Permission denied" | Verify the account has access to Dime.Scheduler tables |
**Step 4 - Retry the failed transaction**
Once the test connection succeeds, retry the failed appointment from the [Errors page](/administration/transactions/errors).
## BC endpoint not found
No SOAP listener answered at the configured URL. WCF raises `EndpointNotFoundException` for both "host unreachable" (DNS failure, tunnel offline, connection refused, port closed) and "host answered with 404" - the URL or the service is wrong.Verify the BC SOAP endpoint URL (typically ending in `/WS//Page/`) **and** that the host is reachable from the back-office service. The page may be unpublished, the BC tenant may be down, or the URL may be pointing at a stale environment.
## BC server too busy
BC's SOAP layer responded with `ServerTooBusy`. The BC tenant is at capacity.Transient - retried automatically. If it keeps happening, contact BC support about tenant throttling.
## BC communication fault
A network blip (connection reset, socket EOF, or I/O error) interrupted the SOAP call.Transient - retried automatically. If it persists, network or proxy investigation needed.
## SOAP "unknown type" fault
BC returned a SOAP fault like `The type X is unknown.` - usually because the published AL extension is out of date and no longer matches the WSDL the connector was generated against.Republish the AL extension on the BC tenant.
## SOAP "service not found" fault
BC returned `Service "X" was not found!` - the SOAP URL is correct but the requested page (`Page/Appointment`) is not exposed at that URL.Publish the missing page in BC's Web Service registry, or correct the page reference in the connector URL.
## BC REST API missing Location header
The BC REST API created the appointment but the response didn't include a `Location` header pointing at the new record - so Dime.Scheduler can't post the follow-up `process` call.The appointment record was likely created on BC; either trigger the BC-side `process` action manually, or check why BC isn't returning a Location header (custom AL code stripping it, reverse proxy rewriting it, etc.).
## SOAP HTTP transient (429/5xx)
The BC SOAP call surfaced as `ProtocolException` with `The remote server returned an unexpected response: (NNN) ...` where NNN is 429/502/503/504. BC's HTTP front-end (or a proxy/load-balancer in front of it) is temporarily unhealthy. SOAP-path counterpart of `DSHTTP001`.Transient - retried automatically. If it persists, check BC tenant health, Azure front door, or any reverse proxy in front of BC.
## BC insufficient permissions
BC returned a SOAP fault like `Sorry, the current permissions prevented the action. (Page 2087632 Dime DS Appointment Resources Resources Execute: )`. The integration user authenticated successfully but lacks the permission set required to read/execute the page named in the parentheses. Distinct from `DSCFG008` (HTTP 401/403 at the transport layer) and `DSBC001` (WCF-level credential rejection) - here the user is recognized and the call gets through, but the AL permission check inside BC rejects it.In BC, open **Permission Sets**, find the Dime.Scheduler permission set (or the one assigned to the integration user), and grant the missing object permission named in the fault. The fault names the exact page ID, type, and permission (Read/Insert/Modify/Delete/Execute) that's missing, so add that row.
## BC lock timeout
BC returned a SOAP fault like `We can't save your changes right now, because the data is being updated in a transaction done by another session. ... Please try again later.`. The row the connector was writing was locked by another session's open transaction - pure contention, nothing misconfigured. The call succeeds once the lock clears.Transient - retried automatically (5 attempts, 2s exponential backoff plus jitter, a longer curve than the standard transients because the fault warns the wait "may take a while"). If it persists, look for a long-running or stuck transaction on the BC tenant holding the lock - a hung NAS session, a manual posting, or another integration writing the same records.
## HTTPS required for SOAP
The BC SOAP `WebServiceAddress` uses `http://`, but BC's SOAP binding requires Transport security (HTTPS). WCF refuses to open the channel and raises `ArgumentException: The provided URI scheme 'http' is invalid; expected 'https'. (Parameter 'via')`.Change the connector URL to `https://`. BC Online only publishes the SOAP endpoint over HTTPS; on-prem BC must have the SOAP service configured with SSL.
## Back-office not allowed
The back-office system was reached but is not configured to accept appointments from Dime.Scheduler (e.g. *Send appointment to web service* is off, or the back-office side rejected the call as not allowed).Open the [connector settings](/administration/connectors), ensure outbound is enabled and the back-office side has the receiving page/extension installed.
## Missing required field
The data Dime.Scheduler sent was rejected as invalid input by the connector. Usually a missing required field on the appointment payload.Check the appointment for required field values that the back-office expects. Common culprits: empty `JobNo`/`TaskNo` when the back-office requires them.
## Back-office returned no data
The back-office returned no data when Dime.Scheduler asked for an appointment by ID. The appointment doesn't exist on the back-office side.Confirm the appointment was actually created on the back-office, and that the IDs match.
## Schema mismatch
The back-office returned data in a type Dime.Scheduler didn't expect. Usually a schema mismatch after an extension update on the back-office side.Republish the AL extension on BC (or equivalent) so the back-office schema matches what the connector expects.
## Wrong system type (REST vs SOAP)
The connector URL points to a back-office that isn't the right kind of system. E.g. an OData REST URL pointed at a SOAP-only endpoint.Verify the `WebServiceAddress` on the connector matches the API type (SOAP vs REST) selected on the connector record.
## OAuth credentials unparseable
The OAuth credential string couldn't be parsed. The connector's password field stores the OAuth client config as a `key=value;key=value` blob and one of the required keys (`client_id`) is missing or the blob has no semicolons.Re-enter the OAuth credentials in the connector settings; ensure all required keys (`client_id`, `client_secret`, `token_endpoint`) are present and semicolon-separated. The [OAuth setup guide](/integrations/bc/guides/oauth2) walks through the full flow.
## Malformed connector URL
The `WebServiceAddress` is not a valid URI (typos, missing scheme, illegal characters).Open the connector settings and fix the URL.
## Unsupported connector configuration
The connector record asks the factory for a combination it can't build - typically `AuthType` or `ApiType` is `None`, or an auth flow Dime.Scheduler doesn't ship for that back-office (e.g. one of the disabled OAuth flows on NAV). The transaction message names the specific connector and the unsupported value (e.g. `BC (SOAP) connector ACME: None is not supported.`).Open the [connector settings](/administration/connectors) and pick a supported **Authentication type** and **API type** for that back-office. Saving the connector without choosing one leaves it on `None`, which is what trips this code.
## OAuth client rejected by Entra ID
Entra ID (Azure AD) rejected the OAuth client credentials with `error: "invalid_client"` - most commonly `AADSTS7000215: Invalid client secret provided`, but the same code also covers expired secrets, a wrong `client_id` for the tenant, or sending the secret **ID** instead of the secret **value**. The credential blob parsed fine (otherwise `DSCFG006` would fire); the values just aren't accepted by AAD. Distinct from `DSCFG008` (HTTP 401 from the back-office itself) - this one fires before any back-office call, at token acquisition.Open the app registration in Entra ID, generate a fresh client secret, copy the **Value** (not the Secret ID), and paste it into the connector's OAuth credential blob under `client_secret`. Re-save the connector. The `AADSTS` sub-code in the recorded message points at the exact reason (e.g. `7000215` = bad secret value, `7000222` = expired secret).
## HTTP endpoint not found
The back-office returned HTTP 404 on a REST call. The host answered, but nothing is published at the URL Dime.Scheduler posted to. REST counterpart of `DSBC002` (`EndpointNotFoundException` on the SOAP path). Not retried - a 404 is a misconfiguration, not a transient blip.Verify the connector's `WebServiceAddress`. Common causes: a typo or stale environment in the URL, the OData API page is not published on the back-office, or the company segment in the OData path is wrong. Confirm the URL resolves to the published API page from the back-office service host.
## Entra ID tenant not found
Entra ID rejected token acquisition with `AADSTS90002: Tenant '' not found`. The tenant in the configured OAuth authority doesn't exist, or has no active subscription. Fires at token acquisition, before any back-office call - distinct from `DSCFG011` (bad/expired secret) and `DSCFG008` (HTTP 401 from the back-office itself). Not retried - a wrong tenant won't self-heal.Check the tenant ID in the connector's OAuth credential blob (the `token_endpoint` / authority URL). Confirm it's the correct directory (Tenant) ID from the app registration's Entra ID overview, that you're pointing at the right cloud, and that the tenant has an active subscription.
## Entra ID app not in tenant
Entra ID rejected token acquisition with `AADSTS700016: Application with identifier '' was not found in the directory ''`, or the older equivalent `AADSTS70001: Application '' is not registered for this tenant`. The tenant exists (otherwise `DSCFG013` would fire) and the secret was never evaluated - AAD couldn't find the app registration in that directory at all. Either the `client_id` belongs to a *different* tenant than the one in the authority, the app is single-tenant and hasn't been installed / admin-consented in this directory, or the `client_id` is stale (the registration was deleted or recreated). Fires at token acquisition, before any back-office call - distinct from `DSCFG013` (tenant doesn't exist), `DSCFG015` (app exists but service principal is disabled), and `DSCFG011` (bad/expired secret). Not retried - a missing app registration won't self-heal.Confirm the `client_id` in the connector's OAuth credential blob matches an app registration that lives in the tenant configured in the authority URL. If the app is single-tenant, point the authority at *its* directory (Tenant) ID. If it's meant to be multi-tenant, have a tenant admin grant admin consent (`https://login.microsoftonline.com//adminconsent?client_id=`) so the service principal is provisioned in that directory.
## App registration disabled
Entra ID rejected token acquisition with `AADSTS7000112: Application ''() is disabled`. The app registration exists in the resource tenant (otherwise `DSCFG014` would fire) but its **service principal object is disabled** there - *Enabled for users to sign-in* is set to **No** on the enterprise application. Per [Microsoft's guidance](https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/app-integration/error-code-aadsts7000112-application-is-disabled), the disable can have been done by the resource tenant admin, by the app owner, or globally by Microsoft itself - so the remediation starts with finding out *why* it's disabled, not just flipping it back on. Fires at token acquisition, before any back-office call. Not retried - a disabled service principal won't self-heal.Confirm with the resource tenant owner whether the disable was intentional. If it was (access deliberately revoked, or Microsoft globally deactivated the app), the integration stays broken until the underlying decision is reversed - don't just re-enable it. If it was a mistake, re-enable from the Entra portal (**Enterprise applications** > find the app by the client ID in the message > **Properties** > **Enabled for users to sign-in** = **Yes**), or via Microsoft Graph PowerShell: `Update-MgServicePrincipal -ServicePrincipalId -AccountEnabled:$true`. Once re-enabled, the next retry of the failed transaction will succeed without any connector change.
## Resource not consented in tenant
Entra ID rejected token acquisition with `AADSTS500011: The resource principal named '' was not found in the tenant named ''`, or the equivalent `AADSTS650056: Misconfigured application` variant. The connector's *client* app is fine (otherwise `DSCFG014` would fire), but the **resource** the connector is requesting a token for - typically the Business Central API enterprise app - has no service principal in the customer's tenant. The resource app was never admin-consented into that directory, so AAD has nothing to issue a token *to*. Fires at token acquisition, before any back-office call. Not retried - missing consent won't self-heal.Have a tenant admin grant admin consent to the resource app in the customer's tenant. For Business Central this is typically the BC API enterprise app - admin-consent it via `https://login.microsoftonline.com//adminconsent?client_id=` so its service principal is provisioned. Distinct from `DSCFG014`, which is about consenting the *client* app; here the client is fine and it's the *resource* that's missing. Once the resource service principal exists in the tenant, the next retry will succeed without any connector change.
## HTTP transient (429/5xx)
The back-office returned HTTP 429 (Too Many Requests), 502, 503, or 504. The server is busy or down. The `Retry-After` header is honored if present.Transient - retried automatically. If it persists, check the back-office status page or tenant-level throttling.
## HTTP proxy tunnel failure
The outbound proxy returned a non-2xx response to the HTTPS `CONNECT` request, so the tunnel to the back-office never opened. `HttpRequestException.HttpRequestError` is `ProxyTunnelError`; `StatusCode` is null because no response was received from the destination.Transient - retried automatically. If it persists, the issue is the outbound proxy health (e.g. `proxy.internal.*.dimescheduler.com`) rather than the back-office itself - escalate to Dime.Scheduler support.
## SQL transient
SQL Server returned one of Microsoft's documented transient error numbers (deadlock victim, throttling, connection reset, etc.).Transient - retried automatically. If it persists, contact Dime.Scheduler support.
## Database access
A repository-level access failure (connection lost, query aborted).Transient - retried automatically.
## Concurrency conflict
Two requests tried to update the same record at the same time and one lost the race.Transient - retried automatically. If it keeps happening on the same appointment, there's an integration loop somewhere.
## SQL timeout
SQL command timeout - the query took longer than the configured limit.Transient - retried automatically. If chronic, contact Dime.Scheduler support about index/query performance.
## Undiagnosed
An exception that doesn't match any known rule reached the pipeline. It was logged for the maintainers and recorded as `Failed` so the RetryService picks it up later.Send the transaction ID and full error message to Dime.Scheduler support - this code means we haven't seen this failure mode before and need to add a rule for it.
## General troubleshooting steps
When encountering transaction errors, follow these steps. For more detailed guidance, see the [Business Central Troubleshooting](/integrations/bc/troubleshooting) guide.
1. **Check the transaction details:**
- Navigate to **Administration** > **Transactions** > **[Errors](/administration/transactions/errors)**.
- Click on the failed appointment to view detailed error information.
- Review the error message - the code in square brackets at the start maps to a section above.
2. **Verify connector configuration:**
- Navigate to **Administration** > **[Connectors](/administration/connectors)**.
3. **Check the Business Central system:**
- Review the **Dime.Scheduler Appointments** table.
- Verify the web service endpoints are available and responding.
4. **Retry the transaction:**
- After resolving the underlying issue, retry the failed transaction.
---
## Troubleshooting(Bc)
import AreaTroubleshootingLanding from '@site/src/components/AreaTroubleshootingLanding';
The most common questions about the two-way message flow between Dime.Scheduler and
Business Central, grouped by what you're seeing.
## Business Central to Dime.Scheduler
The first thing you do on a new Dime.Scheduler instance is run the
[FastTrack Wizard](/integrations/bc/install/bc-to-ds) in Business Central. Work through
every step carefully.
The first step is also the most common place to get stuck: it's where you set up the
flow to Dime.Scheduler, with the environment and the credentials to authenticate
against it.
Watch out for these:
- **User/password authentication**
- An account used in more than one tenant.
- A real person's account instead of a service account. The day that person resets
their password, the integration breaks. Use a service account like
`BCSync@YourCustomerDomain.com`.
- Don't use any domain managed by Dime Software, either: that's a security risk.
- It must be a **Forms** account on a domain your customer controls. MS Entra ID
accounts aren't supported by the API.
- **Environment** (sandbox or production): the on-prem option refers to Dime.Scheduler
on-prem, not BC on-prem.
## Dime.Scheduler to Business Central
The flow from BC to Dime.Scheduler is easy to set up, thanks to the FastTrack Wizard and
the connector. The return flow is trickier, because it depends on how that particular BC
is set up. Two things can fail: Dime.Scheduler can't reach BC, or BC receives the
appointment but can't process it.
### Appointments don't show up in Business Central
Start at the [transactions](/administration/transactions/). Open them from the sidebar,
or right-click the appointment on the planning board and choose **View transactions**.
That opens the appointment's detail page: basic info, the associated connector (if any),
and every transaction that tried to write back to Business Central, Exchange, or a
webhook.

The transactions usually tell you exactly what went wrong. Common causes:
- **No connector** for this item. Data sent from BC carries a Source App identifier. To
send a planned appointment back, Dime.Scheduler looks for a connector with the same
Source App. No match, nothing sent.
- **Invalid or incomplete connector**. Usually the wrong URL or credentials (or app
registration details, for BC cloud).
- **Back-office unavailable**: the administrators need to make the BC SOAP URL reachable
from Dime.Scheduler.
### Appointments couldn't be processed in Business Central
When Dime.Scheduler drops the appointment into BC, the transaction is marked successful
on the Dime.Scheduler side. That doesn't guarantee BC processed it.
In Business Central, open the **Dime.Scheduler Appointments** table. It lists the
transactions and their status. Anything not marked **Processed** went wrong: click the
status column for details.

**Processed** means the planning data went through as expected. **Error** means it
didn't, and the Error Message column tells you exactly why.
## Time zone mismatch
> Appointments scheduled in Dime.Scheduler show incorrect times in Business Central
> (e.g. 9 AM in Dime.Scheduler appears as 7 AM in BC).
**Applies to:** Business Central SaaS.
### Cause
A time zone mismatch between Dime.Scheduler and Business Central, usually a fixed shift
(2 hours is common), almost always from an incorrect time zone in the connector setup.
There's no error code for this: the appointment is delivered fine, just at the wrong
time, so it's a configuration quirk rather than an exception.
### Fix
Open the **Dime.Scheduler Setup** page in Business Central and set the **Time Zone**
field:

The same field also appears at the bottom of the FastTrack wizard:

## Transaction errors
Every failed transaction is tagged with a stable code (e.g. `[DSBC003]`). See
[Transaction Errors](/integrations/bc/transaction-errors) for the full reference and
partner-facing fixes.
---
## Microsoft Entra ID App Registration
For Dime.Scheduler to reach your Microsoft 365 calendar data, the administrator grants
it access through an [application registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app)
in Microsoft Entra ID. A registration defines exactly what an application may do, so
grant the least-permissive set that works and nothing more.
You can create the registration two ways: by hand in the Azure portal, or with a
PowerShell script. Either way, you'll end up with three values to paste into the
[Exchange setup page](/integrations/exchange/configuration):
- the Entra ID **tenant id**
- the app registration **client id**
- the app registration **client secret**
:::tip[TL;DR]
Create an Entra ID app registration with the required API **application** permissions,
then copy the **tenant id**, **client id**, and **client secret** into Dime.Scheduler's
Exchange configuration page.
**Required API application permissions:**
- Calendars.Read/Write
- MailBoxSettings.Read/Write
- User.Read.All
A common mistake is creating **delegated** permissions instead of **application**
permissions. Create application permissions, and grant admin consent.
:::
## Option 1: create the app manually
### Register a new application
Sign in to the [Azure portal](https://portal.azure.com), making sure you're on the
right account and subscription. Go to the **Microsoft Entra ID** service, open the
**Manage** submenu, and click **App registrations**.

Click **New registration** and fill in:
- **Name**: e.g. Dime.Scheduler
- **Supported account types**: *Accounts in this organizational directory only*
- **Redirect URI**: select Web, then enter `https://app.dimescheduler.com/signin-microsoft`
### Add permissions
- On the app's Overview page, select **API permissions**.
- Click **Add a permission** and add:
- **Calendars.Read/Write**
- **MailBoxSettings.Read/Write**
- **User.Read.All**
- Grant admin consent.
### Get the client id and secret
The **client id** is on the app's Overview page:

Create the **client secret** under **Certificates & secrets**:

Secrets expire, so either pick a far-off expiry date or update the value in
Dime.Scheduler before it lapses.
## Option 2: create the app with a script
Open PowerShell as administrator and install the script from the
[PowerShell Gallery](https://www.powershellgallery.com/packages/DimeScheduler.InstallExchangeApp):
```powershell
Install-Script -Name DimeScheduler.InstallExchangeApp
```
Move into the script's folder:
```powershell
cd (Get-InstalledScript -Name "DimeScheduler.InstallExchangeApp").InstalledLocation
```
You'll need your **Azure tenant ID** and **tenant name** (Microsoft's
[guide to finding them](https://docs.microsoft.com/en-us/partner-center/find-ids-and-domain-names)).
Then run:
```powershell
.\DimeScheduler.InstallExchangeApp.ps1 -tenantId MyAzureTenantId -tenantName 'MyAzureTenantName'
```
The script creates the app registration and a client secret, adds the required
permissions (Calendars.Read/Write, MailBoxSettings.Read/Write, User.Read.All), and
grants admin consent. It prints the **client id** and **client secret** at the end -
copy the secret right away, or you'll have to generate a new one.
---
## Bidirectional synchronization
The Exchange connector keeps appointments in sync in both directions: from the Dime.Scheduler planning board to the resources' Outlook calendars, and from those calendars back to Dime.Scheduler. It does this through the Microsoft Graph API, the gateway to data and intelligence in Microsoft 365, and layers its own rules and logic on top to decide what gets shared and how.
Think of the connector as a messenger that takes data from one system and makes sure it lands correctly in the other. The most important part of that job is **data reconciliation**, which maps data between the two systems and connects what would otherwise be two isolated data silos.
**Business rules** matter too, because they protect the privacy of the resources. Resources can decide which types of events are shared with the planner in Dime.Scheduler. The connector acts as the gateway between Dime.Scheduler and Exchange and applies these rules, which include the busy status and the sensitivity of the event in Exchange. Any property of the Dime.Scheduler appointment can also be used to decide whether to send data to someone's Outlook calendar.
When an appointment changes in the planning board, the connector does a few things. First, it checks whether Dime.Scheduler appointments should be synchronized with Exchange for that resource. It then checks whether the event already exists in Microsoft 365 and creates, updates, or deletes the event in Exchange accordingly.
The reverse works the same way. When an event changes in an Outlook calendar, the connector decides whether it should be sent to Dime.Scheduler. It takes the resource's preferences into account, such as the show-as status and sensitivity, then maps the Outlook event to a Dime.Scheduler appointment and creates, updates, or deletes it accordingly.
---
## Categories
import { FaSlidersH } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Indicators", link: "https://app.dimescheduler.com/indicators"}]} />
:::gotcha[Required permission]
Syncing categories to Outlook requires the **MailBoxSettings.Read/Write** application permission on the Microsoft Entra ID app registration. Without it, category mapping will silently fail. See [Add permissions](/integrations/exchange/azureapp#add-permissions) for the full permission set.
:::
Map each Dime.Scheduler category to one of the 20 colors that Outlook supports, so appointments keep a consistent look once they sync to a user's mailbox.
In the Indicators setup, the Categories grid has an extra column, "Outlook Preset", listing the limited set of 20 colors available in Outlook. For each category in Dime.Scheduler, pick the Outlook color that comes closest.

When an appointment is pushed from Dime.Scheduler to Exchange, Outlook creates a category named after the Dime.Scheduler color and applies the Outlook preset color you chose:

The category is clearly visible in the details page of the Outlook event:

---
## Configuration
import { FaCalendar } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Settings", link: "https://app.dimescheduler.com/exchangesetup"}]} />
Every Exchange setting lives on this one setup page.

## Microsoft Entra ID
First, give Dime.Scheduler access to Microsoft Graph by pasting in the values from your
[Entra ID app registration](/integrations/exchange/azureapp).
Once those are filled in, Dime.Scheduler can sync planning to people's Outlook
calendars. Two more things to do: [enable the Exchange integration](/administration/settings/resources)
in the resource setup, and give each resource an email address your Exchange online
administration can use.
## Exchange to Dime.Scheduler
Pulling appointments *from* Exchange back into Dime.Scheduler takes a bit more setup.
To turn it on, tick the bidirectional checkbox and set the range for the
[Microsoft Graph event deltas](https://learn.microsoft.com/en-us/graph/api/event-delta?view=graph-rest-1.0&tabs=http).
On the right of this section, choose whether the same sync settings apply to **all**
resources, or whether each resource decides for itself which appointments flow back.
Two filters are available: **Show As** status and **Sensitivity**.
An appointment flows back to Dime.Scheduler when the resource has opted in to these
settings:
- Enabled (called `Exchange integration (user)` in the resource setup)
- Show as status (choose any):
- Free
- Tentative
- Office
- Out of office
- Sensitivity (choose any):
- Normal
- Personal
- Private
- Confidential
If you let resources decide for themselves, each needs a user account. Grant them
access to the application; when their email matches a resource's email, they can set
their own preferences on a dedicated page.


---
## Resource event deltas
The **Resource event deltas** page lists calendar events the Exchange connector observed on a resource's mailbox but could not align with an existing appointment in Dime.Scheduler. Each row captures what was seen in Exchange, so you can decide whether the gap is expected, a sync timing artefact, or a real divergence that needs to be reconciled by hand.
Open it from the Exchange section of the navigation, or visit `https://app.dimescheduler.com/resourceeventdeltas`.
## Layout
The page is split into two panes:
- **Left** - a list of resources that have one or more recorded deltas. Pick a resource to load its delta history. The splitter between the two panes can be dragged to widen either side.
- **Right** - the delta grid for the selected resource. Empty until a resource is selected.
## Columns
| Column | Description |
| ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Start | Start date and time of the Exchange event. |
| End | End date and time of the Exchange event. |
| Delta | Raw description of what the connector observed, rendered in monospace so that timestamps and identifiers line up cleanly. |
All columns are filterable - text on **Delta**, dates on **Start** and **End**.
## Actions
Each row has a delete button on the right. Removing a row clears that specific delta from the history; it has no effect on the underlying Exchange calendar.
## When deltas appear
A delta is recorded when the [bidirectional sync](/integrations/exchange/bidirectional) encounters a calendar event that does not correspond to a known Dime.Scheduler appointment for that resource. Common causes:
- The resource (or someone on their behalf) created an appointment directly in Outlook.
- An appointment was deleted in Dime.Scheduler while the matching Exchange event lingered.
- A sync ran during an in-flight change and observed a transient state.
Recurring or expected deltas - for example a personal calendar block that the resource keeps in their mailbox - can be left alone or removed in bulk from this page.
## Related
- [Two-way sync](/integrations/exchange/bidirectional) - how Dime.Scheduler and Exchange exchange calendar data.
- [Exchange logs](/integrations/exchange/logs) - the raw connector log stream.
- [Transaction Errors](/integrations/exchange/transaction-errors) - diagnosed appointment failures with `DSEX` codes.
---
## Dime.Scheduler to Exchange
This page explains how appointments planned in Dime.Scheduler reach Outlook calendars, and the rules that decide which ones get there.

## How it works
The flow from Dime.Scheduler to Exchange works almost exactly like the Microsoft Dynamics back office connectors. Every change to an appointment on the planning board triggers a workflow that notifies the connected systems of a pending change to process. For the Exchange connector, the result of that workflow is a calendar event for each resource assigned to the appointment. Resources then see their up-to-date planning in their own email and calendar clients, such as Microsoft Outlook.
## Business rules
- Appointments are sent to Exchange for resources where the ['Exchange Integration Enabled'](administration/settings/resources.md#managing-resource-master-data) flag is set to true.
- Those resources must have a valid email address and be reachable through the Microsoft Entra ID app.
- You can define additional rules with the built-in [rule engine](integrations/exchange/ruleengine.md).
## Configuration
The connector listens for events in Dime.Scheduler and pushes them to Exchange. To enable that, supply these parameters:
- The client id of the Microsoft Entra ID application
- The client secret of the Microsoft Entra ID application
- The tenant id of the Microsoft Entra ID application
Configure all of them in the Exchange config section in Dime.Scheduler. For the steps to set up the Microsoft Entra ID application, see the [configuration guide](integrations/exchange/configuration.md).
---
## Exchange to Dime.Scheduler
This page covers the message flow that runs from Exchange to Dime.Scheduler, where calendar events made in Outlook flow back into the planning board.

## How it works
The mechanics behind this direction are technically involved, but the outcome is straightforward: any change to any event of any resource tracked by Dime.Scheduler Exchange can update the schedule on the planning board.
That behavior is configurable, which gives the planner full control and keeps resources from interfering with the plan. In principle, resources could alter appointments managed in Dime.Scheduler, but not every planner wants that kind of collaboration, and not every resource wants to share their entire calendar with others. The settings below let you decide how much each side can do.
## Configuration
A dedicated section controls the 'Exchange to Dime.Scheduler' message flow:
Appointments are sent to Dime.Scheduler for the resources that meet the configured criteria, set by either the administrator or the resources themselves:
- Enabled
- Show as status (multiple choices)
- Free
- Tentative
- Office
- Out of office
- Sensitivity (multiple choices)
- Normal
- Personal
- Private
- Confidential
## Connector parameters
The connector listens for events in Exchange and pushes them to Dime.Scheduler. To do that, it needs a few parameters. As with the Dime.Scheduler to Exchange message flow, you supply details about the Azure App Registration:
- The client id of the Microsoft Entra ID application
- The client secret of the Microsoft Entra ID application
- The tenant id
Set all of these in the Exchange config section in Dime.Scheduler. For instructions on setting up the Microsoft Entra ID application, see the [Exchange configuration guide](integrations/exchange/configuration.md).
---
## Installation
Setting up the Exchange connector comes down to three steps: register a Microsoft Entra
ID app, copy its values into Dime.Scheduler's Exchange setup, and decide which
appointments may flow to Exchange.
import DocCardList from '@theme/DocCardList';
---
## Exchange Connector
The Exchange connector keeps the appointments in your resources' Outlook calendars in
sync with their planning in Dime.Scheduler. It runs on Microsoft Graph, the developer
platform behind Microsoft 365, and syncs in real time, in both directions.
## Two-way sync
Appointments a planner schedules in Dime.Scheduler appear in the resources' Outlook
calendars, and changes those resources make in Outlook flow back into the planning.
That two-way flow is what makes the connector **bidirectional**.

The connector is the integration component in the middle. One side listens for changes
on the planning board and pushes them into Outlook; the other side is notified by
Microsoft when a calendar changes and adjusts the planning to match.
## How it fits together
The connector ties Dime.Scheduler and Exchange together through the Graph API, driven by
the settings administrators configure in Dime.Scheduler and in the Exchange connector.
## Microsoft Graph and app registration
The connector does its work through Microsoft Graph. That's why setup starts with
[registering a Microsoft Entra ID app](/integrations/exchange/azureapp): it's how your
client decides exactly which permissions an external application is granted.
## Each direction is optional
You can opt in to each flow independently:
- Don't need the planning traced into Outlook? Switch that flow off in the Exchange
administration.
- Don't want resource calendars affecting the planning board? Leave the bidirectional
flow off (the default) on the [Exchange setup page](https://app.dimescheduler.com/exchangesetup).
## Read more
- [Microsoft Graph](https://docs.microsoft.com/en-us/graph/)
- [Microsoft Entra ID App Registration](/integrations/exchange/azureapp)
---
## Exchange logs
The **Exchange logs** page shows the raw log entries the Exchange connector emits while it processes calendar events, pushes appointments, and reacts to Graph notifications. Use it to trace what the connector actually did when a sync misbehaves. It complements the [Errors page](/administration/transactions/errors): the Errors page shows diagnosed appointment failures with stable `DSEX` codes, while the Logs page shows the underlying log stream those diagnoses are drawn from.
Open it from the Exchange section of the navigation, or visit `https://app.dimescheduler.com/exchangelogs`.
## Columns
| Column | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
| Date | Timestamp the entry was written. |
| Level | Severity badge. `ERROR`, `FATAL`, `WARN`, `INFO`, `DEBUG`, `TRACE`. |
| Category | The logical area that emitted the entry (sync direction, rule engine, Graph client, ...). |
| User | The user or service principal the operation ran under, when known. |
| Message | The log message. Hover the cell to see the full text without truncation. |
| Exception | The exception stack trace, when one was captured. Hidden by default - enable from the grid's column chooser. |
## Filtering and sorting
Every column is sortable; most are filterable.
- **Level** offers a combo filter with the standard severities.
- **Category**, **User**, **Message**, and **Exception** offer free-text filters.
- **Date** supports a date filter.
Combine filters to narrow down to a single incident. For example, set `Level = ERROR` and `Category` containing `Push` to isolate failures while pushing appointments to Exchange.
The grid is paged. Use the pager controls at the bottom to step through history.
## Export
The **Export** button in the toolbar saves the currently filtered view to an Excel (`.xlsx`) file. This is handy for attaching to a support ticket or sharing with a colleague who does not have access to the tenant.
## Related
- [Transaction Errors](/integrations/exchange/transaction-errors) - diagnosed appointment failures with `DSEX` codes.
- [Troubleshooting](/integrations/exchange/troubleshooting) - common Exchange integration issues.
- [Resource event deltas](/integrations/exchange/deltas) - calendar events from Exchange that did not match an existing appointment.
---
## Accessing the Exchange Connector as a resource
Resources manage their own Exchange synchronization preferences. When a resource is added to the Dime.Scheduler Exchange connector - by the administrator or automatically by the scheduled resource synchronization job - the system creates a user account for that resource. The connector has two user account types: administrators and resources. Each resource is assigned a resource user account.
A resource user account can only access and modify its own synchronization preferences:

Resources can manage whether they will allow events in their calendar to synchronize back to Dime.Scheduler and if so, can specify which events based on the following metadata:
- **Busy status**, also known as the 'Show as' field in Outlook
- **Sensitivity**, also known as the 'Private' toggle button
Note that administrators can always override these settings when necessary from the resources page of the connector.
---
## Rule Engine
import { FaCalendar } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "Rules", link: "https://app.dimescheduler.com/exchangerules"}]} />
## About the rule engine
By default, every appointment assigned to a resource that syncs with Exchange ends up in that person's Outlook calendar. Planners usually want finer control over which appointments reach the calendars. Temporary appointments or placeholders, for example, add no value there and should stay out.
The rule engine in the Exchange connector, introduced in Dime.Scheduler 2022.2.0, lets you define those filtering rules and change them without recompiling the system. The first iteration of the rule engine is configured in the "Rules" section of the application settings file for the service and the web app (appsettings.json).
## Example
The simplified example below shows how this works. Two conditions must both pass before an appointment is sent to Exchange:
- The category name (the background of the appointment in the planning board) must be “SERVICE” and a certain field on the task needs to be set to true. In this case, a free boolean field is used to indicate whether to synchronize the record to Exchange.
- The time marker (the line below the subject of the appointment in the planning board) name must be “IN PROCESS” and a certain field on the task needs to be set to true. In this case, a free boolean field is used to indicate whether to synchronize the record to Exchange.
Suppose the planner schedules the four appointments below. Edit any of them - change the category, the time marker, or the sync flag - and watch which ones the rules let through to Outlook:
Out of the box, only appointment #1 is sent to Exchange, because it is the only one that meets both requirements: the category name is “SERVICE”, the time marker name is “IN PROCESS”, and the free boolean field is “TRUE”.
This is a deliberately simple example. You define what each rule must check. The rule engine understands a dedicated syntax for building powerful queries, and when requirements change, you update the rules and they take effect immediately. The syntax also gives you access to the entire data graph, so you can filter on data from the appointment, the planning lines, the service orders, customer data, and more.
## Visual workflow builder
The Exchange rules page also includes a **visual workflow builder** that lets you control which appointments synchronize with Exchange, without writing rule expressions by hand.
- **Drag-and-drop interface**: build sync rules visually instead of editing JSON.
- **Conditional logic**: define conditions and branches to control exactly which appointments get synced.
- **Real-time preview**: see how your rules will affect synchronization before applying them.
The visual builder produces the same underlying rule definitions described below, so you can switch between the two views as needed.
## Constructing rules
As the example above shows, one **rule** can hold **multiple conditions**. You decide how to split the filter, but breaking a rule into several expressions makes problems easier to diagnose. Because each condition has a name, you can quickly pinpoint why a given appointment wasn't synchronized to (and from) Exchange.
For each condition, define a unique name, a descriptive message for when the condition isn't met, and the rule itself. The rule uses a syntax for writing expressions that the engine parses into expression trees. In other words, it is a predicate that the application parses and executes at runtime every time an appointment is to be synchronized. An expression has three components.
### The appointment
The expression reads like the body of a filter function found in many programming languages. The `appointment` field is where it all starts: it represents the appointment, and through it you can access the entire appointment graph. Common properties include:
- Category.Name
- TimeMarker.Name
- StartDate
- EndDate
- Subject
- Body
- Task.Taskno
- Task.Job.JobNo
### Operator
Once you have the property you want, compare its value to another value so the expression can decide whether to pass the appointment on to Exchange. Supported operations include:
- `==` or `eq` or `equal`: Equals
- `!=` or `<>` or `ne` or `neq` or `notequal`: Does not equal
- `<` or `lt` or `<=` or `lte`: Less than (or equal)
- `>` or `gt` or `>=` or `gte`: Greater than (or equal)
- `x && y` or `x and y` : Logical AND
- `x || y` or `x or y`: Logical OR
### Reference value
Finally, compare the appointment to a reference value using the operator. This value is usually static, as in the examples, but you can also compare it to another value within the same `appointment` graph.
### Null reference checks
Some navigation properties in the graph might be missing (for example, appointments without a time marker, category, or linked task). To prevent null reference errors while the rule is applied, include the additional checks shown in the examples below.
### Additional examples
Each rule is a named expression. A few common ones:
- **Do not sync appointments marked "TO BE CONFIRMED"** (appointments without a time marker can still be sent): `appointment.TimeMarker == NULL OR appointment.TimeMarker.Name <> "TO BE CONFIRMED"`
- **Only sync appointments linked to a task:** `appointment.Task <> NULL`
- **Sync everything except ABSENCES that are not confirmed yet:** `appointment.Task == NULL OR (appointment.Task.Job <> NULL AND appointment.Task.Job.JobNo == "ABSENCES" AND (appointment.TimeMarker == NULL OR appointment.TimeMarker.Name <> "TO BE CONFIRMED"))`
---
## Transaction Errors(Exchange)
Use this page to look up any Exchange sync failure by its error code and fix it. When an appointment sync to Exchange fails, the connector diagnoses the failure and records it on the transaction row visible on the [Errors page](/administration/transactions/errors). Every diagnosed failure carries a stable `DSEX` code, prefixed on the `Transaction.Message` in square brackets (for example `[DSEX002] The client secret … is invalid …`). A code never changes once issued, and only new ones get added, so it is a reliable lookup key.
For Azure AD app-registration and consent walkthroughs, see [Troubleshooting](/integrations/exchange/troubleshooting). This page covers the runtime classifications.
**Retried** statuses are transient and self-heal on the next retry pass. **Fix configuration** statuses need a change to the Exchange connector or Azure app first; once you correct the setting, the retry job replays the affected appointments automatically on its next pass (within a 14-day window), so no manual replay is needed. Send **Undiagnosed** failures (`DSUNK001`) to support so a rule can be added.
Each section pairs 💡 (what it means) with 🛠️ (what to do).
## Quick lookup
| Code | Goes to | Status | Retry |
|---|---|---|---|
| `DSEX001` | [Expired client secret](#expired-client-secret) | `InvalidBackOfficeSettings` | fix config |
| `DSEX002` | [Invalid client secret](#invalid-client-secret) | `InvalidBackOfficeSettings` | fix config |
| `DSEX003` | [Unknown client ID](#unknown-client-id) | `InvalidBackOfficeSettings` | fix config |
| `DSEX004` | [Unknown or malformed tenant ID](#unknown-or-malformed-tenant-id) | `InvalidBackOfficeSettings` | fix config |
| `DSEX005` | [Other rejected credentials](#other-rejected-credentials) | `InvalidBackOfficeSettings` | fix config |
| `DSEX006` | [Incomplete credentials](#incomplete-credentials) | `InvalidBackOfficeSettings` | fix config |
| `DSEX010` | [Credentials rejected after the call](#credentials-rejected-after-the-call) | `Skipped` | fix config |
| `DSEX011` | [Exchange integration disabled](#exchange-integration-disabled-for-the-assigned-resources) | `Skipped` | fix config |
| `DSEX012` | [Invalid user in the directory](#invalid-user-in-the-directory) | `Skipped` | fix config |
| `DSEX020` | [No mapping entry](#no-mapping-entry) | `EmptyBackOffice` | retried |
| `DSEX021` | [Event already deleted](#event-already-deleted) | `Skipped` | n/a |
| `DSEX030` | [Access denied by the Azure AD app](#access-denied-by-the-azure-ad-app) | `Failed` | retried |
| `DSEX040` | [Microsoft Graph transient](#microsoft-graph-transient) | `Failed` | retried |
| `DSUNK001` | [Unexpected exception](#unexpected-exception) | `Failed` | undiagnosed |
All five credential-rejection cases below surface as the same typed failure (`InvalidExchangeCredentialsException`) on `InvalidBackOfficeSettings`; only the underlying `AADSTS` code and the field to fix differ. Once you correct the offending value, the appointment recovers automatically on the next retry pass.
## Expired client secret
The client secret of the Exchange App Registration has expired. Microsoft Entra ID returns `AADSTS7000222` and the connector records a rejected credential carrying the offending Client ID.
1. Open the Azure portal and navigate to the Exchange app registration.
2. Under **Certificates & secrets**, generate a new client secret. Copy the secret **value** immediately - it is only shown once.
3. Update the secret on the [Exchange configuration page](/integrations/exchange/configuration) under the Microsoft Entra ID section.
:::warning
Client secrets expire after 6, 12, or 24 months. Set a reminder to rotate them before they expire.
:::
## Invalid client secret
The client secret value is wrong - most often the secret **ID** was pasted instead of the secret **Value**. Microsoft Entra ID returns `AADSTS7000215` (`invalid_client`).On the app registration's **Certificates & secrets** page, generate a fresh secret if you no longer have the value, then copy the **Value** column (not the Secret ID) into the [Exchange configuration page](/integrations/exchange/configuration).
## Unknown client ID
The Client ID is not an application in the connected directory - commonly the Tenant ID or a secret was pasted into the Client ID field, or the app registration was deleted. Microsoft Entra ID returns `AADSTS700016`.Copy the **Application (client) ID** from the app registration's **Overview** page into the Client ID field on the [Exchange configuration page](/integrations/exchange/configuration).
## Unknown or malformed tenant ID
The Tenant ID does not match a known directory, or is not a valid identifier at all (rejected before any call goes out). Microsoft Entra ID returns `AADSTS90002` / `AADSTS900023`, or the value fails validation locally.Copy the **Directory (tenant) ID** from the app registration's **Overview** page into the Tenant ID field on the [Exchange configuration page](/integrations/exchange/configuration).
## Other rejected credentials
Microsoft Entra ID rejected the app registration for a reason the connector does not classify into one of the cases above. Any authentication failure that is not a transient throttle is treated as a configuration problem rather than retried blindly.Re-check all three values - Client ID, Tenant ID, Client Secret - on the [Exchange configuration page](/integrations/exchange/configuration) against the app registration's **Overview** and **Certificates & secrets** pages. If they are all correct, send the transaction message to support.
## Incomplete credentials
One or more of the required values - Client ID, Client Secret, Tenant ID - is missing on the Exchange configuration. The connector refuses to call Graph without all three.Fill in every required field on the [Exchange configuration page](/integrations/exchange/configuration) and save.
## Credentials rejected after the call
Graph accepted the request shape but rejected the token at call time. Usually a permission issue rather than a missing value - distinct from the configuration failures above, which fire before any call goes out.In the Azure AD app, confirm that the application permissions (`Calendars.ReadWrite`, `MailboxSettings.ReadWrite`, `User.Read.All`) are present and that **Grant admin consent** has been clicked.
## Exchange integration disabled for the assigned resources
None of the resources on the appointment have Exchange enabled, so there is no mailbox to write to. The message lists the affected resources (`Resource (No)` pairs), or "No resources were assigned to this appointment" when the appointment has no assignments at all.If the resource is meant to sync to Exchange, enable Exchange integration on the resource record. If not, the `Skipped` status is correct and no action is needed.
## Invalid user in the directory
The resource's e-mail address does not match any user in the Microsoft Entra ID directory of the connected tenant. Graph returns no match for that UPN.Verify that the resource's e-mail address corresponds to a real, licensed mailbox user in the tenant. Typos and disabled accounts are the usual causes.
## No mapping entry
The connector tried to look up the Exchange event that previously corresponded to a Dime.Scheduler appointment and resource pair, and no mapping record was found. The originating Exchange event was already deleted, or the mapping table was reset without re-syncing.In most cases this self-heals on the next sync because the next event creation re-establishes the mapping. If the appointment keeps failing, force a full re-sync of that resource's calendar.
## Event already deleted
On a delete, the Exchange event the appointment pointed at no longer exists - it was already removed (in Exchange directly, or by an earlier sync). There is nothing left to delete.None. This is benign and recorded as `Skipped`; the desired end state (no event) is already true.
## Access denied by the Azure AD app
The Azure AD app does not have sufficient permissions to write events to the target calendar. The connector records the underlying Graph message after the canned `"The Azure AD app does not have sufficient permissions to write events to the calendar."` prefix.
1. In the Azure portal, open the Exchange app registration and go to **API permissions**.
2. Make sure `Calendars.ReadWrite` is granted as an **Application permission** (not Delegated).
3. Click **Grant admin consent for [Your Organization]**.
4. Replay the failed transaction from the [Errors page](/administration/transactions/errors).
## Microsoft Graph transient
Graph or the transport layer returned a transient signal - HTTP 429, 503, 504, socket / DNS hiccups, or request timeouts. The message reads `Transient error from Microsoft Graph: . Will retry on the next sync attempt.`.Nothing in the normal case. The transaction is left on `Failed` so the retry job picks it up on the next pass. If the rate of transients spikes, check the Microsoft 365 service health dashboard and tenant-level Graph throttling.
## Unexpected exception
The exception did not match any of the typed shapes above. The connector still records it as `Failed` so the retry job can replay it, but it also writes a full stack trace to the operational log so the maintainers can add a typed rule.Send the transaction ID and full error message to Dime.Scheduler support. If the same shape recurs, a new typed classification will be added in a subsequent release.
---
## Troubleshooting(Exchange)
import AreaTroubleshootingLanding from '@site/src/components/AreaTroubleshootingLanding';
Common Exchange integration problems, starting with the one you'll hit most:
authentication.
## Authentication failures {#azure-ad-app-configuration-issues}
**Symptoms:**
- "Invalid client" or "Invalid client secret" errors
- "AADSTS700016: Application not found in the directory"
- "Insufficient privileges to complete the operation"
- Authentication fails even though the credentials look correct
**Cause:** an incorrect Microsoft Entra ID app registration: wrong Client ID, Client
Secret, or Tenant ID, or missing API permissions.
Work through the three checks below.
### 1. Verify Client ID, Client Secret, and Tenant ID
These are the most frequently reported issues:
1. **Client ID (Application ID):**
- Go to [Azure Portal](https://portal.azure.com) > **Microsoft Entra ID** > **App registrations**.
- Find your Dime.Scheduler app and copy the **Application (client) ID**.
- Make sure it matches exactly what's in the Dime.Scheduler connector settings.
2. **Tenant ID:**
- On the same app overview page, copy the **Directory (tenant) ID**.
- Make sure it matches Dime.Scheduler. A common mistake is using the wrong tenant ID
when you have several.
3. **Client Secret:**
- Go to **Certificates & secrets**.
- Check whether the secret has expired (secrets last 6, 12, or 24 months).
- If it has, or you're unsure, create a new one.
- Copy the secret **value** (not the Secret ID) immediately. You only see it once.
- Update it in the Dime.Scheduler connector configuration.
:::warning
Client secrets expire. Set a reminder to rotate them before they do, to avoid service
interruptions.
:::
### 2. Verify API permissions {#step-2-verify-api-permissions}
The app must have the right permissions:
1. Open your app > **API permissions**.
2. Make sure these **Microsoft Graph** application permissions are present:
- `Calendars.ReadWrite` - calendar sync
- `MailboxSettings.ReadWrite` - mailbox configuration
- `User.Read.All` - reading user information
3. Click **Grant admin consent for [Your Organization]**.
:::danger[Admin consent is required]
Adding the permissions is not enough. You **must** grant admin consent afterwards, or
they won't take effect. This step is the one most often forgotten.
:::
### 3. Update the Dime.Scheduler configuration
1. Open the [Exchange setup page](/integrations/exchange/configuration).
2. In the Microsoft Entra ID section, enter:
- **Client ID**: the Application (client) ID
- **Client Secret**: the secret value (not the secret ID)
- **Tenant ID**: the Directory (tenant) ID
3. Save to apply the changes.
---
## The Logic App connector
Connect Dime.Scheduler to Azure Logic Apps to automate scheduling workflows. The Logic Apps connector is the same connector as the Power Platform connector. Dime Software maintains it as a certified connector, which Microsoft reviews and approves before publishing.
Because both share the same connector, the [Power Platform connector documentation](/integrations/powerplatform) covers how to set it up and use it.

---
## Connecting MCP clients
This page shows how to register the Dime.Scheduler MCP server in the most common clients. They all need the same three things - the [endpoint URL, transport, and `X-API-KEY` header](./configuration.md) - but the configuration format differs.
Replace `YOUR_API_KEY` with a real [API key](./prerequisites.md#an-api-key) and pick the URL that matches your environment.
## MCP Inspector
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the fastest way to verify a connection and explore the tool catalog. We recommend it for first-time setup.
```bash
npx @modelcontextprotocol/inspector
```
In the UI:
1. **Transport type:** `Streamable HTTP`
2. **URL:** `https://sandbox.api.dimescheduler.com/mcp`
3. **Authentication:** _Custom headers_
- **Header name:** `X-API-KEY`
- **Header value:** your API key
4. Click **Connect**, then **List Tools** - you should see the full catalog (tasks, resources, appointments, and more).
From here you can invoke tools by hand, inspect their JSON schemas, and confirm everything works before wiring up a real assistant.
## Claude Desktop
Claude Desktop reads its MCP configuration from `claude_desktop_config.json`:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
Recent versions support remote MCP servers natively:
```json
{
"mcpServers": {
"dimescheduler": {
"url": "https://sandbox.api.dimescheduler.com/mcp",
"headers": {
"X-API-KEY": "YOUR_API_KEY"
}
}
}
}
```
If your version of Claude Desktop only supports `stdio`-based servers, use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge instead:
```json
{
"mcpServers": {
"dimescheduler": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://sandbox.api.dimescheduler.com/mcp",
"--header",
"X-API-KEY:YOUR_API_KEY"
]
}
}
}
```
Restart Claude Desktop and look for the tools icon in the input box - you should see the `dimescheduler` server listed.
## Claude Code
Add the server with the `claude mcp` command:
```bash
claude mcp add --transport http dimescheduler \
https://sandbox.api.dimescheduler.com/mcp \
--header "X-API-KEY: YOUR_API_KEY"
```
Run `/mcp` inside Claude Code to verify the server is connected and to inspect available tools.
## Cursor
Cursor stores MCP servers in `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` in a workspace. The format mirrors Claude Desktop:
```json
{
"mcpServers": {
"dimescheduler": {
"url": "https://sandbox.api.dimescheduler.com/mcp",
"headers": {
"X-API-KEY": "YOUR_API_KEY"
}
}
}
}
```
Reload the Cursor settings and confirm the server appears under **Settings → MCP**.
## Custom clients
If you are building your own client with the [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk):
```ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://sandbox.api.dimescheduler.com/mcp"),
{
requestInit: {
headers: { "X-API-KEY": process.env.DIME_API_KEY! },
},
}
);
const client = new Client({ name: "my-app", version: "1.0.0" });
await client.connect(transport);
const { tools } = await client.listTools();
console.log(tools.map((t) => t.name));
```
The [Python SDK](https://github.com/modelcontextprotocol/python-sdk) follows the same pattern: instantiate a Streamable HTTP client with the `X-API-KEY` header, connect, and call `list_tools()` / `call_tool()`.
## Verifying the connection
Whichever client you choose, run a quick smoke test once it's connected:
> _"List all categories."_
That should invoke [`list_categories`](./intro.md#resources) and return the categories defined in your tenant. If the assistant reports an authentication error, double-check the `X-API-KEY` header. If it cannot find any tools, confirm the URL ends in `/mcp` and that the transport is set to Streamable HTTP.
---
## Configuration(Mcp)
To connect an MCP client to Dime.Scheduler, you need three things: the **endpoint URL**, the **transport** to use, and the **authentication header**. This page gives you all three.
## Endpoints
The MCP server is mounted at `/mcp` on the same hosts as the Dime.Scheduler public API.
| Environment | URL | Status |
| -------------- | ------------------------------------------------ | ------------------- |
| Sandbox | `https://sandbox.api.dimescheduler.com/mcp` | Available |
| Production | `https://api.dimescheduler.com/mcp` | Available |
:::tip[Pick sandbox for experiments]
Use the **sandbox** environment for first contact and for any client that an end-user will configure themselves. It carries no production planning data.
:::
## Transport
The Dime.Scheduler MCP server speaks the **Streamable HTTP** transport defined by the MCP specification. This is the default in every modern MCP client. There is no `stdio` binary to install - the server is reached over plain HTTPS.
If your client only supports `stdio` (some older versions of Claude Desktop, for example), use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge:
```json
{
"mcpServers": {
"dimescheduler": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://sandbox.api.dimescheduler.com/mcp",
"--header",
"X-API-KEY:${DIME_API_KEY}"
]
}
}
}
```
See [Clients](./clients.md) for full examples per product.
## Authentication
Authentication is a single custom header on every request:
| Header | Value |
| -------------- | -------------------------------------- |
| `X-API-KEY` | A valid Dime.Scheduler API key |
The server rejects requests without a valid key with HTTP `401 Unauthorized`. Keys carry the same scope as the user that created them, so the MCP client can only see and modify data that user is allowed to see.
:::warning[Treat the API key like a password]
The key gives full programmatic access to the tenant it was created in. Don't paste it into chat windows, screenshots, or shared configuration files. Use your client's encrypted secret store wherever possible.
:::
## Tool discovery
Once connected, the client should call the standard MCP `tools/list` method to discover the available tools and their JSON schemas. There is no separate manifest URL - the server returns the full tool catalog over the live MCP session.
You can inspect the catalog interactively with the [MCP Inspector](./clients.md#mcp-inspector) before wiring it up to a real assistant.
## Time zones
Many tools take a `timeZone` parameter as an [IANA identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), such as `Europe/Brussels` or `America/New_York`. Always pass the time zone of the **end user**, not the server. The assistant should infer it from context or ask if it isn't sure. The [`parse_relative_time`](./tools/reference.md#parse-relative-time) tool exists specifically to convert phrases like _"next week"_ into concrete UTC ranges.
---
## Example prompts
Once the Dime.Scheduler MCP server is connected to your assistant, here are prompts you can try. They are written the way an end user would phrase them - a dispatcher, a planner, or a field service manager - not a developer.
Each example notes the tools the assistant is likely to call behind the scenes, so you can spot mistakes when a result feels off.
## Dispatcher's daily check
> _"What are the open tasks today, and who is available to do them this afternoon?"_
The assistant typically:
1. Calls [`get_open_tasks`](./intro.md#resources) for the unplanned work.
2. Calls [`get_recommendations`](./tools/resources.md#get-recommendations) for each task, passing the task's skills and a duration.
3. Summarizes the matches in a list.
## Scheduling a known job for a known resource
> _"Schedule task TASK-001 for John Smith tomorrow at 2 PM for 2 hours."_
The assistant has everything it needs to call [`create_appointment`](./tools/appointments.md#create-appointment) directly:
- `taskNo: TASK-001`
- `resourceDisplayNames: John Smith`
- `startDateTime` / `endDateTime` derived from "tomorrow at 2 PM" + 2 hours
- `timeZone` from the user's profile
It may first call [`parse_relative_time`](./tools/reference.md#parse-relative-time) to resolve "tomorrow".
## Finding a free slot
> _"When is John Smith available for a one-hour meeting this week?"_
A simple [`find_available_slots`](./tools/resources.md#find-available-slots) call:
- `resourceDisplayName: John Smith`
- `durationInMinutes: 60`
- `startDate` / `endDate` derived from "this week"
## Discovering the right resource
> _"Who is available next Wednesday morning for a 90-minute electrical job in Antwerp?"_
This is a discovery question, so the assistant picks [`get_recommendations`](./tools/resources.md#get-recommendations) with:
- `requirements: electrical work`
- `duration: PT1H30M`
- `locationAddress: Antwerp`
- `locationCountry: BE`
- `startDate` / `endDate` for next Wednesday morning
The result is a ranked list of candidates, each with proposed slots.
## Moving an appointment
> _"Move tomorrow's installation for Jane Smith to Friday at 9 AM."_
The assistant calls [`reschedule_appointment`](./tools/appointments.md#reschedule-appointment) with:
- `subject: installation` (or `taskNo` if it can find one)
- `resourceDisplayName: Jane Smith`
- `currentDay: tomorrow`
- `newDay: Friday`, `newTime: 09:00`
If the assistant accidentally reaches for `get_recommendations` here, that's a sign the prompt or the system message needs to make the discovery-vs-reassignment distinction clearer.
## Inspecting the planning
> _"What's on Jane Smith's calendar next week?"_
A direct call to [`get_resource_planning`](./tools/appointments.md#get-resource-planning) with the resolved date range.
## Bulk look-up
> _"Show me all 'site visit' appointments next month, grouped by resource."_
The assistant calls [`search_appointments`](./tools/appointments.md#search-appointments) with `searchTerm: site visit` and groups the results client-side. Increase `limit` if the time range is wide.
## Validating a single time
> _"Is Jane Smith free on March 12th from 10 to 11 AM?"_
A single [`check_time_slot_availability`](./tools/resources.md#check-time-slot-availability) call. The response will include any conflicting periods if she isn't available.
## Cancellation
> _"Cancel the 'Customer onsite' appointment for John Smith on Friday."_
[`delete_appointment`](./tools/appointments.md#delete-appointment) with `subject`, `resourceDisplayName`, and a resolved `startDateTime` for Friday.
## Tips for better results
- **Be specific about resources.** Display names are the unique identifier - _"John"_ might be ambiguous, _"John Smith"_ usually isn't.
- **Be explicit about time zones** if you work across regions, especially when scheduling for a customer in a different zone than the resource.
- **Mention skills, not job titles.** _"Plumbing"_ and _"electrical work"_ resolve cleanly; _"the plumbing guy"_ may not.
- **Ask the assistant to confirm before destructive actions.** A short system instruction like _"Always confirm before creating, updating, rescheduling, or deleting"_ prevents accidental changes.
---
## Model Context Protocol (MCP)
:::info[This is the application MCP server]
This page is about the MCP server for the **Dime.Scheduler application** - it lets an AI agent work with your planning data (tasks, appointments, resources). It is **not** the MCP server for the documentation site. To let an agent search and read these docs, see the [documentation MCP server](/develop/mcp) instead.
:::
Connect Dime.Scheduler to any MCP-compatible AI assistant and let your customers plan from a chat window. The [Model Context Protocol](https://modelcontextprotocol.io) (MCP) is an open standard that lets AI assistants reach external systems through a uniform JSON-RPC interface. The Dime.Scheduler MCP server exposes the most common scheduling operations - listing tasks, finding available resources, creating and rescheduling appointments - as MCP **tools** that any compliant client can call.
## What you can do with it
With an MCP-capable assistant connected to Dime.Scheduler, you can:
- **Ask in plain language** - _"Who is available next Tuesday afternoon for a 2-hour electrical job in Antwerp?"_
- **Schedule and reschedule appointments** - create, move, reassign, or cancel work orders directly from chat.
- **Inspect the planning** - pull a resource's calendar for the week, search appointments, or look up open tasks.
- **Discover the right resource** - match jobs to skills, location, and availability through the recommendation tool.
- **Drive automations** - embed Dime.Scheduler tools in larger AI workflows alongside calendars, CRMs, and ticketing systems.
## How it fits in the integration landscape
| Connector | Audience | Style |
| -------------------- | ----------------------------------- | --------------------------- |
| Business Central / NAV | Back-office sync | Bidirectional, transactional |
| Exchange | Calendar sync | Bidirectional, scheduled |
| Webhooks | Custom downstream systems | Outbound, event-driven |
| Zapier / Logic Apps | No-code automation | Bidirectional, workflow |
| **MCP** | **AI assistants and agents** | **On-demand, conversational** |
MCP complements the existing connectors: it does not replace your back-office sync, but it unlocks a new conversational entry point on top of the same data.
## Architecture
```text
AI assistant (Claude, Cursor, …) ──► MCP client
│ JSON-RPC over Streamable HTTP
▼
https://.api.dimescheduler.com/mcp
│
▼
Dime.Scheduler API
```
Every request is authenticated with the same API keys you already use for the public API. The MCP server is stateless - it translates tool calls into Dime.Scheduler API calls and streams the results back to the assistant.
## What's exposed
The MCP server exposes three kinds of capabilities. Most of the work happens through **tools**, but **resources** and **prompts** complement them in important ways.
- **Tools** - actions the agent can call (search, create, reschedule, optimize, ...).
- **Resources** - read-only data sources the agent can pull as standing context without consuming a tool call.
- **Prompts** - pre-built workflows the agent can run end-to-end.
### Tools
The server exposes tools across the following areas. Each is documented on its own page:
- **[Tasks & Jobs](./tools/tasks.md)** - list open tasks, search tasks and jobs.
- **[Resources](./tools/resources.md)** - search resources, inspect skills and capacity, find available slots, get recommendations.
- **[Appointments](./tools/appointments.md)** - search, inspect, create, update, reschedule, and delete appointments.
- **[Optimization](./tools/optimization.md)** - generate optimal route and assignment plans.
- **[Messaging](./tools/messaging.md)** - send real-time messages and inspect notifications.
- **[Reference data & utilities](./tools/reference.md)** - categories, time markers, relative-time parsing.
### Resources
Resources are addressable via `dimescheduler://...` URIs. Reading a resource returns up-to-date data without consuming a tool call, which is useful for keeping standing context (e.g. the current notification stream, the resource roster, the list of categories) cheap.
| URI | Purpose |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dimescheduler://today/open-tasks` | The current set of unplanned (open) tasks. Use this instead of a dedicated tool call for the queue snapshot. |
| `dimescheduler://resources` | The complete resource roster with basic info. Use this when you want the full list - for filtered lookups, prefer [`search_resources`](./tools/resources.md#search-resources). |
| `dimescheduler://resource/{resourceDisplayName}/location` | Most recent GPS ping for a specific resource. Templated: substitute `{resourceDisplayName}` with the resource's `DisplayName` from the `dimescheduler://resources` resource. Returns null when no GPS exists. |
| `dimescheduler://recommendation/scorers` | The scoring strategies available to [`get_recommendations`](./tools/resources.md#get-recommendations). |
| `dimescheduler://categories` | All appointment categories. |
| `dimescheduler://time-markers` | All time markers. |
| `dimescheduler://notifications/recent` | The most recent 50 notifications for the tenant - newest first, including severity, code, message, related entity ids and timestamp. Use as standing context to surface schedule changes, errors, and import warnings. |
### Prompts
Prompts are pre-built workflows. Each one bundles a sequence of tool calls and resource reads into a guided procedure that the agent walks through end-to-end. They all share one rule: **propose, don't act** - the agent stops to confirm with the dispatcher before making destructive changes.
| Prompt | Purpose |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `optimize-and-explain` | Run field-service optimization for one or more resources over a date range and explain the trade-offs (BEFORE vs AFTER, travel time delta, distance delta, reassignments). Wraps [`optimize_field_service`](./tools/optimization.md#optimize-field-service). |
| `cancel-and-notify` | Cancel one resource's appointments for a given date and notify them. Composes `search_appointments` + `bulk_delete_appointments` + `send_message`, with explicit confirmation before any deletion. |
| `find-conflicts` | Inspect a date's schedule for double-bookings, impossible travel times, and unmarked travel gaps. Reads `dimescheduler://resources` and calls `get_resource_planning` per resource. |
| `audit-resource-day` | Inspect one resource's full day - planned appointments, gaps, conflicts, and GPS movement. Useful for end-of-day review or _"what did Sarah actually do today?"_ questions. |
| `balance-workload` | Assess workload distribution across resources for a date range and propose rebalancing for outliers. Computes utilization, flags > 90% (overbooked) and < 40% (underbooked), suggests reassignments without applying them. |
| `onboard-task` | Guided workflow to schedule a single task: confirm details, find the best resource through `get_recommendations`, present top candidates, and call `create_appointment` once the dispatcher picks one. |
| `triage-open-tasks` | Walk through the open tasks queue and propose assignments. Reads `dimescheduler://today/open-tasks` and `dimescheduler://resources`, then proposes a `create_appointment` per task without creating anything until the dispatcher confirms. |
## Next steps
1. Check the **[Prerequisites](./prerequisites.md)**.
2. Pick the right **[endpoint and authentication](./configuration.md)** for your environment.
3. Connect your favorite MCP **[client](./clients.md)** - Claude Desktop, Cursor, the MCP Inspector, or your own.
4. Try the **[example prompts](./examples.md)** to get a feel for what's possible.
---
## Open-source playground
We maintain an open-source playground that wraps the Dime.Scheduler MCP server in a working example application. It's the easiest way to see the tools in action without having to build a full MCP client yourself.
**GitHub:** [dime-scheduler/mcp-examples](https://github.com/dime-scheduler/mcp-examples)
## What's in the repository
The playground covers a handful of common scenarios:
- A minimal **MCP client** in TypeScript that connects to the Dime.Scheduler endpoint with an `X-API-KEY` header.
- Sample **prompts** showing how to ask the assistant to find resources, schedule work, and inspect the planning.
- Notes on configuring the same server with **Claude Desktop**, **Cursor**, and the **MCP Inspector**.
It is a learning resource, not a production-ready application. Copy whatever is useful, raise issues, or open pull requests if you find a rough edge.
## Running it locally
Clone the repository and follow the README. At a minimum you'll need:
- A recent version of Node.js (`>= 20`).
- A Dime.Scheduler API key for the sandbox environment (see [Prerequisites](./prerequisites.md)).
- Whatever client you want to drive the demo with - Claude Desktop, the MCP Inspector, or the SDK-based client included in the repo.
```bash
git clone https://github.com/dime-scheduler/mcp-examples
cd mcp-examples
# follow the README for the example you want to run
```
## Contributing
If you have built an interesting integration on top of the Dime.Scheduler MCP server - a custom dispatcher chatbot, a Slack bot, a voice interface - we would love to hear about it. Open an issue on the repository or reach out via your usual support channel.
---
## Prerequisites
Get these pieces in place before you connect an MCP client to Dime.Scheduler.
## A Dime.Scheduler cloud subscription
The MCP server is hosted as part of the Dime.Scheduler cloud platform. You need an active subscription on the **sandbox** environment to try it out today. See [Configuration](./configuration.md) for endpoint URLs and rollout status.
## An API key
The MCP server authenticates with the same API keys used by the [public API](/develop/api-reference). Every request must include an `X-API-KEY` header.
To create one:
1. Sign in to Dime.Scheduler.
2. Go to **Administration → API Keys**.
3. Create a new key, give it a meaningful name (e.g. `MCP - my-laptop`), and copy the value.
4. Store it in your client's secret store - never commit it to a repository or paste it into chat.
:::tip[Per-client keys]
Create a dedicated API key per MCP client (one for Claude Desktop, one for Cursor, one for CI, …) so you can rotate or revoke them independently if a device is lost.
:::
## An MCP-capable client
Any client that speaks the [Model Context Protocol](https://modelcontextprotocol.io) over **Streamable HTTP** can connect. Tested clients include:
- [Claude Desktop](https://claude.ai/download)
- [Claude Code](https://claude.com/claude-code)
- [Cursor](https://cursor.com)
- [MCP Inspector](https://github.com/modelcontextprotocol/inspector) - recommended for first-time exploration
- Custom clients built with the official [TypeScript](https://github.com/modelcontextprotocol/typescript-sdk) or [Python](https://github.com/modelcontextprotocol/python-sdk) SDKs
If your client only supports the older `stdio` transport, you can bridge to the remote endpoint with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) - see [Clients](./clients.md) for examples.
## Network access
The endpoint is a regular HTTPS URL. There is no special firewall configuration required as long as your client can reach `*.api.dimescheduler.com` on port 443. If your organization terminates outbound TLS, allow-list the host.
## Familiarity with your data
The tools operate on the names and identifiers that already exist in your Dime.Scheduler tenant - resource display names, task numbers, category names, and so on. The richer your master data, the better the assistant will be at finding the right records on the first try.
---
## Appointments(Tools)
These tools cover the full lifecycle of planned work: reading the current planning, creating appointments, changing them, and deleting them.
## Get resource planning
**Tool:** `get_resource_planning`
Returns the scheduled appointments (subjects, times, assigned resources) for one or more known resources over a time range.
| Parameter | Type | Required | Description |
| ---------------------- | ------ | -------- | ---------------------------------------------------------------------------- |
| `resourceDisplayNames` | string | Yes | Display names of the resources, comma-separated |
| `startDateTime` | string | Yes | Start of the period (ISO 8601) |
| `endDateTime` | string | Yes | End of the period (ISO 8601) |
| `timeZone` | string | Yes | IANA timezone identifier |
**Example prompt**
> _"Give me the planning for resource Johnny Marr for this week."_
## Search appointments
**Tool:** `search_appointments`
Searches appointments by subject, date range, resource, or task.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------------------------------------------ |
| `timeZone` | string | Yes | IANA timezone identifier |
| `searchTerm` | string | No | Search term to match against subject or description |
| `startDate` | string | No | Start of the date-range filter (ISO 8601) |
| `endDate` | string | No | End of the date-range filter (ISO 8601) |
| `resourceDisplayName` | string | No | Resource display name to filter by |
| `taskNo` | string | No | Task number to filter by |
| `categoryName` | string | No | Category name to filter by |
| `limit` | number | No | Maximum number of results to return (default: `500`) |
**Example prompt**
> _"Show John's appointments this week."_
## Get appointment details
**Tool:** `get_appointment_details`
Returns full details for one appointment. The appointment can be identified by `appointmentNo` (preferred), task number, subject, resource, and/or day.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------- |
| `timeZone` | string | Yes | IANA timezone identifier |
| `appointmentNo` | string | No | Appointment number (hashed ID) - the most precise way to identify an appointment |
| `taskNo` | string | No | Task number to identify the appointment |
| `subject` | string | No | Subject/title of the appointment |
| `resourceDisplayName` | string | No | Resource display name (highly recommended when a task has multiple appointments) |
| `day` | string | No | Day to find the appointment - ISO date with explicit year, or relative (`today`, `tomorrow`, `Monday`) |
| `startDateTime` | string | No | Start date and time (alternative to `day`) - ISO 8601 with explicit year |
**Example prompt**
> _"Get details for the appointment on task 123 tomorrow."_
## Create appointment
**Tool:** `create_appointment`
Creates a new appointment for one or more resources at a specific time.
| Parameter | Type | Required | Description |
| ---------------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `subject` | string | Yes | Subject/title of the appointment |
| `startDateTime` | string | Yes | Start (ISO 8601) |
| `endDateTime` | string | Yes | End (ISO 8601) |
| `resourceDisplayNames` | string | Yes | Display names of the resources to assign, comma-separated (e.g. `John Doe, Jane Smith`) |
| `timeZone` | string | Yes | IANA timezone identifier |
| `description` | string | No | Description / body text |
| `taskNo` | string | No | Task number to link the appointment to (e.g. `24` or `TASK-001`) |
| `categoryName` | string | No | Category name (e.g. `Installation`, `Repair`) |
| `timeMarkerName` | string | No | Time marker name (e.g. `Travel`, `Break`) |
| `importance` | number | No | Priority: `0` = Medium, `1` = High, `2` = Low |
**Example prompt**
> _"Schedule task 24 for John Smith tomorrow at 2 PM for 2 hours."_
## Update appointment
**Tool:** `update_appointment`
Updates an existing appointment with new details. Only the fields you want to change need to be provided. The appointment is identified by `appointmentNo`, or by `subject` + `currentStartDateTime` (with `resourceDisplayName` to disambiguate when needed).
| Parameter | Type | Required | Description |
| ----------------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `timeZone` | string | Yes | IANA timezone identifier |
| `appointmentNo` | string | No | Appointment number to identify the appointment directly |
| `subject` | string | No | Subject/title of the appointment to update (use with `currentStartDateTime`) |
| `currentStartDateTime` | string | No | Current start date and time of the appointment (ISO 8601) |
| `resourceDisplayName` | string | No | Resource display name to disambiguate when multiple appointments share a subject and date |
| `newSubject` | string | No | New subject/title |
| `startDateTime` | string | No | New start (ISO 8601) |
| `endDateTime` | string | No | New end (ISO 8601) |
| `description` | string | No | New description / body text |
| `resourceDisplayNames` | string | No | New resource display names (comma-separated) - reassigns the appointment |
| `taskNo` | string | No | Task number to link the appointment to |
| `categoryName` | string | No | Category name |
| `timeMarkerName` | string | No | Time marker name |
| `importance` | number | No | Priority: `0` = Medium, `1` = High, `2` = Low |
**Example prompt**
> _"Update the appointment with subject 'Meeting' to start at 3 PM instead of 2 PM."_
## Reschedule appointment
**Tool:** `reschedule_appointment`
A higher-level alternative to `update_appointment` for the common case of moving and/or reassigning work. It accepts natural-language style parameters (`relativeTimeShift: "2 hours later"`, `newDuration: "90 minutes"`) and only requires the parameters that are explicitly being changed.
### Identification
| Parameter | Type | Required | Description |
| ---------------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `timeZone` | string | Yes | IANA timezone identifier |
| `appointmentNo` | string | No | Appointment number to identify the appointment directly |
| `taskNo` | string | No | Task number to identify the appointment |
| `subject` | string | No | Subject/title of the appointment |
| `resourceDisplayName` | string | No | Current resource display name to identify the appointment |
| `currentDay` | string | No | Current day - ISO date with explicit year, or relative (`today`, `tomorrow`) |
| `currentStartDateTime` | string | No | Current start date and time (alternative to `currentDay`) - ISO 8601 with explicit year |
### Move (change start time)
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------- |
| `newDay` | string | No | New day - ISO date with explicit year, or relative |
| `newTime` | string | No | New time of day, e.g. `13:14`, `14:00` - used when only a time is specified |
| `relativeTimeShift` | string | No | Relative shift, e.g. `2 hours later`, `30 minutes earlier` |
| `newStartDateTime` | string | No | Exact new start (ISO 8601 with explicit year) |
### Duration (change end time)
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | ---------------------------------------------------- |
| `newDuration` | string | No | New duration, e.g. `2 hours`, `90 minutes` |
| `newEndDateTime` | string | No | Exact new end (ISO 8601 with explicit year) |
### Reassignment
| Parameter | Type | Required | Description |
| -------------------------- | ------ | -------- | -------------------------------------------------------------------- |
| `newResourceDisplayNames` | string | No | New resource display names, comma-separated |
**Example prompt**
> _"Move tomorrow's appointment for Jack Brugerling to next Monday at 2 PM."_
## Delete appointment
**Tool:** `delete_appointment`
Deletes an existing appointment. Identify it with `appointmentNo`, or with `subject` + `startDateTime` (and `resourceDisplayName` if needed).
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- |
| `timeZone` | string | Yes | IANA timezone identifier |
| `appointmentNo` | string | No | Appointment number to identify the appointment directly |
| `subject` | string | No | Subject of the appointment to delete (use with `startDateTime`) |
| `startDateTime` | string | No | Start of the appointment (ISO 8601) |
| `resourceDisplayName` | string | No | Resource display name to disambiguate when multiple appointments share subject and date |
**Example prompt**
> _"Delete the appointment 'Meeting' scheduled for tomorrow at 2 PM."_
## Bulk reschedule appointments
**Tool:** `bulk_reschedule_appointments`
Reschedules many appointments in one call. Each item is processed independently - one item failing does not stop the others. Returns a per-item outcome with success/failure and the new appointment (or error message). Max **50 items** per call.
Use this for _"move all of John's Friday appointments to Monday"_ or _"reassign these 12 jobs to Sarah"_ style requests.
| Parameter | Type | Required | Description |
| ---------- | --------------------- | -------- | -------------------------------------------------------------------------------------------- |
| `timeZone` | string | Yes | IANA timezone identifier - applied to every item in the batch |
| `items` | `BulkRescheduleItem[]`| Yes | Array of reschedule operations (max 50). Each item identifies an appointment and the changes |
Per-item identification follows the same rules as [`reschedule_appointment`](#reschedule-appointment): use one of `appointmentNo`, `taskNo`, `subject`, or `resourceDisplayName` + `currentDay` / `currentStartDateTime`.
Per-item modifications follow the same rules as [`reschedule_appointment`](#reschedule-appointment): provide at least one of `newDay` / `newTime` / `newStartDateTime`, `newDuration` / `newEndDateTime`, or `newResourceDisplayNames`.
**Example prompt**
> _"Move all of John's Friday appointments to Monday at the same times."_
## Bulk delete appointments
**Tool:** `bulk_delete_appointments`
Deletes many appointments in one call. Each item is processed independently - one item failing does not stop the others. Returns a per-item outcome with success/failure. Max **50 items** per call.
Use this for _"cancel all appointments for tomorrow"_ or _"delete the 8 appointments I just made"_ style requests. The [`cancel-and-notify` prompt](../intro.md#prompts) wraps this tool together with `send_message` for the common case of cancelling a resource's day and informing them.
| Parameter | Type | Required | Description |
| ---------- | ---------------- | -------- | ---------------------------------------------------------------------------- |
| `timeZone` | string | Yes | IANA timezone identifier - applied to every item in the batch |
| `items` | `BulkDeleteItem[]` | Yes | Array of delete operations (max 50). Each item identifies an appointment |
Each item must identify an appointment using one of: `appointmentNo`, or `subject` + `startDateTime` (with optional `resourceDisplayName` to disambiguate).
**Example prompt**
> _"Cancel all of Jane's appointments for tomorrow."_
---
## Messaging
Use these tools to push real-time notifications to users and to read back notifications that Dime.Scheduler has attached to entities (appointments, tasks, jobs).
## Send message
**Tool:** `send_message`
Sends a real-time SignalR message to a specific user (by email) or broadcasts to everyone in the current tenant. Use to notify users of schedule changes, urgent updates, or operational events.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------------------------------------------------------------------------- |
| `text` | string | Yes | The message text to deliver to the recipient(s) |
| `userEmail` | string | No | Recipient email. Omit to broadcast to everyone in the tenant. |
| `severity` | string | No | Severity level. One of: `Verbose`, `Debug`, `Info` (default), `Warning`, `Error`, `Fatal`. |
When the recipient has no active SignalR connections, the response notes that the message was not delivered.
**Example prompt**
> _"Tell John his 14:00 appointment moved to 15:00."_
## Search notifications
**Tool:** `search_notifications`
Searches notifications filtered by entity (appointment, task, or job). At least one of `appointmentId`, `taskId`, or `jobId` is required.
For the **most recent** notifications without entity filtering, prefer reading the [`dimescheduler://notifications/recent` data source](../intro.md#resources) - it returns the same data without consuming a tool call.
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | ---------------------------------------------------------- |
| `limit` | number | No | Maximum number of notifications (default: `50`, max: `500`) |
| `appointmentId` | number | No | Appointment id to filter by |
| `taskId` | number | No | Task id to filter by |
| `jobId` | number | No | Job id to filter by |
**Example prompt**
> _"Show notifications related to job 42."_
---
## Optimization
Optimization tools propose how a planning could look. They do **not** modify the schedule on their own - the dispatcher decides whether to apply the result.
## Optimize field service
**Tool:** `optimize_field_service`
Optimizes field service routes using Vehicle Routing Problem (VRP) optimization. Takes a set of resources and a date range, then calculates the optimal assignment and sequencing of appointments to minimize travel time and distance. The response includes the new total travel time, total distance, the resources covered, and notes on what changed.
Requires the **Field Service Optimizer** optional module and the solver to be configured in [Application Setup → Preview](/administration/setup/preview#solver--optimization). The operation may take a while for large windows.
| Parameter | Type | Required | Description |
| ---------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resourceDisplayNames` | string | Yes | Comma-separated display names of resources to optimize (e.g. `John Doe, Jane Smith`) |
| `startDate` | string | Yes | Start of the optimization window (ISO 8601, e.g. `2026-01-08`) |
| `endDate` | string | Yes | End of the optimization window (ISO 8601, e.g. `2026-01-15`) |
| `timeZone` | string | Yes | IANA timezone identifier used to interpret dates and schedule the result |
| `incrementalMode` | boolean | No | When `true`, existing appointments stay pinned in place and only unplanned tasks are added. When `false` (default), every appointment can be rescheduled for optimal routing. |
**Example prompt**
> _"Optimize routes for John, Jane and Lara from Monday to Friday."_
:::tip[Guided BEFORE/AFTER]
The [`optimize-and-explain` prompt](../intro.md#prompts) wraps this tool in a guided BEFORE/AFTER comparison and explicitly instructs the agent never to apply changes without dispatcher approval.
:::
---
## Tools overview
The Dime.Scheduler MCP server groups its tools into six categories. Every tool listed here is callable through the standard MCP `tools/call` method by any connected client.
| Category | Tools | What it's for |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| **[Tasks & Jobs](./tasks.md)** | `search_tasks`, `search_jobs` | Search work by id, name, or category. |
| **[Resources](./resources.md)** | `search_resources`, `get_resource_details`, `get_resource_availability`, `find_available_slots`, `check_time_slot_availability`, `get_recommendations` | Discover resources, check availability, match skills to jobs. |
| **[Appointments](./appointments.md)** | `get_resource_planning`, `search_appointments`, `get_appointment_details`, `create_appointment`, `update_appointment`, `reschedule_appointment`, `delete_appointment`, `bulk_reschedule_appointments`, `bulk_delete_appointments` | Read, create, change, and remove planned work - one at a time or in batches. |
| **[Optimization](./optimization.md)** | `optimize_field_service` | Generate optimal route and assignment plans for a set of resources. |
| **[Messaging](./messaging.md)** | `send_message`, `search_notifications` | Notify users in real time and inspect notifications attached to entities. |
| **[Utilities](./reference.md)** | `parse_relative_time` | Convert relative date phrases into concrete ranges. |
:::tip[Data the agent reads, not calls]
Master data lookups (open tasks, full resource roster, GPS pings, categories, time markers, recent notifications, recommendation scorers) are exposed as [data sources](../intro.md#resources), not tools - the agent reads them via `dimescheduler://...` URIs without consuming a tool call.
:::
## Conventions
These conventions apply across the whole catalog. Knowing them up front makes the per-tool documentation easier to follow.
### Time
- All date and time parameters are **ISO 8601** strings.
- Most tools that span a date range require an explicit `timeZone` parameter - pass an [IANA identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) like `Europe/Brussels`.
- Weeks start on **Monday**, not Sunday, when the [`parse_relative_time`](./reference.md#parse-relative-time) tool resolves expressions like _"this week"_.
- When in doubt about a relative phrase, call `parse_relative_time` first and feed the resolved range into the next tool.
### Resources
Resources are identified by their **display name** (e.g. `John Smith`), not by an internal ID. Where multiple resources are accepted, the parameter is a comma-separated string: `John Doe, Jane Smith`.
### Appointments
Appointments can be identified in several ways depending on what the assistant knows: by `appointmentNo` (the most precise), by `taskNo` plus a date, or by `subject` plus `startDateTime`. When several appointments could match, also pass `resourceDisplayName` to disambiguate.
### Limits
Read tools that return collections accept a `limit` parameter (defaulting to 100, or 500 for `search_appointments`). Increase it cautiously - large result sets are slow for the assistant to reason about.
### Permissions
The MCP server only exposes data that the API key's owner is allowed to see. If a tool returns no results where you expected some, check the user's role and resource visibility in Dime.Scheduler before assuming a bug.
## Choosing the right tool
Use this guide to pick the right tool for common decisions:
- **Searching for unknown resources that match skills?** → [`get_recommendations`](./resources.md#get-recommendations).
- **Looking for a free slot for a known resource?** → [`find_available_slots`](./resources.md#find-available-slots).
- **Validating one specific time?** → [`check_time_slot_availability`](./resources.md#check-time-slot-availability).
- **Moving an existing appointment?** → [`reschedule_appointment`](./appointments.md#reschedule-appointment), not `get_recommendations`.
- **Need to know what's already scheduled?** → [`get_resource_planning`](./appointments.md#get-resource-planning) or [`search_appointments`](./appointments.md#search-appointments).
---
## Reference data & utilities
These helper tools support the others by turning human-friendly date phrases into concrete ranges. Master-data enumeration (categories, time markers) is exposed as [data sources](../intro.md#resources), not tools, so read `dimescheduler://categories` or `dimescheduler://time-markers` instead of calling a tool.
## Parse relative time
**Tool:** `parse_relative_time`
Converts a natural-language relative time expression (`"this week"`, `"5 days ago"`, `"in 3 months"`) into concrete UTC and local-time ranges. Use this **before** calling tools that take an explicit `startDateTime` / `endDateTime` whenever the user phrases a date relatively.
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------- |
| `expression` | string | Yes | Natural-language expression to parse (e.g. `this week`, `next month`, `5 days ago`) |
| `timeZone` | string | Yes | IANA timezone identifier |
| `referenceTime` | string | No | Reference time in ISO 8601 (defaults to the current time in the specified time zone) |
### Supported expressions
- `this week`, `next week`, `last week` (Monday → Sunday)
- `this month`, `next month`, `last month`
- `this year`, `next year`, `last year`
- `today`, `tomorrow`, `yesterday`
- Numeric offsets: `5 days ago`, `2 weeks from now`, `in 3 months`, …
:::tip[Weeks start on Monday]
The parser uses Monday as the first day of the week, regardless of locale. If your assistant is built for a Sunday-start locale, account for the off-by-one.
:::
**Example prompt**
> _"Parse 'next week' in Europe/Brussels timezone."_
---
## Resources(Tools)
The resource tools handle three jobs: looking up resources and their metadata, checking availability for a **known** resource, and discovering **unknown** resources that match a job's requirements.
To pull the **full** roster without a filter, read the [`dimescheduler://resources` data source](../intro.md#resources) instead. It returns the same data without consuming a tool call.
## Search resources
**Tool:** `search_resources`
Searches resources by display name (partial, case-insensitive match). Returns matching resources with their basic info.
| Parameter | Type | Required | Description |
| -------------------- | ------ | -------- | --------------------------------------------------------------------------------- |
| `displayNameFilter` | string | Yes | Partial display name to filter by (e.g. `john` matches `John Doe`, `Johnny Smith`) |
**Example prompt**
> _"Find resources whose name contains 'john'."_
## Get resource details
**Tool:** `get_resource_details`
Returns detailed information for a single resource: skills, capacity, calendar, and contact details.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | -------------------------------------------------------------------------- |
| `resourceDisplayName` | string | Yes | The display name of the resource |
| `capacityStartDate` | string | No | Start date for capacity and calendar information (ISO 8601) |
| `capacityEndDate` | string | No | End date for capacity and calendar information (ISO 8601) |
**Example prompt**
> _"Get details for resource John Smith."_
## Get resource availability
**Tool:** `get_resource_availability`
Returns **all** availability periods (both available and unavailable) for one resource over a time range. Periods can be any length - this is the raw availability timeline.
Use this only when the assistant needs to render or reason over a complete calendar view. For "find me a free slot", prefer [`find_available_slots`](#find-available-slots).
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------------------------------------------ |
| `resourceDisplayName` | string | Yes | The display name of the resource |
| `startDateTime` | string | Yes | Start date and time (ISO 8601) |
| `endDateTime` | string | Yes | End date and time (ISO 8601) |
| `timeZone` | string | Yes | IANA timezone identifier (e.g. `Europe/Brussels`) |
**Example prompt**
> _"Show me all availability periods for John Smith next week."_
## Find available slots
**Tool:** `find_available_slots`
Returns slots of a specified duration that fit inside the resource's availability over a date range. **This is the preferred tool for scheduling questions** of the form _"when is X free for N hours?"_.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | ------------------------------------------------------------------------ |
| `resourceDisplayName` | string | Yes | The display name of the resource |
| `startDate` | string | Yes | Start of the search range (ISO 8601) |
| `durationInMinutes` | number | Yes | Duration of the slot, e.g. `30`, `60`, `120` |
| `timeZone` | string | Yes | IANA timezone identifier |
| `endDate` | string | No | End of the search range (defaults to the same day as `startDate`) |
**Example prompt**
> _"When is John Smith available for a 2-hour meeting this week?"_
## Check time slot availability
**Tool:** `check_time_slot_availability`
Validates whether one specific time window is available for one resource. Returns a yes/no answer plus any conflicting periods.
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | -------------------------------------------------------------------------- |
| `resourceDisplayName` | string | Yes | The display name of the resource |
| `startDateTime` | string | Yes | Start of the slot to check (ISO 8601) |
| `timeZone` | string | Yes | IANA timezone identifier |
| `endDateTime` | string | No | End of the slot (defaults to one hour after `startDateTime`) |
**Example prompt**
> _"Is John Smith available on January 15th at 2 PM?"_
## Get recommendations
**Tool:** `get_recommendations`
The **discovery** tool. Finds resources matching a set of skills, roles, and/or location, and returns the best available slots for each candidate. Use it when the user does not yet know **which** resource should do the work.
:::warning[Discovery vs. reassignment]
For moving an existing appointment to another resource, use [`reschedule_appointment`](./appointments.md#reschedule-appointment) - not `get_recommendations`.
:::
### Time window
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------------------------------------- |
| `startDate` | string | Yes | Start of the availability search window in UTC (ISO 8601) |
| `endDate` | string | Yes | End of the availability search window in UTC (ISO 8601) |
| `timeZone` | string | Yes | IANA timezone identifier |
### Filters
| Parameter | Type | Required | Description |
| ---------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ |
| `requirements` | string | No | Skills filter - _what the resource can do_ (e.g. `plumbing, electrical work, HVAC`). Comma-separated. |
| `resourceTypes` | string | No | Role filter - _what the resource is_ (e.g. `technician, engineer, driver`). Comma-separated. |
| `duration` | string | No | Required duration in ISO 8601 format (e.g. `PT2H`, `PT30M`) |
| `granularity` | string | No | Time-slot granularity in ISO 8601 format (e.g. `PT15M`, `PT30M`, `PT1H`) |
### Location
| Parameter | Type | Required | Description |
| -------------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `locationAddress` | string | No | Address string used for proximity scoring |
| `locationCountry` | string | No | Country code for geocoding (e.g. `US`, `BE`, `GB`) |
| `locationLatitude` | number | No | Latitude (alternative to `locationAddress`) |
| `locationLongitude` | number | No | Longitude (alternative to `locationAddress`) |
| `locationMode` | string | No | `home` (default) or `actual` - base proximity on home address or current GPS location |
| `maxDistanceKm` | number | No | Maximum distance in kilometers for the resource filter |
**Example prompt**
> _"Who is available this week for a 2-hour job near London (UK) with the skill electrician?"_
---
## Tasks & Jobs
Find work that still needs to be planned with these tools. They are read-only: scheduling something is the job of the [appointment tools](./appointments.md).
:::tip[Open tasks live in a resource, not a tool]
The full set of unplanned tasks is exposed as the [`dimescheduler://today/open-tasks` data source](../intro.md#resources), not as a tool. Reading the resource is cheaper than a tool call and gives the agent standing context for the dispatcher's queue.
:::
## Search tasks
**Tool:** `search_tasks`
Looks up tasks by task number, subject, job, or category. Reach for this when the assistant needs a specific task by name or wants to filter by job.
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------------------------------------------- |
| `taskNo` | string | No | Task number to search for (exact match) |
| `jobNo` | string | No | Job number to filter by |
| `searchTerm` | string | No | Search term to match against task subject or description |
| `categoryName` | string | No | Category name to filter by |
| `limit` | number | No | Maximum number of results to return (default: `100`) |
**Example prompts**
> _"Search for tasks related to plumbing."_
>
> _"Which open tasks belong to job ABC123?"_
## Search jobs
**Tool:** `search_jobs`
Looks up jobs by job number, name, or category. Useful when the user mentions a project or work order rather than an individual task.
| Parameter | Type | Required | Description |
| -------------- | ------ | -------- | ------------------------------------------------------------ |
| `jobNo` | string | No | Job number to search for (exact match) |
| `searchTerm` | string | No | Search term to match against job name or description |
| `categoryName` | string | No | Category name to filter by |
| `limit` | number | No | Maximum number of results to return (default: `100`) |
**Example prompt**
> _"Find all jobs for project ABC123."_
---
## Troubleshooting(Mcp)
Use this page to fix the most common MCP connection problems before you open a support ticket. Most issues fall into one of a few categories, and each section below tells you exactly what to check.
## Connection fails with `401 Unauthorized`
**Cause:** the `X-API-KEY` header is missing, malformed, or carrying a key that has been revoked.
**What to check:**
- The header name is spelled exactly `X-API-KEY` (case-insensitive in practice, but stick to upper-case).
- The value contains no surrounding quotes, whitespace, or `Bearer` prefix - it's a raw key.
- The key still exists and has not been revoked in **Administration → API Keys**.
- The key was created in the same environment you're calling.
## Client cannot find any tools
**Cause:** the URL or transport is misconfigured, so the client connected but never received the tool catalog.
**What to check:**
- The URL ends with `/mcp` (e.g. `https://sandbox.api.dimescheduler.com/mcp`).
- The transport is set to **Streamable HTTP**. Older `stdio`-only clients need the [`mcp-remote`](./clients.md#claude-desktop) bridge.
- The client was restarted after you edited its configuration - most clients only re-read the config on startup.
If the [MCP Inspector](./clients.md#mcp-inspector) sees the tools but your real client doesn't, the issue is on the client side, not on the server.
## "Resource not found" or "Task not found" errors
**Cause:** the assistant called a tool with a name or identifier that does not match any record visible to the API key.
**What to check:**
- The resource is referenced by **display name**, not employee number or email.
- The user that owns the API key has permission to see the resource. Resource visibility filters apply to MCP requests just like they apply to the UI and the public API.
- Capitalization and accents match. _"José Garcia"_ and _"Jose Garcia"_ are different display names.
- For tasks and jobs, the format matches - `TASK-001` is not the same value as `001`.
## Wrong data returned for "this week" / "tomorrow" / …
**Cause:** the relative phrase was resolved against the wrong time zone or reference time.
**What to do:**
- Make sure the assistant passes a `timeZone` parameter that matches the **user's** time zone, not the server's.
- Have the assistant call [`parse_relative_time`](./tools/reference.md#parse-relative-time) before any tool that needs a concrete date range. Passing relative phrases straight into the parameters of other tools is brittle.
- Remember that weeks start on **Monday**. _"This week"_ on a Sunday morning means the previous Monday → today's Sunday.
## "I scheduled it on the wrong day"
**Cause:** the user's prompt was ambiguous, the assistant guessed a date, and didn't ask to confirm.
**What to do:**
- Add a system instruction to your assistant along the lines of _"Always confirm the date, time, and resource before creating, updating, rescheduling, or deleting an appointment."_
- If you operate in a strictly controlled environment, point the MCP client at the **sandbox** environment first and rehearse common prompts before pointing it at production data.
## The assistant calls `get_recommendations` when it should reschedule
**Cause:** `get_recommendations` is the **discovery** tool - _"find me a resource"_. `reschedule_appointment` is for moving existing work. Some prompts are ambiguous, and the model picks the wrong one.
**What to do:**
- Use the keyword **move** or **reschedule** when you mean reassignment, and **find** or **who is available** when you mean discovery.
- Add a system instruction that explicitly draws the line: _"Use `reschedule_appointment` to change an existing appointment. Use `get_recommendations` only when the resource is not yet known."_
## Something else
If none of the above apply:
1. Reproduce the call against the [MCP Inspector](./clients.md#mcp-inspector) to isolate whether the issue is server-side or client-side.
2. Capture the **exact tool name**, the **parameters** the assistant sent, and the **response** the server returned.
3. Open a ticket through your usual support channel with that information attached.
The more concrete the report, the faster we can help.
---
## Common Transaction Errors
When appointments fail to sync between Dime.Scheduler and NAV, the transaction log records why. This page collects the error messages you'll run into most often, explains what causes each one, and walks you through fixing it.
## Request Channel Timeout
> The request channel timed out attempting to send after 00:01:00. Increase the timeout
value passed to the call to Request or increase the SendTimeout value on the Binding.
The time allotted to this operation may have been a portion of a longer timeout. The
HTTP request to 'http://[server]/[instance]/WS/[company]/Page/Appointment' has exceeded
the allotted timeout of 00:01:00.
### Cause
A web service call between Dime.Scheduler and NAV took longer than the configured timeout (1 minute by default).
**Most common cause:** Dime.Scheduler can't reach the web service at all, usually because a firewall is blocking the connection.
Other causes include:
- Network latency or connectivity issues
- The NAV/NAV server is under heavy load
- Large data payloads being transmitted
- Slow database queries on the NAV/NAV side
- The web service endpoint is processing complex business logic
### Solution
1. **Check firewall and network accessibility:**
- Verify that the NAV/NAV web service URL is accessible from Dime.Scheduler cloud
- Open the required firewall ports to allow traffic from Dime.Scheduler to NAV/NAV
- For on-premises: Ensure the web service endpoint is published and accessible externally
- For cloud: Verify that the service is not blocked by network security groups or Azure firewall rules
- Test connectivity using tools like `telnet`, `curl`, or browser access from Dime.Scheduler cloud
2. **Verify connector URL:**
- Navigate to **Administration** > **Connectors** in Dime.Scheduler
- Verify the web service URL is correct and accessible
3. **Increase the timeout value if connectivity is confirmed:**
- Edit the NAV/NAV connector
- Increase the **Request Timeout** value (e.g., from 60 to 120 seconds or higher)
- Save the connector configuration
4. **Optimize NAV/NAV performance:**
- Review and optimize any custom code in the appointment processing codeunits
- Check for database locks or slow queries
- Ensure the NAV/NAV server has adequate resources
- Review the NAV/NAV server event logs for performance issues
5. **Retry the transaction:**
- After resolving connectivity issues, retry the failed transaction from the [Errors page](/administration/transactions/errors)
:::tip
Timeouts that cluster around specific times of day usually point to server load. Schedule heavy processing tasks outside of peak planning hours.
:::
## HTTP Request Unauthorized
> The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic realm=...'
This error, and variants like "401 Unauthorized" or "Invalid credentials", means the connector's authentication doesn't match what NAV/NAV expects, or the credentials are wrong.
### Cause
The authentication method set on your connector doesn't match the server, or your credentials are incorrect or expired.
### Solution
### Step 1: Fix the authentication
1. Go to **Administration** > **[Connectors](/administration/connectors)** in Dime.Scheduler
2. Edit your NAV connector
3. Set authentication to the desired authentication method
4. Fill in the required authentication fields
:::tip[Using Basic auth?]
If the test fails with a wrong password, check that the account isn't locked in Active Directory or NAV.
:::
### Step 2: If the test still fails
Check these common issues:
| Problem | Solution |
| ------------------------------ | -------------------------------------------------------------- |
| OAuth: "Client secret expired" | Generate a new secret in Azure Portal and update the connector |
| OAuth: "Invalid tenant ID" | Verify the tenant ID matches your Azure AD tenant |
| Basic Auth: "Invalid username" | Try without the domain prefix (just `USERNAME`) |
| Basic Auth: "Account locked" | Unlock the account in Active Directory or NAV/NAV |
| "Permission denied" | Verify the account has access to Dime.Scheduler tables |
### Step 4: Retry the failed transaction
Once the test connection succeeds, retry the failed appointment from the [Errors page](/administration/transactions/errors).
---
## General Troubleshooting Steps
For any transaction error, work through these steps. For deeper guidance, see the [NAV Troubleshooting](/integrations/nav/troubleshooting) guide.
1. **Check the transaction details:**
- Navigate to **Administration** > **Transactions** > **[Errors](/administration/transactions/errors)**
- Click on the failed appointment to view detailed error information
- Review the error message, timestamp, and affected connector
2. **Verify connector configuration:**
- Navigate to **Administration** > **[Connectors](/administration/connectors)**
- Review the connector settings for the affected back office system
3. **Check the NAV/NAV system:**
- Review the **Dime.Scheduler Appointments** table
- Check for any error messages or processing failures
- Verify the web service endpoints are available and responding
4. **Retry the transaction:**
- After resolving the underlying issue, retry the failed transaction
- Monitor the transaction to ensure it completes successfully
---
## Updated standard objects
A few FastTrack solutions require small changes to standard Dynamics NAV objects so that every feature works. The footprint is deliberately minimal: only one table and one codeunit have been changed, and in each of those a single line was added to call a function of the Dime.Scheduler framework.
This page lists the standard objects modified per FastTrack solution and explains each change, so you can decide whether a given modification is needed for your setup. Most of these changes can be handled in other ways. For example, the buttons added to the menu ribbon on the resource card send resources to Dime.Scheduler; if you would rather not take over that modification, you can skip it and provide your own solution.
These objects ship in separate fob and txt files named `Dime.Scheduler_xxxx.x.x_NAVxxxxW1_FastTrack`. Merge the objects for the FastTrack solutions you want to use, or apply the modifications manually since they are very limited.
## Standard Resources
| Object | Name | Modification |
| --------- | -------------------------- | ---------------------------------------------------- |
| Page 76 | Resource card | Buttons added in menu ribbon to send resource data |
| Page 77 | Resource list | Buttons added in menu ribbon to send resource data |
| Page 6013 | Resource capacity settings | Button added in menu ribbon to send capacity entries |
## Service Management
| Object | Name | Modification |
| ---------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Page 5900 | Service order | Buttons added in menu ribbon to send or update service order |
| Page 9318 | Service orders | Buttons added in menu ribbon to send or update service order |
| Table 5901 | Service item line | 1 function call added to the OnValidate of the `Repair Status` field in order to send an update of the category or time marker when the repair status changes. |
## Jobs
| Object | Name | Modification |
| --------- | -------------- | -------------------------------------------------------------------- |
| Page 88 | Job card | Buttons added in menu ribbon to send or update job or job task lines |
| Page 1002 | Job task lines | Buttons added in menu ribbon to send or update job or job task lines |
## Assembly Orders
| Object | Name | Modification |
| -------- | -------------- | ------------------------------------------------------------- |
| Page 900 | Assembly order | Buttons added in menu ribbon to send or update assembly order |
## Production Routing
| Object | Name | Modification |
| ------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Page 99000754 | Work center card | Buttons added in menu ribbon to send work center data as resources |
| Page 99000760 | Machine center card | Buttons added in menu ribbon to send machine center data as resources |
| Page 99000813 | Planned production order | Buttons added in menu ribbon to send production routing lines |
| Page 99000829 | Firm planned prod. order | Buttons added in menu ribbon to send production routing lines to |
| Codeunit 5407 | Prod. order status management | 1 function call added to the function ChangeStatusOnProdOrder in order to send an update of the category or time marker when the production order status changes. |
## Sales
| Object | Name | Modification |
| ------- | ----------- | ---------------------------------------------------------- |
| Page 42 | Sales order | Buttons added in menu ribbon to send or update sales order |
## HR
| Object | Name | Modification |
| --------- | -------------------- | ------------------------------------------------------------------------------- |
| Page 5212 | Absence registration | Buttons added in menu ribbon to send the absence registration as an appointment |
---
## Versioning and solution IDs
## Versioning
Every Dynamics NAV connector object carries a tag in its Version List so you can tell at a glance which add-on and FastTrack solution it belongs to. The tag follows one of these patterns:
- **Standard add-on objects**
- Uses the Dime.Scheduler add-on version number
- For example: DSTW118.0.0
- **Standard add-on objects for FastTrack solutions**
- Uses the Dime.Scheduler add-on version number and the FastTrack version number and solution ID
- For example: DSTW118.0.0,DST.SOL.SERVMGT
- **Standard NAV objects that have been modified to fully support the FastTrack functionality**
- Uses the standard NAV version number and the FastTrack version number and solution ID
- For example NAVW19.00,DSTW118.0.0.SOL.MFG
## FastTrack solution IDs
These IDs appear in the Version List to mark which FastTrack solution an object belongs to:
| FastTrack solution | ID |
| ------------------------------------------------------------------------------ | ------- |
| Global (objects used in multiple solutions) | GLOB |
| Service Management | SERVMGT |
| Jobs | JOB |
| Assembly order planning | ASSORD |
| Production routing planning | MFG |
| Sales documents | SAL |
| HR absence | HR |
| Time sheet integration (no specific ID as part of other solutions or global) | |
---
## Sending and receiving data
## Sending data
NAV sends all of its data to Dime.Scheduler through the Dime.Scheduler REST Service. This is a generic web service built on a key-value pair schema, which gives you full flexibility over both the source and the format of the data and keeps the business logic inside NAV.
> ℹ️ We strongly recommend using the "empty" codeunits provided in a separate fob file to build custom solutions, rather than modifying the standard add-on objects. Those standard objects *will* gain new functionality in future releases, so working in the empty codeunits avoids merge conflicts when you update. Copy a standard add-on codeunit, or one of its functions, into an empty codeunit and make your changes there. Remember to point to your codeunit or function afterwards, for example by updating the call from one of the action buttons on a page.
### Data types
You can send the following types of data from NAV to Dime.Scheduler.
- **Resources and the resources' filter values**: remember that a resource is a general term and that you can send any data from NAV as a resource to Dime.Scheduler whether this represents a person, machine, (office) equipment, hotel or meeting room, car, etc.
- **Jobs, tasks and the tasks' filter values**: remember that jobs and tasks are general terms and that you can send any data from NAV as a job and its associated tasks to Dime.Scheduler whether these are from standard tables, custom tables or tables that are part of a vertical solution.
- **Filter groups and filter values**
- **Categories**: the category determines the color of an appointment.
- **Time markers**: the time marker is shown as a colored horizontal bar attached to the appointment.
- **Pins**: the pin determines the color of the markers shown in the map.
- **Appointments**: when a task is planned in Dime.Scheduler an appointment is created. You can also create appointments directly from NAV. For example, when an employee registers a holiday request in the NAV employee absence table, you can send that absence record as an appointment to Dime.Scheduler and inform the planners of the holiday right away. With the right security setup in Dime.Scheduler, planners see that appointment but cannot modify or delete it.
- **Appointment updates**: dedicated functions let you update an appointment's category or time marker. The category and time marker control the colors shown on the planning board in Dime.Scheduler and are often used to visualize an appointment's status: that a task has started, is in process, is completed or is overdue, or, for example, that the related NAV document or transaction has been invoiced.
- **Task and appointment locks**: appointments can be locked or unlocked. A locked appointment cannot be edited in Dime.Scheduler. You can lock or unlock individual appointments, or every appointment attached to the same task.
- **GPS tracking and asset location**: when you receive or store GPS tracking or asset location data in NAV, you can send it to Dime.Scheduler to update the coordinates immediately, which helps planners respond better to planning requests.
### Supported areas
To update data you have already sent to Dime.Scheduler, just send it from NAV again. Codeunits or functions handle the sending. A user can trigger them manually with a button on the menu ribbon or form, they can run during the process flow (for example when a record's status changes), or they can be scheduled.
The Dime.Scheduler Connector ships with pre-built codeunits that support several standard NAV tables and areas, including but not limited to:
- Service orders
- Jobs
- Sales orders and quotes
- Production orders
- Employee absences
You can extend or modify these codeunits, write your own, or integrate the functions into other objects.
### Developing a send function
Every function you need to send data to Dime.Scheduler lives in codeunits 2087625 "DS Web Service Management" and 2087644 "DS Dime.Scheduler Management". A send function calls the Dime.Scheduler REST Service, passes the necessary parameters and, when required, sends document filter values as well.
The Dime.Scheduler REST Service expects 3 parameters:
- The function name to process the data
- An array with parameter names
- An array with the corresponding values
A send function builds and sends those 3 parameters in 4 steps:
1. To transfer document filter values, pass the current record's `RecRef` to the `TransferDocFilterValues` function of "DS Dime.Scheduler Management". The table the record belongs to must be set up in DS Doc. filter values. Document filter values are the filter values defined for a resource or task.
2. Initialize: call the `InitParameters` function of "DS Web Service Management". No parameters are required.
3. Populate the parameter arrays: call the `AddParameter` function of "DS Web Service Management" for each field. The AddParameter function requires 2 parameters:
- **ParameterName**: the name of the field
- **ParameterValue**: the value for the field
4. Call the Dime.Scheduler web service: call the `CallDimeSchedulerWS` function of "DS Web Service Management". The CallDimeSchedulerWS function requires 1 parameter:
- **StoredProcedureName**: the name of the stored procedure or function that Dime.Scheduler uses to process the data.
The example below shows codeunit 2087631 "DS Send Resource", which sends a NAV Resource to Dime.Scheduler. It illustrates the 3 steps along with the inclusion of the document filter values (1).

For a complete, detailed overview of the functions in the Dime.Scheduler web service, see the [developers manual](develop/api.md). For an overview of the codeunits and functions provided by the Dime.Scheduler Connector, see the [connector functions reference](integrations/nav/dev/functions.md).
### Calling a send function
To send data to Dime.Scheduler, run a codeunit's RUN-method or call the send function, passing the record you want to send. A separate NAV object file (`*.fob`) provides several standard pages and forms with the send function already added.
For example, on Page 5900 Service Order an ActionGroup is added in the Action Designer to send a Service Order to Dime.Scheduler. It runs the RUN-method of codeunit 2087627 "DS Send Service Header", passing the Service Header record.

That is all you need.
On Page 1002 Job Task Lines an ActionGroup is added in the Action Designer with the option to send a status update to Dime.Scheduler. Here the appointment's category (which sets the color of the appointment on the schedule) visualizes the status of the job task. Changing the category updates the appointment's color in Dime.Scheduler immediately, giving users an instant visual read on the overall status.
This example uses the DS Source Type to retrieve the appointment that belongs to the job task. See the [receiving data section](#receiving-data) to learn more about the DS Appointment table.

This kind of setup is also handy when you display Dime.Scheduler on a separate (large) monitor as a master console or control room screen.
## Receiving data
When you plan a task in Dime.Scheduler an appointment is created, and that appointment is sent straight back to the NAV web service for Dime.Scheduler. Every change to an appointment in Dime.Scheduler, whether it is created, modified or deleted, adds an entry to table 2087631 "DS Appointment". For every resource linked to the appointment (Dime.Scheduler supports linking multiple resources to a single appointment) an entry is added to table 2087632 "DS Appointment Resource". When an appointment has custom fields, an entry with the value for each field is added to table 2087642 "DS Appointment Field Value".
Codeunit 2087635 "DS Handle Appointment" then processes the "DS Appointment" record together with its linked "DS Appointment Resource" and "DS Appointment Field Value" records. The source type that was sent with the task, and returned with the appointment, determines which codeunit processes the appointment's data further into NAV.
This gives you the flexibility to handle planning and scheduling data in standard tables, custom tables or tables that belong to a vertical solution. It also guarantees that the appointment data is processed entirely through the business logic of NAV.
| Field Name | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entry No. | A unique, sequential number. |
| Source Type | The source type that is sent with the task to Dime.Scheduler is returned with the appointment. The source type defines how the appointment data is processed in NAV. |
| Source App | The source app that is sent with the task to Dime.Scheduler is returned with the appointment. |
| Job No. | Contains the identification of the job, the parent of task. For example this can be the service order, sales order, job or production order number. |
| Task No. | Contains the identification of the task. For example this can be the service item line or sales order line number or the job task number. |
| Appointment Id | The unique id of the appointment within Dime.Scheduler. This id allows you to update the appointment from NAV. |
| Subject | The subject of the appointment as shown in Dime.Scheduler. When you update the appointment from NAV you can: Return the subject as is.Send an empty subject. The subject is then generated by Dime.Scheduler as defined by the setup templates.Specify a value, thereby overwriting the subject in Dime.Scheduler. This allows you to send data from NAV as part of the subject shown in Dime.Scheduler and is yet another example of how to display status information in Dime.Scheduler based on the data and business logic of NAV. |
| Body | The body text of the appointment as shown in Dime.Scheduler. When you update the Appointment from NAV you can: Return the body as is.Send an empty body. The body is then generated by Dime.Scheduler as defined by the setup templates.Specify a value, thereby overwriting the body in Dime.Scheduler. This allows you to send data from NAV as part of the body shown in Dime.Scheduler. |
| Start | The start date and time of the appointment. |
| End | The end date and time of the appointment. |
| Time Marker | The current value of the time marker. You can use the appointment update functions to change the time marker in Dime.Scheduler based on the data and business logic of NAV. |
| Category | The current value of the category. You can use the appointment update functions to change the category in Dime.Scheduler based on the data and business logic of NAV. |
| Importance | The importance or priority of the appointment. Values are: 0 = None or Medium1 = High2 = Low |
| Duration (Sec.) | The duration of the appointment in seconds. Seconds is the base unit of measure for Dime.Scheduler. |
| Non Working Time (Sec.) | The amount of non-working time for an appointment. |
| Planning Quantity | The planned quantity expressed in the Unit of Measure. Calculated as: (Duration (Sec.) - Non Working Time (Sec.)) / Planning UoM Conversion |
| Planning Unit of Measure | The unit of measure code. |
| Planning UoM Conversion | The conversion from seconds to the unit of measure. |
| Use Fix Planning Quantity | Indicates whether the planning quantity is editable by the user in Dime.Scheduler (True) or calculated based on duration (Sec.) (False=default) |
| Round To Unit Of Measure | Indicates whether duration (sec.) is rounded to the unit of measure. Rounding is performed in Dime.Scheduler. |
| Last Modified Date | Date and time of the last modification |
| Last Modified User | User that made the last modification |
| Created Date | Date and time when then appointment was first created |
| Created User | User that created the appointment |
| Sent From Backoffice | Indicates whether the update of the appointment was made by the user (False) or by the back office (True) |
| Backoffice Id | Optional parameter that can be sent from the back office to link the appointment to a specific record. This information is simply returned by Dime.Scheduler. |
| Backoffice Parent Id | Optional parameter that can be sent from the back office to indicate the parent record of the record to which the appointment is linked. This information is simply returned by Dime.Scheduler. |
| Database Action | Indicates whether the appointment was created, modified or deleted in Dime.Scheduler. Values are:I = InsertU = UpdateD = Delete |
| Status | Status of the appointment record in NAV. Values are: New: the appointment record was received by NAV but not yet processed.Processed: the appointment record was successfully processed by NAV. Error: an error occurred during the processing of the appointment record. It is up to the user to take the necessary corrective actions after which the appointment record can be processed again. |
| Error Message | When an error occurs during the processing of the appointment record then this field will contain the last known error. |
| DateTime Received | The date and time when the appointment record was received by NAV. |
| Resource Report Filter | This field is used to retrieve the resource number that is passed by the action URI. Its only purpose is to extract the resource number from the URI and pass it to the defined codeunit. |
The "Process" action on the menu ribbon, or the button on the form, lets the user process an appointment record manually.
---
## NAV Objects and configuration overview
The installation package gives you everything you need to set up the NAV connector: the objects, the setup data, and the roles and permissions. Every object file ships as both a fob and a txt file.
File names follow this convention: `Dime.Scheduler_version_NAV version` and `version number_short description.extension`.
For example, `Dime.Scheduler_16.2.1_NAV2016W1_AllObjects.fob` breaks down as:
- Prefix: Dime.Scheduler
- Version of DS: 16.2.1
- NAV version: NAV2016W1
- Short description: AllObjects
The table below explains each file, keyed by the Short description in its file name:
| Codeunit | Purpose |
| --------------- | --------------------------------------------------------------------------------------------------------------------------- |
| _AllObjects | Contains all objects of the Dime.Scheduler Connector for NAV. No standard objects are included in this file. |
| _MenuSuite | Contains the Classic and RTC menu suite for Dime.Scheduler. |
| _FastTrack | Contains standard objects that have been modified in order to support all functionality offered by the FastTrack modules. |
| _EmptyCodeunits | Contains 10 empty codeunits which can be used to create an integration with your Vertical solution or a custom solution. |
| NAV 2009 | |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| User Roles NAV2009.xml | Contains 2 Roles DS-ALL and DS-SUPER. To be imported into the Data Migration. Administration - Application Setup - Company Setup - Data Migration. |
| Permissions NAV2009.xml | Contains the permissions of the 2 Roles.To be imported into the Data Migration. |
| DS Setup and Connector Setup NAV2009.xml | Contains default setup for Dime.Scheduler. To be imported into the Data Migration. |
| NAV 2013 (R2) - NAV 2015 | |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Package DIME.SCHEDULER.rapidstart | Contains 2 Permission Sets (DS-ALL and DS-SUPER), the Permissions for these sets and the default setup for Dime.Scheduler. To be imported in /Administration/Application Setup/RapidStart Services for Microsoft Dynamics NAV/Configuration Packages. We recommended to use the FastTrack Wizard instead of the RapidStart package |
---
## Send, receive and support functions
This is the reference catalogue of codeunits in the Dynamics NAV connector. Use it to find which codeunit handles a given task when you are configuring or extending the integration: send functions push data from NAV to Dime.Scheduler, receive functions write planning data back into NAV, and support functions are the helpers you call from your own code.
## Send functions
These codeunits push data from NAV to Dime.Scheduler. The FastTrack column shows which wizard module installs each one.
| Codeunit | Name | Purpose | FastTrack |
| -------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| 2087627 | DS Send Service Header | Contains all functions to create, update or delete jobs and Tasks in Dime.Scheduler using the Service Header and Service Item Lines. Contains additional functions to update an Appointment or planned task when the Repair Status Code on the Service Item Line is modified. | SERVMGT |
| 2087628 | DS Send Assembly Order | Contains all functions to create, update or delete Jobs and Tasks in Dime.Scheduler using the Assembly Order. Contains additional functions to create Appointments or planned tasks based on "Resource" lines in the Assembly Order when these where not created in Dime.Scheduler. | ASSORD |
| 2087629 | DS Send Absence Reg. | Creates an Appointment or planned task in Dime.Scheduler from an Absence Registration in NAV. An Absence Registration can be created by a user in NAV or by a user in Dime.Scheduler using the "New Absence Registration" shortcut. | HR |
| 2087630 | DS Action New Absence Reg. | Opens the Cause of Absence page when a user in Dime.Scheduler uses the "New Absence Registration" shortcut. After selecting a value an Absence Registration is created and then sent to Dime.Scheduler using codeunit 2087629 "DS Send Absence Reg." | HR |
| 2087631 | DS Send Resource | Uses the standard Resource table to create resources in Dime.Scheduler | GLOB |
| 2087632 | DS Send FilterGroup | Sends FilterGroups. | |
| 2087633 | DS Send FilterValue | Sends FilterValues. | |
| 2087634 | DS Scheduled Synch. | Sends Resources, FilterGroups, FilterValues, Work Centers and Machine Centers. Used to schedule in Job Queue | |
| 2087637 | DS Send Job | Contains all functions to create, update or delete Jobs and Tasks in Dime.Scheduler using the Job and Job Task lines. | JOB |
| 2087638 | DS Send Notification | Contains all functions to create, update or delete Notifications in Dime.Scheduler. These Notifications can be general or linked to a Job, Task and/or Appointment. | |
| 2087639 | DS Send Sales Header | Contains all functions to create, update or delete Jobs and Tasks in Dime.Scheduler using the Sales Order. Contains additional functions to create Appointments or planned tasks based on "Resource" lines in the Sales Order when these where not created in Dime.Scheduler. | SAL |
| 2087640 | DS Send Appointment | Contains functions to create, update or delete an Appointment (planned task) in Dime.Scheduler and to add or remove a resource to or from an existing Appointment | |
| 2087642 | | Currently not in use | |
| 2087643 | DS Update Appointment Status | Contains auxiliary functions to update Appointments in Dime.Scheduler: UpdateCategory UpdateCategoryFromId UpdateTimeMarker UpdateTimeMarkerFromId UpdateLocked UpdateLockedFromId | |
| 2087647 | DS Capacity Management | Contains the functions to send Resource Capacity Settings to Dime.Scheduler: SendResourceCapacity SendResourceGroupCapacity. An example is implemented on page 6013 "Resource Capacity Settings" | |
| 2087649 | DS Send Production and Routing | Sends a Production Order as a Job. Sends the Routing Lines as Tasks and creates Appointments based on the Routing Lines. Also contains the functions: UpdateAppointmentFromRouting UpdateAppointmentOrderStatus | MFG |
| 2087650 | DS Send Work & Machine Centers | Uses the standard Work Center and Machine Center tables to create resources in Dime.Scheduler. | MFG |
| 2087671 | DS Send Captions | Example on how to send NAV Captions to Dime.Scheduler | |
## Receive functions
These codeunits process the appointment data Dime.Scheduler sends back and write it into the matching NAV tables.
| Codeunit | Name | Purpose | FastTrack |
| -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
| 2087635 | DS Handle Appointment | The codeunit that is run by the web service after the Appointment data is received and written to the DS Appointment and DS AppointmentResource table. | |
| 2087641 | DS Handle Absence Reg. | Processes the Appointment data for Absence tasks into the Absence Registration table | HR |
| 2087645 | DS Handle Service Allocation | Processes the Appointment data for Service Orders into the Service Order Allocation. | SERVMGT |
| 2087646 | DS Handle Job Planning | Processes the Appointment data for Jobs into the Job Planning Lines. | JOB |
| 2087648 | DS Handle Sales Order Update | Processes the Appointment data for Sales Orders: creates, updates or deletes Resource lines and updates dates. | SAL |
| 2087651 | DS Handle Routing Update | Processes the Appointment data for Production Orders to update the corresponding Routing Line(s) | MFG |
| 2087652 | DS Handle Time Sheet | Updates the Time Sheet using the Appointment data for Service and Job planning when enabled in setup | GLOB |
| 2087653 | DS Handle Assembly Planning | Processes the Appointment data for Assembly Orders: creates, updates or deletes Resource lines and updates dates on the Assembly Order and linked Sales Order | ASSORD |
## Support functions
These are the helper codeunits and functions you call from your own code, for formatting values, building URLs, converting durations, and similar tasks.
| Codeunit | Name or function | Purpose |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2087625 | DS Web Service Management HandleDateTime/HandleDate/HandleTime/HandleBool | When adding a value to the parameter array using the AddParameter function use these functions to correctly format date, time, datetime and Boolean values. |
| 2087636 | DS Doc. Filter Value Mgt. | Contains the function TransferDocFilterValues which is used to send the Filter Values for Resources and Tasks to Dime.Scheduler |
| 2087644 | DS Dime.Scheduler Management | |
| | GetSourceType | Returns the Source Type code for a given table from the DS Source Type setup. |
| | GeneratePageUrl | Generates the Page or Form Link URL for a given record and page/form which can then be sent to the TaskUrl's or ResourceUrl's in Dime.Scheduler. |
| | GeneratePageUrlWeb | Generates the Page or Form Link URL to the NAV Web Client (as of NAV 2015) for a given record and page/form which can then be sent to the TaskUrl's or ResourceUrl's in Dime.Scheduler. |
| | GenerateActionUrl | Generates an ActionUrl for the Windows client |
| | GenerateActionUrlWeb | Generates an ActionUrl for the Web client |
| | ConvertPrioritytoImportance | Converts a NAV option field with "Low, Medium, High" values to the corresponding Importance values of Dime.Scheduler. |
| | ConvertDecimaltoTime | Converts a decimal representation of a time duration to a time variable: e.g. 4,75 is converted to 04:45. NOTE: time is limited to 23:59:59. For longer durations use ConvertDecimaltoSeconds |
| | ConvertDecimaltoSeconds | Converts a decimal representation of a time duration to the number of seconds: e.g. 4,75 is converted to 17100 seconds. NOTE: there is no limit to the length of the duration in seconds. For any duration longer than 24 hours use the number of seconds as a value for DurationInSeconds of a Task. |
| | ConvertTimetoSeconds | Converts a time to the number of seconds: e.g. 4:45 is converted to 17100 seconds. |
| 2087654 | DS Finish Configuration Wizard | Executed upon completion of the FastTrack Wizard. Generates setup like Filter Groups for Resources and Tasks, Categories and Timemarkers based on the selected options. |
| 2087675 to 2087684 | DS empty n | These codeunits do not contain code and can be used to implement Dime.Scheduler at your customer or to integrate Dime.Scheduler with your vertical solution. These codeunits will never be used for the Dime.Scheduler core development so you are ensured that your modifications will not interfere with any future developments. |
---
## Connector(Install)
Every planning action in Dime.Scheduler is sent straight to the NAV web service, where
NAV's business logic processes it. To enable that return path, add a connector under
`Administration -> Connectors` in Dime.Scheduler.
See [Connection with NAV](/integrations/nav/install/ds-to-nav) for the web service URL,
authentication, and source app settings, or the [connector setup docs](/administration/connectors)
for the full reference.
---
## Establishing a connection with NAV
This completes the loop. The FastTrack Wizard connected NAV to Dime.Scheduler; this
connector entry is the reverse path. Every planning action in Dime.Scheduler is sent
straight to the NAV web service, where NAV's business logic processes it. You give the
connector two things: **the web service URL** and **how to authenticate**.
:::note[Open the connector setup]
[Production](https://app.dimescheduler.com/connectors) ·
[Sandbox](https://sandbox.app.dimescheduler.com/connectors)
:::
:::tip
This page only covers the NAV-specific bits. For everything else about connectors, see
the [connector setup docs](/administration/connectors).
:::
## Web service URL
Two Dime.Scheduler objects need to be published in the NAV web service setup. The
FastTrack Wizard adds them automatically; to do it by hand, add them yourself:

Keep the service **Name** as `Appointment`, and publish the **page** only.
## Authentication
NAV supports three methods:
- Web service access keys
- Basic authentication
- Windows auth: NTLM and Kerberos
### Web service access keys
Microsoft's [web service authentication guide](https://docs.microsoft.com/en-us/dynamics365/business-central/dev-itpro/webservices/web-services-authentication#how-to-use-an-access-key-for-soap-and-odata-web-service-authentication)
walks through obtaining an access key.
### Basic / Windows auth
Both configure the same way: in the connector's Authentication section, pick the type
and enter the credentials of a NAV **service account** Dime.Scheduler can use.

## Source App
The **source app** must match the value you set in the FastTrack Wizard back in NAV.
It's what tells Dime.Scheduler which back-office system a given resource, task or
appointment belongs to.
---
## Microsoft Dynamics NAV Connector
Connecting Dynamics NAV to Dime.Scheduler wires up traffic in both directions: NAV
sends its jobs, tasks and resources to Dime.Scheduler, and Dime.Scheduler sends the
planning you do back. This works for all supported NAV versions, including C/AL-based
(objects) Business Central installations. Anything version-specific is flagged inline.
The setup runs in three parts: four steps inside NAV, one on the Dime.Scheduler side, and two optional steps that round it off for production.
1. **In NAV.** [Import the Dime.Scheduler objects](/integrations/nav/install/objects) into NAV.
1. **In NAV.** [Update the standard objects](/integrations/nav/install/objects) for the solutions you'll use.
1. **In NAV.** [Run the FastTrack Wizard](/integrations/nav/install/nav-to-ds) to connect NAV to Dime.Scheduler.
1. **In NAV.** [Publish the web service](/integrations/nav/install/ds-to-nav) NAV exposes to Dime.Scheduler.
1. **In Dime.Scheduler.** [Configure the connector](/integrations/nav/install/ds-to-nav) so Dime.Scheduler can write back to NAV.
1. **Optional.** [Configure security](/integrations/nav/install/security) in NAV.
1. **Optional.** [Set up the job queue](/integrations/nav/install/jobqueue) to keep master data fresh.
import DocCardList from '@theme/DocCardList';
---
## Configure job queue
:::info[Optional]
This step keeps master data fresh automatically. Skip it for a quick trial; add it for
any real deployment.
:::
Master data in Dime.Scheduler can drift as it changes in NAV. To keep it in sync, add
the **`DS Scheduled Synch`** codeunit to the NAV job queue so it runs on a schedule.
You can also trigger it manually from the **DS Setup** page.
Each run synchronizes:
1. Filters, categories, time markers, and pins
2. Resources (if standard resources are enabled)
3. Work and machine centers (if enabled in production routing planning)
---
## Establishing a connection with Dime.Scheduler(Install)
import Modules from '@site/src/components/docs/nav/Modules';
The FastTrack Wizard walks you through every module and sets up the connection to
Dime.Scheduler. Find it in the Departments menu:
> Departments -> Dime.Studio -> Dime.Scheduler -> Connector
It's also on the action ribbon of the **DS Setup** page.
## Establish the connection
After the welcome screen, fill in the connection settings between NAV and
Dime.Scheduler.

- **SourceApp**: tags every record a NAV company sends to Dime.Scheduler, so
Dime.Scheduler knows which company to send the planning back to. The wizard
generates one from the company name; change it if you like. If several companies,
databases, or applications connect to the same Dime.Scheduler installation, each
needs its own unique SourceApp code.
- **API Type**: select `REST Service`.
- **Scheduler Url**: the Dime.Scheduler web service URL, the same for every company or
application on this Dime.Scheduler installation:
| Environment | URI |
| ----------- | --------------------------------------- |
| Production | `https://api.dimescheduler.com` |
| Sandbox | `https://sandbox.api.dimescheduler.com` |
- **Scheduler User**: the user you'll connect with. It must exist in Dime.Scheduler
and be a `Forms` user. Microsoft Entra ID users are not supported by the integration
services.
- **Scheduler Password**: the password for that user.
- **Connection Tested**: validate before continuing. Click `No` (or **Test Connection**
on the NAV Classic Client). On success it flips to `Yes` and the **Next** button
unlocks. On error, recheck the settings above, or ask whoever installed your
Dime.Scheduler instance for the correct ones.
## Modules
Pick what you want to plan:
Use the FastTrack Wizard for the initial setup even if you'll add customizations later.
---
## Objects
## Import Dime.Scheduler objects
Start by importing the Dime.Scheduler add-on objects into Dynamics NAV. You'll find the installation package on the [partner zone](https://www.dimescheduler.com/partnerzone). Be sure to grab the objects that match your NAV version.
Import the `*.fob` or `*.txt` file named `Dime.Scheduler_xxxx.x.x_NAVxxxxW1_AllObjects` from the installation package. These files hold all objects of the Dime.Scheduler add-on and contain no standard NAV objects, so you can import them safely.
## Update standard objects
A few FastTrack solutions rely on small changes to standard objects to round out their functionality. The footprint is tiny: only one table and one codeunit are touched, and in each, a single line of code is added to call a function in the Dime.Scheduler framework.
The [list of standard objects modified per FastTrack solution](integrations/nav/dev/fasttrack-updates.md) explains each change, so you can decide whether the modification is required or worth taking over. Most changes can be handled in other ways. For example, some buttons have been added to the menu ribbon on the resource card to send resources to Dime.Scheduler. If you'd rather not adopt that change, you can opt out and provide your own solution.
These objects ship in separate fob and txt files named `Dime.Scheduler_xxxx.x.x_NAVxxxxW1_FastTrack`. Merge the objects for the FastTrack solution(s) you want to use, or apply the modifications manually since they are so limited.
---
## Security
Give the integration user a role that matches what it needs to do. For service orders,
for example, it must be able to create service order allocations. In every case, the
user needs **full permissions on all Dime.Scheduler objects** (version `DSTW1*`).
For more on the `DSTW1*` object versions, see [Versioning and solution IDs](/integrations/nav/dev/functions).
---
## Overview(Nav)
import Modules from '@site/src/components/docs/nav/Modules';

## Installation
With your Dime.Scheduler instance up and running, connect it to Microsoft Dynamics NAV by working through these steps:
1. Install the extension in Microsoft Dynamics NAV
2. Connect NAV to Dime.Scheduler by running the FastTrack Wizard in Microsoft Dynamics NAV
3. Connect Dime.Scheduler to NAV by adding a connector in the multi-back-office setup in Dime.Scheduler that points to the desired NAV instance
4. Configure security in Microsoft Dynamics NAV
5. Configure the Job Queue in Microsoft Dynamics NAV (optional)
Together, these steps establish the following two-way workflow:
## Modules
Dime.Scheduler taps into many sources in Microsoft Dynamics NAV. While versatile and extensive, the extension doesn't compromise on usability and relies on standard solutions wherever possible. This keeps the connector lightweight and extensible, and removes the risk of breaking existing functionality.
Select a module card below to learn more about that module and start planning:
---
## Lifecycle policy(Nav)
The Dime.Scheduler extension for Microsoft Dynamics NAV follows the same lifecycle as Microsoft Dynamics NAV itself.
## Supported versions
The table below shows which on-prem versions of Microsoft Dynamics NAV the connector supports, and whether each one is still under active development.
| Version number | Supported | Active development |
| --------------------------- | --------- | ------------------ |
| 2018 | ✅ | ❌ |
| 2017 | ✅ | ❌ |
| 2016 | ✅ | ❌ |
| 2015 (R2) | ✅ | ❌ |
| 2013 (R2) | ✅ | ❌ |
| 2009 SP1/R2 Classic and RTC | ✅ | ❌ |
## Supported client types
All client types are supported:
- Windows Client
- Web Client
- Mobile Client
---
## Common actions(Modules)
Pushing data to Dime.Scheduler is the heart of the integration. In Microsoft Dynamics NAV, you do this through actions, which are added to both entity cards and lists.
The common list actions are:
- Send selection
- Send all
You'll find both list actions on these pages:
- [Resources](/integrations/nav/modules/resource)
- [Sales orders](/integrations/nav/modules/salesorder)
- [Service orders](/integrations/nav/modules/serviceorder)
- [Jobs](/integrations/nav/modules/job)
- [Assembly orders](/integrations/nav/modules/assemblyorder)
- [Work centers](/integrations/nav/modules/workcenter)
- [Absences](/integrations/nav/modules/hr)
- [Production orders](/integrations/nav/modules/production)
The common card actions are:
- Send entity
- Delete entity
## Send record
You can send a record to Dime.Scheduler at any time. If the record doesn't exist yet, it is created; if it does, it is updated.

With the default behavior, each record in the tasks grid becomes a task in Dime.Scheduler.
## Send selected or all records
To send records in bulk, open the list, click the 'More Options' button, then 'Actions'. In the Dime.Scheduler menu, choose to send the selected records or send all of them:

## Delete record
You can remove an item from Dime.Scheduler even after it has been sent. The combination of `SourceApp`, `SourceType`, and the entity's `No.` field lets the integration trace which item is stored in Dime.Scheduler.
When no appointments have been planned, this action runs safely and removes the item, along with its associated tasks, from Dime.Scheduler. If appointments have been planned, you may get an error message about the existing planning. To override this, switch off the `Check Appointment on Delete` flag on the Dime.Scheduler Setup page in NAV.

Keep in mind that removing an item from Dime.Scheduler does not remove it from NAV.
The reverse is also true: removing an item in NAV does not remove the corresponding item in Dime.Scheduler. Deleting an item in NAV without deleting it in Dime.Scheduler creates phantom jobs, and Dime.Scheduler will be unable to write back planning data for jobs that no longer exist in NAV.
---
## Assembly Order(Modules)
The Dime.Scheduler FastTrack solution for Assembly Order planning lets you schedule the resources that build an assembly order and keep the order's dates in sync. Its key features are:
- Assign multiple resources on different dates and times to a single assembly order.
- Planning resources on an assembly order in Dime.Scheduler creates or updates the resource lines on that order.
- When resources are assigned, the starting, ending and due dates can be updated from the first and last planned task. For an assemble-to-order, the shipment date on the linked sales order line is updated to match.
- Create resource lines on the assembly order in NAV before sending it to Dime.Scheduler, and those lines become planned tasks in Dime.Scheduler so you can pre-assign resources.
## Configuring assembly orders

- Select "`Enable Assembly Order planning`" to turn on the FastTrack solution.
- A planned task in Dime.Scheduler defaults to 1 hour when you drag and drop a task from open tasks onto a resource on the planning board. To use a different default, enter it in the "`Default Duration Assembly Ord.`" field.
- Select "`Update Dates`" to update the starting, ending and due date on the assembly header from the first and last date of planned tasks in Dime.Scheduler. When "`Update Dates`" is selected and the assembly order is linked to a sales order (assemble-to-order), the shipment date on the corresponding sales line is updated too.
- Select "`Send Resources with Order`" to turn resource lines created on the assembly order in NAV into planned tasks in Dime.Scheduler when you send the order over.
---
## Doc filter value sources
Document filter value sources tell NAV where and how to find the filter values for each task or resource it sends to Dime.Scheduler.
You can set default values and override them when data is retrieved from tables further down in the hierarchy. This lets you supply a value when nothing is defined in NAV, or use different values depending on whether data is present. For example, you could set "No" as the default when no skills are found and "Yes" when skills are found for a given task or resource.
Keep in mind that tasks and resources are generic terms within Dime.Scheduler and do not map to any specific NAV table. You can define multiple sources for both resources and tasks.
To manage the document filter value sources, navigate to `Departments -> Dime.Studio -> Dime.Scheduler -> Filter Groups/Filter Values`.
| Field, tab or group | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Table No. | Enter or select the table number that is the source of the task or resource. |
| Comment | Free text for internal use. |
| Entity Type | Specifies whether the table is a source for resources or for tasks in Dime.Scheduler |
| Resource Key Field | Enter or select the field that identifies the resource when entity type = resource. This is the field sent to Dime.Scheduler as `ResourceNo` |
| Job Key Field | Enter or select the field that identifies the job when entity type = task. This is the field sent to Dime.Scheduler as `JobNo` |
| Task Key Field | Enter or select the field that identifies the task when entity type = task. This is the field sent to Dime.Scheduler as `TaskNo` |
The example below shows the setup of 1 resource and 2 task entities:
docfiltervaluesources
Once the document filter value source is declared, specify the source(s) and rules for every filter group the entity has filter values for. You can link to any number of tables at any hierarchical depth. For each table link, define how records are retrieved and which filters and conditions apply.
Go to `General -> Tables (page)` or `Tables (form)`.
The hierarchy starts from the entity's base table, which you specified above and which is created automatically in the Tables page or form.
| Field, tab or group | Description |
| ------------------- | --------------------------------------------------------------------------------------------- |
| Link to Table No. | Enter or select the table number of the next table in the hierarchy. |
| Method | Specify how the table is to be read: |
| | Match: Retrieves the record with an exact match based on the links and filters. First: Retrieves the first record matching the criteria of the links and filters. Last: Retrieves the last record matching the criteria of the links and filters.All: Retrieves all records matching the criteria of the links and filters. |
For each table you can specify:
- **Links**: the field or fields that link the selected table with the parent table, for instance linking the document no. field of a line with the no. field of a header.
- **Filters**: filters that apply on the selected table to limit the number of records, for instance only retrieving records that are not blocked.
- **Conditions**: filters on the parent table that determine whether the link applies, for instance only linking the line table (the parent) with the item table (the child) when the line type of the line table is item.
- **Fields**: the fields that are the source of a filter value. You can also specify default values and override behavior.
## Links
Go to `Home -> Links (page)` or `Relationship -> Links (form)`
| Field, tab or group | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Link from Field No. | Enter or select the field from the parent table. |
| Link to Field No. | Enter or select the field from the child table to connect it with the field from the parent table (link from field no.). |
You can link any number of fields between the parent and child table.
## Filter
Go to `Home -> Filters (page)` or `Relationship -> Filters (form)`
| Field, tab or group | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Link from Field No. | Enter or select the field from the selected table, the child table, on which to apply the filter. |
| Value | Enter the filter value; this can be any value which is recognized as a filter by NAV. Option values are recognized and autocompleted. |
You can apply any number of filters on the child table. Note that all filters are applied at once.
## Conditions
Go to `Home -> Conditions (page)` or `Relationship -> Conditions (form)`
| Field, tab or group | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Link from Field No. | Enter or select the field from the parent table on which to apply the filter. |
| Value | Enter the filter value; this can be any value which is recognised as a filter by NAV. Option values are recognised and auto-completed. |
You can apply any number of filters on the parent table. Note that all filters are applied at once.
## Fields
Go to `Home -> Conditions (page)` or `Relationship -> Conditions (form)`
### Filter group
Enter or select the filter group for which to set a filter value.
### Field number
Enter or select the field that contains the filter value. This is not a mandatory field.
### Fixed value
A free text field where you can enter a fixed or default value (see also the related fields below).
### Fixed value behavior
The behavior options relate to the field number above and determine if and how the fixed value is used as the filter value.
- **None**: The fixed value is used as the filter value if no field number is set.
- **Overrule**: The fixed value always overrules the value from field number; use this to set a specific value when a record exists for field number, regardless of the value of the field number
- **Overrule when Empty**: The fixed value overrules the value from field number only when the field number is empty.
### Fixed value when not found
A free text field where you can specify a value for the filter value if no record exists for the field number. Combined with the overrule behavior of the fixed value, you can set a Yes/No or true/false (or any other value pair) depending on whether a record exists.
### Hierarchy behavior
If you have set a value for the same filter group on a higher level in the hierarchy, the hierarchy behavior options determine whether the current level overrides that value. The options below apply only if the current level returns a filter value. In other words, if a field number is defined but none of the fixed values are entered and no record is found for the current level, then the current level returns no filter value and the options below are ignored.
- **None**: The current level does not override a filter value that was set at a higher level in the hierarchy. The current level does set the filter value if no filter value was set for the filter group.
- **Overrule**: The current level always overrules a filter value that was set at a higher level in the hierarchy.
- **Overrule when Empty**: The current level overrules an empty filter value that was set at a higher level in the hierarchy.
### Filter group (form)
Enter or select the filter group for which to set a filter value.
### Filter group field number
Enter or select the field that contains the filter value. This is not a mandatory field.
### Fixed Value
A free text field where you can enter a fixed or default value (see also the related fields below)
### Fixed Value behavior
The behavior options relate to the field number above and determine if and how the fixed value is used as the filter value.
- **None**: The fixed value is used as the filter value if no field number is set.
- **Overrule**: The fixed value always overrules the value from field number; use this to set a specific value when a record exists for field number, regardless of the value of field number.
- **Overrule when empty**: The fixed value overrules the value from field number only when field number is empty.
### Fixed value when not found (form)
A free text field where you can specify a value for the filter value if no record exists for field number. Combined with the overrule behavior of the fixed Value, you can set a Yes/No or true/false (or any other value pair) depending on whether a record exists.
### Hierarchy behavior (form)
If you have set a value for the same filter group on a higher level in the hierarchy, the hierarchy behavior options determine whether the current level overrides that value. The options below apply only if the current level returns a filter value. In other words, if a Field No. is defined but none of the fixed values are entered and no record is found for the current level, then the current level returns no filter value and the options below are ignored.
- **None**: The current level does not override a filter value that was set at a higher level in the hierarchy. The current level does set the filter value if no filter value was set for the Filter Group.
- **Overrule**: The current level always overrules a filter value that was set at a higher level in the hierarchy.
- **Overrule when empty**: The current level overrules an empty filter value that was set at a higher level in the hierarchy.
---
## Filter Groups(3)
Filter groups are a core part of the Dime.Scheduler framework. The same DS Filter Group setup also defines categories, time markers and pins, so the configuration below applies to all four entities.
Creating any of these entities follows the same steps:
- Create the item
- Specify the source
- Generate the available values (filter groups only)
- Send the record to Dime.Scheduler
You can send filter groups and filter values to Dime.Scheduler manually or through a scheduled job.
## Create the filter group
Go to `Departments -> Dime.Studio -> Dime.Scheduler -> Filter Groups/Filter Values`.
| Field, tab or group | Description |
| ------------------- | ---------------------------------------------------------------------------------------- |
| Type | Select Filter group, category, time marker or pin. |
| Group | Enter the name of the entity. This name is also displayed in Dime.Scheduler. |
## Specify the source for the filter values
Go to `Navigate -> Sources (page)` or `Filter Group -> Sources (form)`.
| Field, tab or group | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Table No. | Enter or select the table number that is the source for the filter values |
| Field No. | Enter or select the field number of the field that contains the filter values. |
| Field Filter | Enter an optional filter to limit the available values (e.g. WHERE(Type=CONST(Resource),Skill Code=FILTER(*DS*))) |
## Generate the available values
Go to `Actions -> Create Filter Values (page)` or `Functions -> Create Filter Values (form)`.
You can also enter filter values manually on the filter values page or form.
## Send the filter values to Dime.Scheduler
Go to `Navigate -> Filter Values (page)` or `Source -> Filter Values (form)`.
On the filter values page or form, you can mark individual filter values to be excluded from Dime.Scheduler. This is an alternative to the field filter on the filter values sources. To exclude a filter value, enable the `Do Not Synch` field.
- To manually send the Filter Values to Dime.Scheduler:
`Actions -> Synch. All (page) or Scheduler -> Send All to Scheduler (form)`.
- To manually send a selection of Filter Values to Dime.Scheduler (pages only):
`Actions -> Synch. Selection`
- To manually send the filter values for all filter groups at once to Dime.Scheduler, from the filter groups page or form:
Go to `Actions -> Synch. All (page)` or `Scheduler -> Send All to Scheduler (form)`.
- To manually send the filter values for a selection of filter groups at once to Dime.Scheduler, from the filter groups page only:
`Actions -> Synch. Selection`
- To send filter groups, categories and time markers to Dime.Scheduler automatically, use the supplied codeunit DS Scheduled Sync.
:::tip
Use the job queue to run DS Scheduled Sync every night.
:::
---
## Filter Values(Modules)
Filter values setup is where you define the available values for each filter group, following the process described above. From here you can also set the colors for categories, time markers and pins inside NAV when using the RTC or Windows client. On the DS filter value page, click the AssistEdit button to open the color picker.

## Filter values vs doc filter values
Document filter values are the filter values that belong to a specific task or resource. They are usually a subset of the filter values, which list all the available values for a filter group.
The document filter values of a task describe the characteristics a resource needs in order to perform that task. A few examples make this concrete:
- Which skills are required from a person to carry out a task?
- What is the minimal pressure or power required from a machine?
- What minimum capacity is required for a conference room?
The document filter values of a resource describe what that resource offers:
- What are the skills of a person?
- What is the minimal pressure or power delivered by a machine?
- What is the capacity of a conference room?
---
## HR(Modules)
The Absence Registration FastTrack solution brings employee absences into your resource planning. Once you link employees to resources in NAV, you can plan their absences against those resources in Dime.Scheduler.
You can plan absences in three ways:
- Drag and drop an absence task onto the planning board in Dime.Scheduler.
- Enter an absence in NAV and send it to Dime.Scheduler as a planned task.
- Use the action shortcut (right-click on the planning board for the selected resource and date) to create an absence in NAV.
## Configuring HR absence registration
In Dime.Scheduler you plan "tasks", and every task belongs to a "job". "Job" and "task" are generic names for whatever you want to schedule. To let users drag and drop absence tasks onto the planning board, you need a "job" and "tasks" in Dime.Scheduler. Absences have no header like a sales or assembly order, so the wizard creates a default "job" for you.

- Select "`Enable Absences planning`" to turn on the solution.
- Enter a descriptive code in "`Job No. for Absences`". This code lets you sort, group and filter tasks in the open and planned Tasks grids in Dime.Scheduler.
- Select "`Create Tasks from Cause of Absence`" to create tasks in Dime.Scheduler based on the values in the cause of absence table.
- To show the type of absence on a planned task in Dime.Scheduler, pick a value for "`Show Cause of Absence as`":
- **Blank**: no visual indicator.
- **Category**: the color of the planned task shows the cause of absence.
- **Time marker**: the time marker of the planned task shows the cause of absence.
When the user sends an absence from NAV or creates one with the action shortcut, "`Create Appointment as`" controls how the planned task appears in Dime.Scheduler:
- **Working Time**: the absence uses the start and end time set in "`Starting Time`" and "`Ending Time`".
- **Full Day**: the absence is created as a 24h planned task, and the "`Starting Time`" and "`Ending Time`" fields are not used.
To let users create a new absence via the action shortcut, select "`Create New Absence Action`" and enter the caption shown to the user in Dime.Scheduler (see the screenshot below):

---
## Jobs(3)
The Dime.Scheduler FastTrack solution for jobs lets you schedule NAV jobs from the planning board. It gives you:
- A choice between planning a job as a single task or planning each job task line as its own task. The default category can be set to the job posting group.
- Two-way updates: when you plan resources for jobs in Dime.Scheduler, the matching job planning lines in NAV are created or updated automatically.
- Person-responsible filtering. Create a filter group in Dime.Scheduler based on the person responsible for the job, and the data-driven security mechanism gives each user a personalized view that shows only the jobs they are responsible for. A manager who oversees several job managers sees all of their jobs.
- Time sheet integration. When this is active, the resource's time sheet is filled out automatically from the planning in Dime.Scheduler, and it updates again whenever you change the planning.
## Configuring jobs

- To enable the FastTrack solution, select "`Enable Jobs`".
- Two checkboxes control how tasks are created:
- To create a single task in Dime.Scheduler for the job, select "`Send Job as Task`".
- To create a task for each job task line, select "`Send Tasks with Job`".
- A planned task in Dime.Scheduler defaults to 1 hour (when you drag and drop a task from open tasks to a resource on the planning board). To use a different value, enter it in "`Default Duration Job Task`".
- In "`Default Category`", choose which NAV field sets the color of a planned task when you drag and drop a task from open tasks to a resource on the planning board: select a job posting group, or leave it blank to use the default color (blue).
- Select "`Create Person Responsible Filter`" and enter the filter name that users see in Dime.Scheduler. The FastTrack Wizard sets up the "`Person Responsible Filter`" configuration for you, and NAV then sends the person responsible for each job to Dime.Scheduler.
- Select "`Plan In Gantt Chart`" to plan your jobs in the Gantt chart. The [scheduling mode](user/components/gantt.md#scheduling-mode) can be one of:
- Normal
- Fixed Duration
- Effort Driven
- Dynamic Assignment
- Select "`Update Time Sheet from Planning Board`" to create or update the resource's time sheet in NAV automatically whenever a task is allocated to a resource or a planned task is modified in Dime.Scheduler.
---
## Machine Centers(Modules)
A machine center in Dynamics NAV is the unit you actually load: the machine, line, or station that work is booked against. The connector builds Dime.Scheduler resources from the standard NAV machine center table, so the planner can see which machine is free and which is already committed, and drag routing lines onto capacity instead of guessing at it.
## Setup
In Dynamics NAV, machine centers are not a FastTrack solution of their own. They come with production routing planning. In the FastTrack wizard, select `Enable Production Routing planning`, then select `Use Machine Centers` so the connector creates resources from the standard machine center table.

See [production orders](/integrations/nav/modules/production) for the rest of the wizard setup, including how order status maps to a category or time marker.
## Usage
The connector adds a button to the machine center card (page 99000760) that sends the machine center to Dime.Scheduler as a resource. See [common actions](/integrations/nav/modules/actions) for how sending works across the connector.
Machine centers are also refreshed by the scheduled master data sync. Each run of the `DS Scheduled Synch.` codeunit sends resources, filter groups, filter values, work centers and machine centers, provided production routing planning is enabled. See [job queue](/integrations/nav/install/jobqueue) for how to schedule it.
---
## Overview(3)
To link Dynamics NAV to Dime.Scheduler, the connector adds a set of new objects and makes a few small changes to existing ones. The changes to existing objects are limited to extra buttons and fields, and leave standard NAV functionality untouched.
The pages that follow walk you through the new functionality the connector brings. Most of these additions are for internal use, so you don't need to know how they work unless they were covered in earlier sections.
## New artifacts
| Name | Type |
| ----------------------------- | ------ |
| Appointments | Table |
| Appointment card | Card |
| Appointment field values | Table |
| Appointment resources | Table |
| Source types | Table |
| Document filter value sources | Table |
| Filter groups | Table |
| Filter values | Table |
| Send resource capacity | Action |
| Send resource group capacity | Action |
## Updated artifacts
| Name | Type |
| ------------------------ | ------ |
| Send record | Button |
| Repair status | Field |
| Job planning line budget | Field |
---
## Production Orders(Modules)
Plan production order routing lines on the Dime.Scheduler planning board, with drag and drop and the full NAV business logic behind it. The FastTrack solution for Production Order - Routing Lines gives you:
- Visualize and plan routing lines via drag and drop, using and respecting all the NAV business logic.
- Resources in Dime.Scheduler created from the standard NAV work center and machine center tables.
- Automatic updates to a planned task's category or time marker when the order status of the production order changes.
## Configuring production orders - routing lines

Set up the solution in the wizard:
- To use the FastTrack solution, select "`Enable Production Routing planning`".
- To create resources in Dime.Scheduler from the work center and machine center tables, select "`Use Work Centers`" and "`Use Machine Centers`" (recommended).
- In "`Show Order Status as`", choose how the planned task(s) in Dime.Scheduler update when the order status of the production order in NAV changes. You can update the category or the time marker. Leave it blank if you don't want the order status to show.
---
## Repair Status
Lock appointments in the planning board based on an order's status in Dynamics NAV. The standard NAV **Repair Status Setup** page has been extended with one new field, **Lock Appointment**. Enable it for a status, and any order in that status has its planning fixed so it can no longer be moved.

---
## Resources(3)
This solution creates resources in Dime.Scheduler from the standard NAV Resource table. Most other FastTrack solutions rely on these standard resources, the exception being production routing planning, which uses work centers and machine centers instead.
The options in DS Setup and the FastTrack Wizard (shown in the screenshot below) let you define how to create groups and teams. Because most FastTrack modules depend on standard resources, we recommend selecting "Use Standard Resource table".

## Resource type
A resource type is any property you use to categorize resources. Terms like rooms, vehicles, and machines are good candidates. It is a simple way to organize your resources and is especially useful on the planning board, where you can filter on it. Users can even create a planning board per resource type.
The FastTrack wizard lets you set up resource types from MS Dynamics NAV rather than in Dime.Scheduler directly (which is also possible):

The options in the "Resource Type From" selection list are:
- **Default**: the resource type will be "Resource" for all resources.
- **Resource Group**: the resource type will be the value of the resource group you have selected for the resource in MS Dynamics NAV.
- **Field**: select any field (including any custom fields that may have been added) from the resource table. For every value of the selected field the resource type will be the field value you have selected for the resource in MS Dynamics NAV.
- Select the field in "Resource Type Field"
- **Dimension**: select a dimension, for every value of the selected dimension the resource type will be the dimension you have selected for the resource in MS Dynamics NAV.
- Select the dimension in "Resource Type Dimension".
> ℹ️ The resource type setting can be modified at any time. The modification takes effect the next time you send the resources to Dime.Scheduler. Any planned data remains in place as each resource has a unique identifier regardless of its resource type. Any changes made to the resource types in Dime.Scheduler will be overridden by the settings in MS Dynamics NAV.
## Department
A department is a generic term in Dime.Scheduler that allows you to sort and group resources on the planning board (among other components such as the planned tasks grid).

Options for "Department from" are:
- **Blank**: the value for department is empty for all resources
- **Resource Group**: resources are grouped in departments according to their assigned resource group
- **Field**: select any field (including any custom fields that may have been added) from the resource table. Resources are grouped according to the value assigned in the field.
- Select the field in "Department Field"
- **Dimension**: select a dimension, resources are grouped according to the value assigned in the default dimension.
- Select the dimension in "Department Dimension"
> ℹ️ The department setting can be modified at any time. The modification takes effect the next time you send the resources to Dime.Scheduler. Any changes made to the departments in Dime.Scheduler will be overridden by the settings in MS Dynamics NAV.
## Team
Teams give you another way to sort and group resources in Dime.Scheduler. Teams carry more functionality in MS Dynamics NAV (for example, assigning the role of a team member), but the FastTrack modules limit teams to sorting and grouping.

The options for "Team from" are:
- **Blank**: the value of team is empty for all resources
- **Resource Group**: resources are grouped in teams according to their assigned resource group
- **Field**: select any field (including any custom fields that may have been added) from the resource table. Resources are grouped according to the value assigned in the field.
- Select the field in "Team Field"
- **Dimension**: select a dimension, resources are grouped according to the value assigned in the default dimension
- Select the dimension in "Team Dimension"
> ℹ️ The team setting can be modified at any time. The modification takes effect the next time you send the resources to Dime.Scheduler. Any changes made to the teams in Dime.Scheduler will be overridden by the settings in MS Dynamics NAV.
## Resource Filters
Resource filters are a powerful mechanism for finding the right resource for a task. They also drive the data-driven security model, where they define segregation of roles and responsibilities. The filter options for standard resources are mainly used in service management, but they apply to other areas too. You can define additional filter groups in the `DS Filter Groups` setup outside of the FastTrack Wizard.

- Enable "Create Skill Filter" to create the necessary configuration in NAV to filter resources based on their skills.
- Enter the name the users will see in Dime.Scheduler for this filter in "Skill Filter Name".
- Enable "Create Service Zone Filter" to create the necessary configuration in NAV to filter resources based on the service zones in which they operate.
- Enter the name the users will see in Dime.Scheduler for this filter in "Service Zone Filter Name".
> ℹ️ Enable these filters when you use service management. Enabling one or both makes the FastTrack Wizard create the configuration in NAV needed to send the required filter values along with service orders, so users can rely on the automatic filter options in Dime.Scheduler.
Resources that are "Blocked" in NAV are still created in Dime.Scheduler, but they are hidden from users. Blocking or unblocking a resource in NAV updates the "Do Not Show" setting in Dime.Scheduler.
---
## Capacity(Modules)
:::info[Status: Preview]
Resource capacity sync is currently in preview. Functionality and behavior may change between releases. Not recommended for production yet.
:::
Send resource availability from Dynamics NAV to Dime.Scheduler. Next to the standard `Resources` table, Dime.Scheduler reads the standard `Resource Capacity` matrix to create capacity entries, which drive the [capacity component](user/components/pivot.md).
You can reach the resource capacity matrix from a few places, so there are also a few ways to push capacity to Dime.Scheduler.
## Resource card
The Actions menu of the resource card offers two Dime.Scheduler actions: send the resource to Dime.Scheduler, and send the resource's capacity matrix to Dime.Scheduler.
When you send capacity, enter a start and end date so Dime.Scheduler knows how many capacity entries to generate. The resource filter in this window is set for you automatically.

## Capacity matrix
To generate capacity entries for several resources at once, use the resource capacity matrix instead. Click the `Send Capacity` button in the Dime.Scheduler submenu of the matrix:

## Read more
- [Common actions](/integrations/nav/modules/actions)
- [FastTrack for resources](/integrations/nav/modules/resource)
---
## Group Capacity(Modules)
:::info[Status: Preview]
Resource group capacity sync is currently in preview. Functionality and behavior may change between releases. Not recommended for production yet.
:::
Send capacity for a whole resource group in one action instead of resource by resource. Alongside the standard `Resources` table in Dynamics NAV, Dime.Scheduler uses the standard `Resource Group Capacity` matrix to create capacity entries, which surface in the [capacity component](user/components/pivot.md).
This works much like resource capacity, with one advantage: you generate data in bulk and apply the same capacity setup to every resource in a group.
To do this, select the resource group you want to update and click the `Send Capacity` button:

A window pops up and prompts you for a start and end date. The 'Resource Group' is set automatically to the group you selected in the matrix:

Click OK to generate the capacity entries for every resource that belongs to this resource group.
---
## Resources(4)
## Description
Dime.Scheduler builds its resources from the standard NAV Resource list. On top of the usual fields like name, unique number, type and address, two new fields are added specifically for Dime.Scheduler:
- Enable Gps Tracking
- Enable Exchange Synchronisation
You'll find these fields at the bottom of the resource card:

For more on what these fields do, see the [resource settings manual](administration/settings/resources.md#gps-tracking-and-exchange-integration).
## Send resource to Dime.Scheduler
You can send a resource to Dime.Scheduler at any time. If the resource doesn't exist yet, it's created; otherwise it's updated. As in NAV, the `No` field is used for that check.
On the resource card, open the 'Actions' menu and expand the Dime.Scheduler submenu:

Choose 'Send resource' to transfer the resource to Dime.Scheduler right away.
## Send resources to Dime.Scheduler
To send resources in bulk, open the resource list. Click 'More Options', then 'Actions'. The Dime.Scheduler menu lets you send the selected records or send all of them:

---
## Sales Orders(Modules)
Plan resources directly against sales documents. The FastTrack solution for sales document planning lets you:
- Assign multiple resources on different dates and times to a single sales document.
- Create or update resource lines on the sales document as you plan those resources in Dime.Scheduler.
- Update the document's selected dates based on the first and last planned task.
- Pre-assign resources: if you add resource lines to the sales document in NAV before sending it to Dime.Scheduler, those lines become planned tasks in Dime.Scheduler.
## Configuring sales documents

- Select "`Enable Sales planning`" to turn on the FastTrack solution.
- Select "`Send Sales Header as Task`" to create a single task in Dime.Scheduler for the sales document.
- Select "`Send Tasks with Sales Header`" to create a task for each sales line.
> ℹ️ By design, only sales lines of type item are sent to Dime.Scheduler, however this can be modified if required.
- A planned task in Dime.Scheduler defaults to 1 hour when you drag and drop it from open Tasks onto a resource on the planning board. Enter a different value in "`Default Duration Sales Task`" to override this default.
- Select "`Send Resources with Order`" if you want resource lines entered on the sales document in NAV to become planned tasks in Dime.Scheduler when the document is sent.
- Select "`Create Resource Lines`" if you want resource lines to be created and updated on the sales document based on the planned tasks in Dime.Scheduler. This differs from assembly order planning, where resource lines are always created or updated.
- Use "`Update Date`" to choose which date on the sales document is updated based on the planned tasks in Dime.Scheduler. This is only applicable when "`Create Resource Lines`" is selected.
The options are:
- **Blank**: no date is updated
- **Shipment Date (H)**: on the Sales Header is updated
- **Promised Delivery Date (H)**: on the Sales Header is updated
- **Shipment Date (L)**: on the Sales Line is updated
- **Planned Shipment Date (L)**: on the Sales Line is updated
- **Planned Delivery Date (L)**: on the Sales Line is updated
> ℹ️ Updates are validated so other dates might be updated as well.
---
## Service Orders(Modules)
Plan Dynamics NAV service orders on the Dime.Scheduler planning board. The FastTrack solution for service orders gives you these key features:
- Plan a service order as a single task, or plan each of its service item lines as a separate task. The default category and time marker of a task can be drawn from the service order type, the service line repair status or the service zone.
- When you allocate resources in Dime.Scheduler for service orders or service item lines, the service order allocation in NAV is updated.
- Allocate multiple resources to the same service item line on the same date. When the repair status changes on the service item line, the color or time marker of the planned task in Dime.Scheduler can update automatically.
- With the time sheet integration active, the resource's time sheet is filled out automatically from the planning in Dime.Scheduler. Change the planning and the time sheet updates with it.
## Configuring service orders

- Select "`Enable Service Management`" to turn on the FastTrack solution.
- Two checkboxes control how tasks are created:
- To create a single task in Dime.Scheduler for the service order, select "`Send Service Header as Task`".
- To create a task for each service item line, select "`Send Lines with Service Header`".
- A planned task in Dime.Scheduler defaults to 1 hour when you drag and drop a task from open tasks to a resource on the planning board. To use a different value, enter it in the "`Default Duration Service Line`" field.
- "`Default Category`" sets which NAV field colors a planned task when you drag and drop it from open tasks to a resource on the planning board. Choose between service order type, repair status (from the service item line) and service zone. Leave it blank to use the default color (blue).
- "`Default Timemarker`" sets which NAV field colors the time marker on a planned task when you drag and drop it from open tasks to a resource on the planning board. Choose between service order type, repair status (from the service item line) and service zone. Leave it blank to skip time markers altogether.
- "`Show Repair Status as`" controls how the planned task(s) in Dime.Scheduler update when the repair status on the service item line in NAV changes. Choose to update the category or the time marker. Aligning this with the default category and default time marker settings is sensible but not required. For example, set service order type as the default category and then update the category when the repair status changes: the color shows the order type until the repair status changes, then reflects that status.
- Select "`Update Time Sheet from Planning Board`" to create or update the resource's time sheet in NAV automatically whenever a task is allocated to a resource or a planned task is modified in Dime.Scheduler.
The FastTrack Wizard creates the configuration for the "`Skill Filter`" and "`Service Zone Filter`" automatically if they were enabled in the standard resource section. NAV then sends the skills a resource needs (based on the service item and the service zone of the service order) to Dime.Scheduler, where you can use the automatic filter option to pick the right resource for the task.
---
## Source Types(Modules)
Source types tell Dime.Scheduler which area of NAV a task came from. That value travels back with the planning data, so NAV can route each task through the right business logic.
For example, when a task is planned for a service item line, you process the planning data into resource allocations in NAV. When a task is planned for a custom table, you typically process the planning data into a custom table as well.
A single NAV company can feed multiple sources at once. You might send service tasks, job tasks, sales tasks and custom tasks from one company. Give each task type its own source type, and the planning data returning from Dime.Scheduler is handled by the correct logic in NAV.
To start configuring source types, navigate to `Departments -> Dime.Studio -> Dime.Scheduler -> Filter Groups/Filter Values`.
| Field, tab or group | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Source Table No. | Enter the table number or select it from the list for each table that is sent to Dime.Scheduler (e.g. 5901, Service Item Line or 1001, Job Task) |
| Source Type | Fill in the source type; this is a free text field (e.g. SERVICE or JOB) |
| Processing Codeunit No. | Enter the number of the codeunit that processes planning data coming from Dime.Scheduler into NAV |
| Appointment Template Code | In Dime.Scheduler you can define custom fields on an appointment card. You can have different fields based on the type of task using an appointment template. Specify the appointment template code for the task, based on the source type. For more information about Appointment Templates refer to the [administration manual](administration/settings/appointmenttemplates.md). |
| Action Report No. | The report to run when the user selects an action URI for this source type. Leave 0 (zero) to use the standard report DS Action Launch. The report parses the parameters passed by Dime.Scheduler as field filters and then launches the defined codeunit (see below) |
| Action New Description | Action new is the URI shown when the user opens the context menu in the planning board. The description or caption is shown to the user. |
| Action New Codeunit No. | The codeunit executed by the report defined in Action Report No. |
| Action Edit Description | Action Edit is the URI shown when the user opens the context menu of an appointment with matching source app and source type fields. The description or caption is shown to the user. |
| Action Edit Codeunit No. | The codeunit executed by the report defined in Action Report No. |
The "Send Action URI" action generates and sends the ActionURI to Dime.Scheduler for both 'Action New' and 'Action Edit', as long as a value is entered in the matching description field.
---
## Time Sheets(Modules)
Push planned tasks from Dime.Scheduler straight into the NAV time sheet. This integration is available for the following modules:
- [Service orders](/integrations/nav/modules/serviceorder)
- [Jobs](/integrations/nav/modules/job)
To enable this feature, open the FastTrack wizard, enable the module of interest, and tick the time sheet checkbox for that module.
Once enabled, planning on the planning board no longer only results in new information such as resource allocations or job planning lines - it also creates the matching time sheet entries for the associated resource. When the planning is later modified, the time sheet is updated automatically. For planning that already existed before the feature was enabled, nothing is done by default.
This is all standard NAV functionality: Dime.Scheduler fills out the same time sheet lines a user would otherwise enter by hand.
---
## Work Centers(Modules)
In manufacturing, the work center is where the work actually happens, so a planner scheduling production needs those work centers on the planning board as something they can drop jobs onto. The connector builds Dime.Scheduler resources from the standard NAV work center table, and from that point scheduling against a work center is no different from scheduling against a person.
## Setup
In Dynamics NAV, work centers are not a FastTrack solution of their own. They come with production routing planning. In the FastTrack wizard, select `Enable Production Routing planning`, then select `Use Work Centers` so the connector creates resources from the standard work center table.

See [production orders](/integrations/nav/modules/production) for the rest of the wizard setup, including how order status maps to a category or time marker.
## Usage
The connector adds a button to the work center card (page 99000754) that sends the work center to Dime.Scheduler as a resource. See [common actions](/integrations/nav/modules/actions) for how sending works across the connector.
Work centers are also refreshed by the scheduled master data sync. Each run of the `DS Scheduled Synch.` codeunit sends resources, filter groups, filter values, work centers and machine centers, provided production routing planning is enabled. See [job queue](/integrations/nav/install/jobqueue) for how to schedule it.
---
## Modules(Nav)
import Modules from '@site/src/components/docs/nav/Modules';
The Dime.Scheduler FastTrack for Microsoft Dynamics NAV gives you fully functional, out-of-the-box solutions across multiple areas of standard Microsoft Dynamics NAV.
The wizard walks you through the setup and generates all the configuration you need, so you can start planning right away.
Select a module card above to learn more about it and start planning.
## Compatibility matrix
Each FastTrack module is available only on the NAV versions that include the underlying base functionality. For example, Assembly Orders aren't part of NAV 2009, so the FastTrack solution for Assembly Order planning isn't available there either.
| Solution | 2009 R2 | 2013 | 2013 R2 | 2015 | 2016 | 2017 | 2018 | BC |
| --------------------------- | ------- | ---- | ------- | ---- | ---- | ---- | ---- | --- |
| Standard resources | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Service order planning | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| - Time sheet integration | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Job planning | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| - Time sheet integration | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Assembly Order planning | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Production Routing planning | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Sales planning | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Absence registration | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
---
## Requirements(Nav)
Before you set up the Dynamics NAV connector, make sure the NAV environment meets these two requirements:
- **SOAP web services** must be enabled.
- **WebDAV** must be disabled.
---
## Troubleshooting(Nav)
import AreaTroubleshootingLanding from '@site/src/components/AreaTroubleshootingLanding';
The most common questions about the two-way message flow between Dime.Scheduler and
NAV, grouped by what you're seeing.
## NAV to Dime.Scheduler
The first thing you do on a new Dime.Scheduler instance is run the
[FastTrack Wizard](/integrations/nav/install/nav-to-ds) in NAV. Work through every step
carefully.
The first step is also the most common place to get stuck: it's where you set up the
flow to Dime.Scheduler, with the environment and the credentials of a Dime.Scheduler
Forms user.
Watch out for these:
- An account used in more than one tenant.
- A real person's account instead of a service account. The day that person resets
their password, the integration breaks. Use a service account like
`NAVSync@YourCustomerDomain.com`.
- Don't use any domain managed by Dime Software, either: that's a security risk.
- It must be a **Forms** account on a domain your customer controls. MS Entra ID
accounts aren't supported by the API.
- Pick the right **environment** (sandbox or production), and make sure the user exists
in the instance on that environment.
## Dime.Scheduler to NAV
The flow from NAV to Dime.Scheduler is easy to set up, thanks to the FastTrack Wizard
and the connector. The return flow is trickier, because it depends on how that
particular NAV is set up. Two things can fail: Dime.Scheduler can't reach NAV, or NAV
receives the appointment but can't process it.
### Appointments don't show up in NAV
Start at the [transactions](/administration/transactions/). Open them from the sidebar,
or right-click the appointment on the planning board and choose **View transactions**.
That opens the appointment's detail page: basic info, the associated connector (if any),
and every transaction that tried to write back to NAV, Exchange, or a webhook.

The transactions usually tell you exactly what went wrong. Common causes:
- **No connector** for this item. Data sent from NAV carries a Source App identifier. To
send a planned appointment back, Dime.Scheduler looks for a connector with the same
Source App. No match, nothing sent.
- **Invalid or incomplete connector**. Usually the wrong URL or credentials.
- **Back-office unavailable**: the administrators need to make the NAV SOAP URL reachable
from Dime.Scheduler.
### Appointments couldn't be processed in NAV
When Dime.Scheduler drops the appointment into NAV, the transaction is marked successful
on the Dime.Scheduler side. That doesn't guarantee NAV processed it.
In NAV, open the **Dime.Scheduler Appointments** table. It lists the transactions and
their status. Anything not marked **Processed** went wrong: click the status column for
details.

---
## Creating appointments
Schedule work from a Power Automate flow by creating an appointment in Dime.Scheduler. Before you can do this, the job and task must already exist. For the reasoning behind that, see [from job to appointment](user/basic-concepts.md#from-job-to-appointment). To create jobs and tasks from the Power Platform, see the [previous article](/integrations/powerplatform/actions/tasks).
An appointment ties three things together: a job (the equivalent of a project), a task (the equivalent of an activity within that project), and a resource. In other words, an appointment assigns a task to a resource to be carried out at a specific date and time.
The action on the Power Platform makes this clear:

Every field is documented in the [developer manual](/develop/api/appointment#upsert-appointment).
---
## Using the Power Platform connector
Use the certified Dime.Scheduler connector in any Power App, Power Automate flow, or Logic App. For the complete list of supported actions, see the [Microsoft connector reference](https://learn.microsoft.com/en-us/connectors/dimescheduler/).
## Power Automate Flows
You'll find the connector in the 'Premium' section:
Selecting the connector shows all the actions you can run:
## Power Apps
The formula bar lets you invoke the same actions as in Power Automate flows. The formula below creates a new task for order 'POWERAPP_001':
```powerfx
'Dime.Scheduler'.Task(
{
'ds-append': true,
sourceApp: "POWERAPPS",
sourceType: "POWERAPPS",
jobNo: "POWERAPP_001",
taskNo: ThisRecord.'Task No'.Value,
shortDescription: ThisRecord.Description.Value,
description: ThisRecord.Description.Value
});
```
Power Apps intellisense makes it easy to find the right action:
---
## Creating resources
A resource is a plannable entity in Dime.Scheduler. The 'Resource' action creates these resources from any source you connect through the Power Platform, and every field you populate in the action flows straight into Dime.Scheduler:

You can manage the following categories of information for a resource:
- Identifiers
- Names
- Groups (department, team, types)
- Contact details
- Address info
- Free fields
- Links
- Visual indicators
Every field is documented in the [developer manual](/develop/api/resource#resource). The `Resource No.` field is mandatory, and it is what links the same entity across business systems. When you later schedule a task for this resource and want to process that appointment in your own application, the `Resource No.` lets you trace each scheduled appointment back to the resource you created in Dime.Scheduler.
You can manage other attributes too, such as resource filters, types, certifications, capacity, and live locations. The rule of thumb: a `Resource No.` must be specified to assign any of these attributes to the resource in the database.
## Related entities
Beyond the master record, separate actions let you append or remove the following related entities:
- Resource calendars
- Resource filter values
- Resource actual location
- Resource capacity
- Resource URI
---
## Creating tasks
To create a task from the Power Platform, you need both a *job* and a *task* in Dime.Scheduler. For the reasoning behind this, see [From job to appointment](user/basic-concepts.md#from-job-to-appointment).
Every task references a job, which is roughly the equivalent of a project or an order. The job holds customer info, contact details, addresses, and so on. A task is the lowest unit of work to be carried out, and the job is finished once all its tasks are complete.
The job holds the general information about the project:

The task holds the details about the actual work to be done:

Every field is documented in the [job](/develop/api/job) and [task](/develop/api/task) API references. The task action also includes a shortcut flag called `Create job`. For simple tasks that don't need the full set of fields exposed at the job level, this flag takes a few fields and creates the job for you. A job is still created, just implicitly and automatically.
---
## Tutorial: Booking
This tutorial shows you how to make a Microsoft Booking appointment appear in Dime.Scheduler. You'll go one step further than the usual planning process: instead of routing the work through the open task list, you'll push the appointment straight onto the planning board.
## Step 1: What constitutes a task
Dime.Scheduler is all about planning resources and tasks. Both concepts can refer to anyone or anything: a resource is an entity that can carry out work, while a task is the unit of work that a resource needs to execute.
Booking appointments shift the picture slightly, because the planning has already happened. To keep a complete view of your workforce's schedule, you can link the appointments generated in Booking back to Dime.Scheduler, and that is exactly what you'll do here.
Like the other tutorials in this section, this example is deliberately simple. The task data model in Dime.Scheduler handles far more complex business scenarios because it has two levels: **jobs** and **tasks**. A job is the high-level entity, best compared to a project: a collection of activities (the **tasks**) that need to be completed to reach an outcome. Jobs bundle common information that applies to all their tasks, such as customer info, billing details, and address. For the full list of a job's properties, see the [API reference](/develop/api/job). A task is the actual unit of work to be carried out. Every task belongs to a job and uniquely identifies an item of work that lands in the open task list and, once scheduled, on the planning board. For the full list of a task's properties, see the [API reference](/develop/api/task).
This data model gives you many ways to organize work. In this tutorial you'll use a demonstration booking page, so it makes sense to group every instance of this booking type (a service, in Booking terms) under the same job. Demonstrations are typically free of charge, so there's no need for a more granular design.
This use case introduces a third entity: the [appointment](/develop/api/appointment). An appointment assigns a resource to a task on a given date and time. Put simply: `Appointment = Task + Resource + Date & Time`. By calling this API in the Power Platform connector, you can push appointments directly onto the planning board.
## Step 2: Create a new flow
Start with your booking page. This isn't a tutorial on building a Booking page, but there is one crucial piece of information you'll need to continue.
Once the page exists, several places let you copy its link. It typically looks like this:
`https://outlook.office365.com/owa/calendar/DimeSchedulerdemo@dime-software.com/bookings/s/s_umXVExTECHTgc3QyIybw2`
Copy the email address from this URI. You'll use it in the trigger of a new flow in Power Automate. Choose the 'When a appointment is Created' trigger and paste the email address into the booking page address field:
## Step 3: Inserting appointments
As noted above, a plannable task in Dime.Scheduler has two levels: jobs and tasks. A job holds the high-level properties such as customer and billing info, while a task concerns only the work to be performed. To make a Booking appointment available in Dime.Scheduler, you'll invoke two actions in the flow: create a job, then create a single task for that job. Expanding either action reveals an extensive list of fields you can use to shape Dime.Scheduler's behavior. For the details, see the [Job API reference](/develop/api/job) and [Task API reference](/develop/api/task).
For most actions in the Dime.Scheduler connector for the Power Platform, the first field is a Yes/No field that asks whether to append or remove the record. Append means create or update, and it's idempotent: if a record already exists with that external identifier, it gets updated. When set to false, Dime.Scheduler attempts to remove the record matching the external identifier (`JobNo` or `TaskNo`) you provide. For this tutorial, set append to true.
The Power Platform lets you click your workflows together. Inside an action, Power Automate shows you which fields are in scope. Find the right field in Dime.Scheduler and populate it with the info a Booking appointment provides:
For the source app and source type fields, we've set static values that apply to all data passing through this flow. That way, every Booking appointment that flows through is marked with a Booking item as its origin and a generic 'Demo' type. If your booking page has multiple services, you can use these fields to distinguish between the different service types in Dime.Scheduler as well.
When creating a task, provide a unique external identifier in the `TaskNo` field and a reference to its job using the job's unique external identifier in the `JobNo` field. The `Job No` field must therefore hold the same value in both actions (and so must the Source App and Source Type fields).
A Booking appointment carries a lot of information. This tutorial uses a 1:N relationship between a job and its tasks, so customer information belongs on the task level. (On the job level it would be overwritten every time a new prospect books a meeting, and you'd lose data.) If you'd rather track each appointment individually, use a 1:1 approach instead, which lets you place customer information on the job level.
The final step creates the appointment. This action resembles the others: specify the source app, source type, job no, and task no so the application knows which task the appointment belongs to. These fields should reference the same values you used in the previous steps. On top of that, specify a start and end time and the resource number assigned to the Booking appointment:
Save the flow and test it by creating a new Booking appointment.
To check the status of the flow, go to its 'Run history' section and open the most recent run:
Open the planning board, and you should see a new appointment that holds the exact same information as the Booking appointment. If nothing appears, check the status of the step in the Power Platform, or the logs in Dime.Scheduler.
Congratulations, you've just added a new appointment to Dime.Scheduler!
---
## Tutorial: Excel
import DocCardList from '@theme/DocCardList';
Use **Excel files** as a data source for Dime.Scheduler. In this tutorial you will:
- Set up the connector
- Create resources and work items from simple Excel sheets
- Write the planning results back to your Excel sheets once scheduling is done
This is a small example of what the Power Platform and Dime.Scheduler can do together, and how easily the two combine to handle your scheduling needs.
---
## Part 2: Resources
In this part you take a list of resources stored in an Excel file and make them available in Dime.Scheduler.
:::info
This is the second post in the guide on how to integrate Dime.Scheduler with Microsoft Excel through the Power Platform:
- Part 1: Setting up the connector
- **Part 2: Resources**
- Part 3: Tasks
- Part 4: Processing appointments
:::
By the end, you will know how to:
- Use the resources action in the Power Platform connector
- Create and manage resources in Dime.Scheduler
- Schedule resources in the planning board
## Step 1: A first look at the data
This tutorial shows how a simple Excel file becomes a powerful data source for scheduling. The Excel file is stored in a SharePoint library, which makes it easy for the Power Platform to connect to. The file contains a list of employees we want to schedule, in the 'Resources' sheet:
The work that needs to be done is listed in the 'Events' sheet:
These two sheets demonstrate the two dimensions of Dime.Scheduler's data model. Everything revolves around the actor that carries out the work (the `Resource`) and the work itself that needs to be carried out (the `Task`). In this example, the employees are the resources and the office events are the tasks.
:::tip
This example is deliberately simple. As long as the Power Platform can tap into your data source of choice, whether that is a SharePoint list or service cases in Microsoft Dynamics CRM, you can use it to feed information into Dime.Scheduler. Think of the Power Platform as a toolbox that connects your application suite to Dime.Scheduler.
:::
This part focuses on resources and how to make them available in Dime.Scheduler. The next part does the same for the work that needs to be planned.
## Step 2: What constitutes a resource
A resource is someone or something that can be scheduled. That is as specific as the definition gets, which makes it an open-ended and flexible concept. You might plan consultants, meeting rooms, and vehicles all at once in the same tool. You decide what constitutes a resource.
In this tutorial, the resources are employees of the company Dunder Mifflin Inc. You could account for many dimensions when planning employees, such as seniority, expertise, languages, and region. But since the goal here is to showcase the connectivity between the Power Platform and Dime.Scheduler, we keep it simple and track only their name, department, and job title, as shown in the Excel sheet above.
Dime.Scheduler's `Resource` entity contains many fields and references to other entities, as you can see in the [API reference](/develop/api/resource). Because we have only a handful of fields in the Excel sheet, we need just a small subset of the available Dime.Scheduler fields:
| Employee field (Excel) | Resource field (Dime.Scheduler) | Comment |
| ---------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Number | ResourceNo | The `ResourceNo` field serves as an external text identifier that can be traced back to a single entity. |
| Name | DisplayName | |
| Type | Department | |
| Title | FreeText1 | There is no field for a job title, but we can use one of the `FreeText` fields. |
| N/A | SourceApp | We will use a fixed value to indicate that the original data source is the Excel file in the SharePoint library. This is particularly useful in situations where you manage resources from multiple back-office systems. |
| N/A | SourceType | We will use a fixed value to indicate that the original data source is the 'Resources' sheet in said Excel file. |
| N/A | ResourceType | Since we only will plan employees, we can use a static value. |
:::info
Many entities in Dime.Scheduler have a field named after the entity, followed by 'No'. For example, `ResourceNo`, `TaskNo` and `JobNo` all create a link between the records stored in the database of the original system and those in Dime.Scheduler. This link is crucial when you want to write schedule information back after planning a resource or task.
The `SourceApp` and `SourceType` fields matter too when you schedule resources and work items from various data sources. `SourceApp` identifies the underlying back-office system (in this case the Excel file), while `SourceType` represents the underlying data set (in this case the 'Resources' Excel sheet).
:::
## Step 3: Create a new flow
Create a new flow in Power Automate. To keep things simple, create one you trigger manually:
In the modal window, give the flow a name and select **'Manually trigger a flow'**:
The Flow builder opens with the trigger already on the canvas. Now the real work begins.
## Step 4: Looping through each record in the Excel sheet
One advantage of storing the Excel file on Office 365 is that the Power Platform can not only access the file, but also read and update its rows and columns. In this example, we want to send over the entire list in the 'Resources' sheet, and one command runs an action against every item it finds.
Add a step to the flow and look for the action 'List rows present in a table' in the Excel Online connector. The action is straightforward: tell it where to find the Excel file and which table to read:
With the list in hand, iterate through each record in the table using the 'Apply to each' action. For each record, we want to add a resource in Dime.Scheduler. Inside the 'Apply to each' section, add the action 'Add, update or remove a resource' from the Dime.Scheduler connector:
In the output field of the loop action, select `value`.
## Step 5: Inserting resources
When you expand the resource action, you'll find an extensive list of fields that control the behavior of Dime.Scheduler. For more detail, see the [API reference](/develop/api/resource).
For most actions in the Dime.Scheduler connector for the Power Platform, the first field is a Yes/No field that asks whether to append the record or remove it. To append is to create or update a record, and it is idempotent: if a record already exists with that external identifier, it is updated. When set to false, Dime.Scheduler attempts to remove the resource with the resource number you provide in the `ResourceNo` field. For this tutorial, set append to true.
What makes the Power Platform so effective is that you can click your workflows together. Inside an action, Power Automate shows you which fields are in scope. Using the mapping table above, find the right field in Dime.Scheduler and populate it with the columns from the Excel sheet:
For the source app, source type, and resource type fields, we added a static value that applies to all data passing through this flow. For each resource in the Excel sheet, we then know that its original data source is a SharePoint library and that it comes from the 'Resource' sheet. The resource type field distinguishes between different types of resources. You only need to specify a name, and Dime.Scheduler creates the type while appending the resource. We've kept it simple and used the default value that ships when you deploy Dime.Scheduler.
Save the flow, hit the Test button in the top-right corner, select 'Manually', and click the Test button at the bottom of the fly-in window.
To follow the status of the flow, go to its 'Run history' section and click the last run:
When all goes well, you will see the list of resources in the planning board:
If nothing appears, check the status of the step in the Power Platform, or the logs in Dime.Scheduler.
:::tip
To change the caption of the 'Free Text 1' field, go to Settings -> Localization, find the FreeText1 field on the 'Resource' source table, select it, and add a caption in your language. The application will then display something more meaningful, such as 'Job title' instead of the field name 'FreeText1'.
:::
Congratulations, you've added resources to Dime.Scheduler and are ready to schedule them. You can already create manual appointments on the planning board. It takes two to tango, though, so the next part focuses on the list of work that needs to be planned.
---
## Part 1: Setting up the connector
import { FaSlidersH } from 'react-icons/fa';
This first part has a single goal: connect the Power Platform to Dime.Scheduler so you can build flows against it.
:::info
This is the first post in the guide on integrating Dime.Scheduler with Microsoft Excel through the Power Platform:
- **Part 1: Setting up the connector**
- Part 2: Resources
- Part 3: Tasks
- Part 4: Processing appointments
:::
By the end of this part, you will know:
- The prerequisites
- How to obtain an API key
- How to create a connection in the Power Platform
- How to test the connector by inserting a visual indicator
## Step 1: Get a Dime.Scheduler API key
Ask your administrator to generate an [API key](/administration/apikeys) and store it somewhere safe. You will need it in the next step.
## Step 2: Create a Power Platform connection
Open the [Power Platform](https://make.powerapps.com/) Connections list and click the 'New connection' button. Scroll down to find 'Dime.Scheduler', or search for 'Dime.Scheduler' in the search box:
Click the + button to open the connection dialog:
Fill out the form with the following values:
- **Environment**: select 'Production' or 'Sandbox'
- **API key**: the Dime.Scheduler API key.
## Step 3: Test the connector
Take the connector for a test drive by inserting a [category](/user/components/planningboard/appointments#category), a visual indicator that sets the background color of appointments on the planning board.
Create a new **Instant cloud flow**:
In the modal window, give the flow a name and select **'Manually trigger a flow'**:
The Flow builder opens with the trigger already on the canvas. Add one step to give the flow meaning. Click the **+ New step** button, search for Dime.Scheduler, then select the 'Add, update or remove a category' action from the list.
If all went well, the flow should look like this:
This is where your knowledge of Dime.Scheduler pays off. If you know the data model, the Power Platform is straightforward to use. If not, these references will help you navigate the data model and its terminology:
- [Glossary](user/basic-concepts.md)
- [Fields and actions](develop/api.md)
A category is a simple type. Fill out a name and a color, and indicate whether to append (create or update) or to remove the record. The name serves as the external identifier, while the display name appears throughout the application. When you're done, you should have something like this:
Save the flow, click the Test button in the top-right corner, select 'Manually', and click the Test button at the bottom of the fly-in window.
To check the status, go to the flow's 'Run history' section and click the last run:
When you navigate to -> Indicators, you should see a new category in the list:
If nothing appears, check the status of the step in the Power Platform, or the logs in Dime.Scheduler.
Congratulations, you've just created your first Power Automate flow with the connector for Dime.Scheduler.
The next part explores the connector further and shows how to manage your resources and work items with the Power Platform connector and Dime.Scheduler.
---
## Part 4: Processing appointments
This part closes the loop: when a task is (re)scheduled in Dime.Scheduler, you'll reach back to the underlying application (the Excel file) and update the task. You'll do this with the webhook connector and an HTTP-triggered Power Automate flow.
:::info
This is the fourth post in the guide on how to integrate Dime.Scheduler with Microsoft Excel through the Power Platform:
- Part 1: Setting up the connector
- Part 2: Resources
- Part 3: Tasks
- **Part 4: Processing appointments**
:::
In this post, you will learn:
- How to post back to a Power Automate Flow
- Update the Excel file with appointment info
## Step 1: What happens when you schedule an appointment
Unlike the standard connectors that Dime Software provides, your Power Platform solution does nothing by default when you modify an appointment on the planning board. If you want to act on appointment information, you build that behavior yourself.
That's where the [webhook connector](integrations/webhook/intro.md) comes in. As soon as you change the planning, the webhook connector sends an HTTP POST request to your endpoint of choice with the *entire* appointment data graph. From the request body you can navigate the graph and pull out the details you need: who is assigned, when and where the work takes place, and more. For a complete overview of what's in the graph, see the [API reference](/develop/api/appointment).
Since a Power Automate flow can be invoked with an HTTP request, that endpoint can be a flow of your own. In this example, the flow goes back to the Excel file on SharePoint and sets the 'Finished' status to true when a task has been scheduled:
## Step 2: Create a flow
To keep this flow simple, make one small addition to the flow that creates tasks: add the Number column to one of the free text fields.
Run this flow again to update your tasks. Add the `Task FreeText 1` column to the open task column list to confirm that the values from the `Number` column landed correctly in the free text property.
The postback uses this information to find and update the right row in Excel. Create another Power Automate flow, this time with a different trigger. Select "When a HTTP request is received" and paste the following JSON into the text area:
```json
{
"type": "object",
"properties": {
"Task": {
"type": "object",
"properties": {
"FreeText1": {
"type": "string"
}
}
}
}
}
```
The Power Platform parses this JSON and generates variables you can use in later steps.
:::info
The JSON that is actually posted is much, much larger. We trimmed it here to keep the example focused. To see the entire data graph, use a network monitor or a testing tool such as [webhook.site](https://webhook.site/).
:::
To return to the original Excel file and update the row of the scheduled appointment, select the 'Update a row' action and point it back to the original Excel sheet.
The `Key Column` and `Key Value` tell the Power Platform which column to use and which identifier to look for. Here, the `Number` field in the Excel sheet serves as the unique field. Scheduling an appointment sends Dime.Scheduler's entire appointment data graph, so you map the data once more, this time in the other direction (from Dime.Scheduler back to the Power Platform). Because you added the Number field to the task's FreeText1 field earlier in this step, the lookup is straightforward. To update this record, set the `Finished` column value to 'Yes'.
Save the flow and copy the value in the HTTP POST URL field of the HTTP trigger at the top of this flow.
## Step 3: Set up the connector
When you schedule an appointment, Dime.Scheduler figures out which back-office system to notify. Recall that every flow specified a `SourceApp` value of 'SharePoint'. When such a task is scheduled, Dime.Scheduler looks for a connector with the same value.
On the connector setup page, make sure a record carries that value and a valid URI pointing to the back-office system. Here, you want to invoke the Power Automate flow you just created. Create a new record, specify the same source application as the jobs and tasks you created, tick the checkbox, select 'Webhook' as the back-office system, and paste the URL of the Power Automate flow trigger:
:::tip
To test this connector, consider a testing site such as [webhook.site](https://webhook.site/), which also shows you *all* the fields and values that Dime.Scheduler publishes.
:::
## Step 4: Test
With the Power Automate flow and Dime.Scheduler connector in place, it's time to test. Pick any task, drop it on the planning board, and check the Power Automate flow history.
And sure enough, the flow has updated the correct row:
Congratulations: you now have a two-way message flow between Dime.Scheduler and the Power Platform. From here, the possibilities are wide open. The appointment graph carries all the details of the scheduled work, along with references to the original items, so you can build rich, powerful solutions on top of it.
---
## Part 3: Tasks
In this part, you'll take the list of work in your Excel file and make it available in the open task list in Dime.Scheduler.
:::info
This is the third post in the guide on how to integrate Dime.Scheduler with Microsoft Excel through the Power Platform:
- Part 1: Setting up the connector
- Part 2: Resources
- **Part 3: Tasks**
- Part 4: Processing appointments
:::
By the end of this post, you'll know how to:
- Use the job and task actions in the Power Platform connector
- Create and manage tasks in Dime.Scheduler
- Schedule tasks
## Step 1: What constitutes a task
With your resources available for planning, it's time to give them some work. You can use manual appointments, but those usually fit ad-hoc and unstructured interventions, such as tentative appointments awaiting confirmation from the customer. Dime.Scheduler comes into its own when used together with other systems. As covered in earlier parts of this series, a simple Excel sheet provides the list of work that needs to be planned:
Much like a resource, a task can be anything: it's simply a piece of work that needs to be carried out. In this tutorial, we'll plan a number of events for the employees of the company Dunder Mifflin Inc. The plannable work in the Excel sheet above is as simple as it gets: a list of events that need to be planned. The 'Finished' column indicates whether an event has been planned yet. This is just a showcase, though. It's ultimately up to you to define what a task is, and you can make it as complex as you want.
:::tip
Any system that can provide a list of work is eligible to connect to Dime.Scheduler. Any system that can make its data flow through the Dataverse can leverage our connector and add work to the list that needs to be scheduled.
:::
The data model of a task in Dime.Scheduler is slightly different, as it has two levels: **jobs** and **tasks**. A job is the high-level entity, best compared to a project: it's a collection of activities (or **tasks**) that need to be completed to reach a certain outcome. Jobs bundle common information that applies to all underlying tasks, such as customer info, billing details, and address. For a complete overview of a job's properties, see the [API reference](/develop/api/job).
A task, on the other hand, is the actual unit of work to carry out. A task always belongs to a job, and it uniquely identifies an item of work that appears in the open task list, and then in the planning board once scheduled. For a complete overview of a task's properties, see the [API reference](/develop/api/task).
This data model gives you many ways to organize your work. For simplicity, we'll use a 1:1 relationship between jobs and tasks: for each task we'll create a new job. You may instead want to group tasks if you need to know how many hours your consultants have worked on a given project, or get a detailed report of how much budget is left on a fixed-budget project.
| Event field (Excel) | Field (Dime.Scheduler) | Type | Comment |
| ------------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------- |
| Event + Number | JobNo | Job | The `JobNo` field serves as an external identifier that can be traced back to the job |
| Event + Number | TaskNo | Task | The `TaskNo` field serves as an external identifier that can be traced back to the task |
| Description | Description | Job/Task | |
| Region | Region | Job | |
| Responsible | Region | Job | |
In this setup, concatenating `Event + Number` creates a unique identifier for the work item. For more complex projects with multiple tasks, the `JobNo` and `TaskNo` will almost always differ.
The next two steps are much like the previous part: you'll create a flow that is triggered manually, then invoke the right actions with the correct data mapping to send the data to Dime.Scheduler and start planning.
## Step 3: Create a new flow
Create a new flow in Power Automate. To keep things simple, we'll trigger it manually:
In the modal window, give the flow a name and select **'Manually trigger a flow'**:
The Flow builder opens with the trigger already added to the canvas.
## Step 4: Looping through each record in the Excel sheet
One of the many advantages of storing the Excel file on Office 365 is that the Power Platform can not only access the file, but also read and update rows and columns. In our example, we want to send over the entire list in the 'Events' sheet. A few simple commands let us run an action against every item found.
Add a step to the flow and look for the action 'List rows present in a table' in the Excel Online connector. This action is self-explanatory: tell it where to find the Excel file and which table to look for:
With the list in hand, iterate through each record in the table. The Power Platform has you covered with the 'Apply to each' action. For each record in the table, we want to add a task in Dime.Scheduler. Inside the 'Apply to each' section, add the following actions, in this order:
- Add, update or remove a job
- Add, update or remove a task
In the output field of the loop action, select `value`.
## Step 5: Inserting tasks
When you expand either action, you'll find an extensive list of fields you can use to alter the behavior of Dime.Scheduler. To dig deeper, check out the [Job API reference](/develop/api/job) and [Task API reference](/develop/api/task).
For most actions in the Dime.Scheduler connector for the Power Platform, the first field is a Yes/No field that asks whether to append the record or remove it. To append is to create or update a record, and it's idempotent: if a record already exists with that external identifier, it's updated. When set to false, Dime.Scheduler attempts to remove the record with the external identifier (`JobNo` or `TaskNo`) you provide in the action. For this tutorial, set append to true.
What makes the Power Platform so powerful is that you can simply click your workflows together. Inside an action, Power Automate tells you which fields are in scope. Using the mapping table from step 1, look for the right field in Dime.Scheduler and populate it with the columns of the Excel sheet:
For the source app and source type fields, we've set static values that apply to all data passing through this flow. For each job in the Excel sheet, we'll know its original data source is a SharePoint library and that it comes from the 'Event' sheet.
When creating a task, you need to provide a unique external identifier in the `TaskNo` field, and you also need to reference a job using the job's unique external identifier in the `JobNo` field. So the value of the `Job No` field in both actions must be the same (as must the Source App and Source Type fields).
Two additional fields aren't visible in the screenshot below:
- Duration in seconds is set to 14400. This value (4 hours) sets the initial value of the appointment when it's dropped on the planning board.
- Subject is set to 'Event `Number`:`Description`'. When dropped on the planning board, this format sets the appointment's subject.
Save the flow, hit the Test button at the top-right corner of the screen, select 'Manually', and click the Test button at the bottom of the fly-in window.
To follow up on the status of this flow, go to the 'Run history' section of the flow and click the last run:
When you navigate to the open task list, you'll see the list of tasks:
If nothing appears, check the status of the step in the Power Platform, or the logs in Dime.Scheduler.
Congratulations, you've added tasks to Dime.Scheduler and are ready to plan them. Take any task, look for an available resource, drag and drop it on the right date and time, and you're done.
---
## Power Platform Guides
import DocCardList from '@theme/DocCardList';
Four tutorials, one pattern. Each takes a different Microsoft source of work (a spreadsheet,
a task list, a booking calendar, a project board), pushes it into Dime.Scheduler as resources
and tasks, and then sends the resulting appointments back with a webhook.
That means you do not need to work through all four. Pick the one whose source system is
closest to your customer's, and the mechanics carry over to the others:
- **Tutorial: Excel** if the work arrives as rows in a spreadsheet. This is the longest of
the four and the best one to read if you want the full picture, because it covers resources
and tasks as separate steps.
- **Tutorial: To Do** if the work is a personal or shared task list.
- **Tutorial: Booking** if appointments already exist in Microsoft Bookings.
- **Tutorial: Planner** if the work lives on a Planner board.
All four assume you have already created an [API key](/administration/apikeys) and set up
the [connector](/integrations/powerplatform).
---
## Tutorial: Planner
Use **Planner tasks** as a data source for Dime.Scheduler. By the end of this tutorial, you will be able to:
- Add a Power Automate flow that runs whenever a new Planner task is added.
- Schedule the work in Dime.Scheduler and update the Planner task whenever it is planned.
This is a straightforward example of how the Power Platform and Dime.Scheduler work together to cover your scheduling needs.
:::info[Prerequisites]
This tutorial assumes you have already created a Power Platform connection for Dime.Scheduler. If you haven't, start with the [Excel tutorial](/integrations/powerplatform/guides/excel/overview).
:::
---
## Part 2: Processing appointments
In this part, you'll push scheduling changes back to Planner: when an appointment is (re)scheduled in Dime.Scheduler, the matching Planner task gets updated. You'll do this with the webhook connector and an HTTP-triggered Power Automate flow.
:::info
This is the second post in the guide on how to integrate Dime.Scheduler with Microsoft Planner through the Power Platform:
- Part 1: Tasks
- **Part 2: Processing appointments**
:::
You will learn how to:
- Post appointment changes back to a Power Automate flow
- Update the Planner task with the appointment info
:::caution
This tutorial assumes a Power Platform connection for the Dime.Scheduler has already been created. If you haven't done so, make sure to check out the [Excel tutorial](/integrations/powerplatform/guides/excel/overview).
:::
## Step 1: What happens when you schedule an appointment
Unlike the standard connectors that Dime Software provides, a Power Platform solution does nothing by default when you modify an appointment on the planning board. To act on appointment information, you build that part yourself.
That's where the [webhook connector](integrations/webhook/intro.md) comes in. As soon as you change the planning, the webhook connector makes an HTTP POST request to the endpoint of your choice with the *entire* appointment data graph. From the request body, you can read the details you need, such as who is assigned, and when and where the work takes place. For a complete overview of what's in the graph, see the [API reference](/develop/api/appointment).
Because a Power Automate flow can be triggered by an incoming HTTP request, you can point the webhook straight at a flow. In this example, the flow goes back to the Planner task and updates its start and end times along with the assigned people:
## Step 2: Create a flow
:::tip
You should also consider the [triggers](/integrations/powerplatform/triggers) that ship out of the box in Dime.Scheduler's connector.
:::
Create another Power Automate flow, this time with a different trigger. Select "When a HTTP request is received" and paste the following JSON in the text area:
```json
{
"type": "object",
"properties": {
"Task": {
"type": "object",
"properties": {
"FreeText1": {
"type": "string"
}
}
},
"Assignments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"Resource": {
"type": "object",
"properties": {
"Email": {
"type": "string"
}
}
}
}
}
},
"StartDate": {
"type": "string"
},
"EndDate": {
"type": "string"
}
}
}
```
The Power Platform will parse this JSON and generate variables for you to use in subsequent steps.
:::info
The JSON that is actually posted is much larger. We trimmed it here to keep the example readable. To see the entire data graph, use a network monitor or a testing tool such as [webhook.site](https://webhook.site/).
:::
To update the Planner task, first retrieve it. Add the 'Get a task' action, select the targeted list, and use the FreeText1 variable in the to-do task field. Recall that you stored the Planner task's identifier in this free text field in Part 1, so you use it here to fetch the original item in the plan.
Now add the 'Update a task' action and fill out the fields you want to update. The first two fields identify the item to update, so fill out the list and the Id of the item you fetched in the previous action. We use the appointment's end date as the task's due date, so set that field as well.
The next two steps could be refined further, but this approach is simple and gets the job done: it synchronizes the resources that the planner has (un)assigned in Dime.Scheduler. Add two loops. The first loop iterates through each assignment of the current Planner task and removes them. The second loop iterates through the assigned resources in Dime.Scheduler and assigns them to the Planner task.
Save the flow and copy the value in the HTTP POST URL field of the HTTP trigger at the top of this flow.
## Step 3: Set up the connector
When you schedule an appointment, Dime.Scheduler works out which back-office system to notify. Recall that you set a `SourceApp` value of 'PLANNER' in all of your flows. When such a task is scheduled, Dime.Scheduler looks for a connector with the same value.
On the connector setup page, make sure a record exists with that same value and a valid URI pointing to the back-office system. In this scenario, you want to invoke the Power Automate flow you just created. Create a new record, specify the same source application as the jobs and tasks you created, tick the checkbox, select 'Webhook' as the back-office system, and paste the URL of the Power Automate flow trigger:
:::tip
To test this connector, you can also use a testing site such as [webhook.site](https://webhook.site/), which shows you *all* the fields and values that Dime.Scheduler publishes.
:::
## Step 4: Test
With the Power Automate flow and Dime.Scheduler connector in place, it's time to test. Pick any task, drop it on the planning board, and check the Power Automate flow history. If all went well, the flow has updated the Planner task successfully:
Congratulations, you've now set up a two-way message flow between Dime.Scheduler and the Power Platform. From here, the possibilities are wide open. The appointment graph carries all the information about the scheduled work, along with references to the original items, so you can build rich and powerful solutions on top of it.
---
## Part 1: Setting up tasks
This part shows you how to make a Planner task available for planning in Dime.Scheduler the moment a user creates it.
:::info
This is the first post in the guide on how to integrate Dime.Scheduler with Microsoft Planner through the Power Platform:
- **Part 1: Tasks**
- Part 2: Processing appointments
:::
You will learn how to:
- Set up a Power Automate flow
- Convert a Planner task into a Dime.Scheduler task
:::caution
This tutorial assumes a Power Platform connection for the Dime.Scheduler has already been created. If you haven't done so, make sure to check out the [Excel tutorial](/integrations/powerplatform/guides/excel/overview).
:::
## Step 1: What constitutes a task
Dime.Scheduler is built around planning resources and tasks. A resource is anyone or anything that can carry out work, and a task is the unit of work a resource needs to execute.
In this tutorial, you schedule Planner tasks that employees create in a plan. The example uses a simple Kanban board, a common tool for teams that want to collaborate and streamline their work.
The Dime.Scheduler task model maps cleanly onto Microsoft Planner because it also has two levels: **jobs** and **tasks**. A job is the high-level entity, closest to a plan in Microsoft Planner. It is a collection of activities (the **tasks**) that need to be completed to reach an outcome, and it bundles information shared by all of those tasks, such as customer info, billing details, and address. For the full list of a job's properties, see the [API reference](/develop/api/job).
A task is the actual unit of work to be carried out. Every task belongs to a job and uniquely identifies an item of work that appears in the open task list, and then on the planning board once scheduled. For the full list of a task's properties, see the [API reference](/develop/api/task).
## Step 2: Create a new flow
Create a new flow in Power Automate and choose the 'When a new task is created' trigger.
Make sure you have already created a plan in Planner before you build the flow.
## Step 3: Inserting tasks
A plannable task in Dime.Scheduler has two levels: jobs and tasks. A job holds the high-level properties such as customer and billing info, while a task covers the actual work to be performed. To make a Planner task available in Dime.Scheduler, the flow runs two actions: it creates the job, then creates the task. Each time the flow runs, it should ensure the job exists and add the new task to the list in Dime.Scheduler.
Expand either action in Power Automate and you'll find a long list of fields that control how Dime.Scheduler behaves. For the details, see the [Job API reference](/develop/api/job) and [Task API reference](/develop/api/task).
For most actions in the Dime.Scheduler connector for the Power Platform, the first field is a Yes/No field that asks whether to append or remove the record. Append creates or updates a record and is idempotent: if a record already exists with that external identifier, the action updates it. When set to false, Dime.Scheduler removes the record with the external identifier (`JobNo` or `TaskNo`) you provide. For this tutorial, set append to true.
The Power Platform lets you click your workflows together. Inside an action, Power Automate shows which fields are in scope. You just need to find the right field in Dime.Scheduler and populate it with the value a Planner entry provides:
For the source app and source type fields, set static values that apply to every record passing through this flow. For each Planner task, this records that the original data source is a Planner list and that the work type is 'CONTENT' (this board tracks content to be added to the documentation platform). Because the flow is scoped to a single board, you can also use a fixed `JobNo` field.
When creating a task, you provide a unique external identifier in the `TaskNo` field and a reference to the job through the job's unique external identifier in the `JobNo` field. The `Job No` value must be the same in both actions, and so must the Source App and Source Type fields.
A plan carries little project metadata. At the time of writing, the Planner connector in Power Automate is limited, so there isn't much to do at the job level. Tasks offer more options. For example, you can link Planner labels to the visual indicators in Dime.Scheduler. A Planner task also has a Priority field that indicates its importance, just like Dime.Scheduler, so you can use that field too.
This example also stores the Planner task's ID value in one of the task's FreeText fields. You'll use that value once the task is planned in Dime.Scheduler.
Save the flow and test it by creating a new Planner task. To check the flow's status, go to its 'Run history' section and click the last run:
Open the open task list and you should see a new record that refers to the Planner task. If nothing appears, check the status of the step in the Power Platform or the logs in Dime.Scheduler.
You've now added tasks to Dime.Scheduler and they're ready to plan. Take any task, find an available resource, and drag and drop it onto the right date and time.
---
## Tutorial: To Do
Use **To Do lists** as a data source for Dime.Scheduler. In this tutorial you will:
- Add a Power Automate flow that runs whenever a new To Do entry is added.
- Schedule the work in Dime.Scheduler and update the To Do item whenever the task is planned.
It is a compact example of how the Power Platform and Dime.Scheduler work together to cover your scheduling needs.
:::info[Prerequisites]
This tutorial assumes a Power Platform connection for the Dime.Scheduler has already been created. If you haven't done so, make sure to check out the [Excel tutorial](/integrations/powerplatform/guides/excel/overview).
:::
---
## Part 2: Processing appointments(Todo)
In this part, you'll push changes back to the underlying application: when a task is scheduled or rescheduled in Dime.Scheduler, you'll update the matching To Do item. You'll do this with the webhook connector and an HTTP-triggered Power Automate flow.
:::info
This is the second post in the guide on how to integrate Dime.Scheduler with Microsoft To Do through the Power Platform:
- Part 1: Tasks
- **Part 2: Processing appointments**
:::
In this post, you will learn how to:
- Post back to a Power Automate flow
- Update the To Do item with appointment info
:::caution
This tutorial assumes a Power Platform connection for the Dime.Scheduler has already been created. If you haven't done so, make sure to check out the [Excel tutorial](/integrations/powerplatform/guides/excel/overview).
:::
## Step 1: What happens when you schedule an appointment
Unlike the standard connectors that Dime Software provides, your Power Platform solution does nothing on its own when you modify an appointment on the planning board. If you want to act on appointment changes, you have to build that yourself.
This is where the [webhook connector](integrations/webhook/intro.md) comes in. Whenever you change the planning, the webhook connector sends an HTTP POST request to the endpoint of your choice carrying the *entire* appointment data graph. Inside the request body, you can navigate the graph and pull out what you need: who is assigned, when and where the work takes place, and more. For a complete overview of what's in the graph, see the [API reference](/develop/api/appointment).
Because a Power Automate flow can be triggered by an HTTP request, you can point the webhook straight at a flow, which is exactly what you'll do here. In this example, you'll go back to the To Do item and set its Due Date field to the appointment's end date:
## Step 2: Create a flow
:::tip
Also consider the [triggers](/integrations/powerplatform/triggers) that ship out of the box in Dime.Scheduler's connector.
:::
Create another Power Automate flow. This time, use a different trigger. Select "When a HTTP request is received" and paste the following JSON in the text area:
```json
{
"type": "object",
"properties": {
"EndDate": {
"type": "string"
},
"Task": {
"type": "object",
"properties": {
"FreeText1": {
"type": "string"
}
}
}
}
}
```
The Power Platform parses this JSON and generates variables for you to use in later steps.
:::info
The JSON that is actually posted is much, much larger. We trimmed it here to keep the example focused. To see the entire data graph, use a network monitor or a testing tool such as [webhook.site](https://webhook.site/).
:::
To update the To Do item, you first have to retrieve it. Add the 'Get a to-do' action, select the target list, and use the FreeText1 variable in the to-do task field. Recall that you stored the To Do's identifier in this free text field, which you now use to fetch the original item from the To Do list.
Next, add the 'Update to-do' action and fill out the fields you want to update. The first two fields identify the item to update, so set the list and the Id of the item you fetched in the previous action. Since you're using the appointment's end date as the task's due date, set that field too.
If all went well, your flow should look like this:
Save the flow and copy the value from the HTTP POST URL field of the HTTP trigger at the top of the flow.
## Step 3: Set up the connector
When you schedule an appointment, Dime.Scheduler works out which back-office system to notify. Remember that you set a `SourceApp` value of 'TODO' in all of your flows. When such a task is scheduled, Dime.Scheduler looks for a connector with the same value.
On the connector setup page, make sure there is a record with that same value and a valid URI pointing to the back-office system. In this scenario, you want to invoke the Power Automate flow you just created. Create a new record, specify the same source application as the jobs and tasks you created, tick the checkbox, select 'Webhook' as the back-office system, and paste the URL of the Power Automate flow trigger:
:::tip
To test this connector, you can also use a testing site such as [webhook.site](https://webhook.site/), which shows you *all* the fields and values that Dime.Scheduler publishes.
:::
## Step 4: Test
With the Power Automate flow and the Dime.Scheduler connector in place, it's time to test. Pick any task, drop it on the planning board, and check the Power Automate flow history.
And sure enough, the flow has updated the To Do item:
Congratulations, you've now set up a two-way message flow between Dime.Scheduler and the Power Platform. From here, there are no limits. The appointment graph holds all the information about the scheduled work, plus references back to the original items, so you can build rich and powerful solutions on top of it.
---
## Part 1: Setting up tasks(Todo)
This guide walks you through making a Microsoft To Do item available for planning in Dime.Scheduler the moment a user creates it.
:::info
This is the first post in the guide on how to integrate Dime.Scheduler with Microsoft To Do through the Power Platform:
- **Part 1: Tasks**
- Part 2: Processing appointments
:::
By the end, you will know how to:
- Set up a Power Automate flow
- Convert a To Do item into a Dime.Scheduler task
:::caution
This tutorial assumes you have already created a Power Platform connection for Dime.Scheduler. If you haven't, start with the [Excel tutorial](/integrations/powerplatform/guides/excel/overview).
:::
## Step 1: What constitutes a task
Dime.Scheduler is all about planning resources and tasks. Both concepts can refer to anyone or anything: a resource is an entity that can carry out work, while a task is the unit of work a resource needs to execute.
In this tutorial, you plan the To Do items that employees create in their Outlook calendars. Salespeople and consultants often use this workflow to keep an overview of the work that comes out of emails from customers and coworkers. Like the other tutorials in this area, the example is simple and unstructured, but the task data model in Dime.Scheduler can handle far more complicated business scenarios because it works on two levels: **jobs** and **tasks**. A job is the high-level entity, best compared to a project: a collection of activities (or **tasks**) that need to be completed to reach an outcome. It bundles the common information that applies to all underlying tasks, such as customer info, billing details, and address. For a complete overview of a job's properties, see the [API reference](/develop/api/job).
A task is the actual unit of work that needs to be carried out. A task always belongs to a job and uniquely identifies an item of work that ends up in the open task list and, once scheduled, on the planning board. For a complete overview of a task's properties, see the [API reference](/develop/api/task).
This data model gives you many ways to organize your work. For simplicity, we use a 1:1 relationship between jobs and tasks: each To Do item produces one job and one task, so every To Do item is treated independently from the others in Dime.Scheduler. You may instead want to group tasks if you need to know how many hours your consultants have worked on a given project, or to report how much budget is left on a fixed-budget project.
## Step 2: Create a new flow
Create a new flow in Power Automate and choose the 'When a new to-do in a specific folder is created' trigger. To make the example usable for multiple users, listen to a specific folder that those users can access. A single Power Automate flow then gathers all new To Do items:
Create the To Do list before you build the flow.
## Step 3: Inserting tasks
As noted above, a plannable task in Dime.Scheduler has two levels: jobs and tasks. A job holds the high-level properties such as customer and billing info, while a task is only concerned with the work to be performed. To make a To Do item available in Dime.Scheduler, the flow needs to invoke two actions: create a job, then create a single task for that job. Expand either action and you'll find an extensive list of fields you can use to shape how Dime.Scheduler behaves. For the full picture, see the [Job API reference](/develop/api/job) and [Task API reference](/develop/api/task).
For most actions in the Dime.Scheduler connector for the Power Platform, the first field is a Yes/No field that asks whether to append the record or remove it. To append is to create or update a record, and it is idempotent: if a record already exists with that external identifier, it is updated. When set to false, Dime.Scheduler attempts to remove the record with the external identifier (`JobNo` or `TaskNo`) you provide in the action. For this tutorial, set append to true.
The Power Platform lets you click your workflows together. Inside an action, Power Automate shows you which fields are in scope. You just look for the right field in Dime.Scheduler and populate it with the info a To Do entry provides:
For the source app and source type fields, we set static values that apply to all data passing through this flow. For every To Do item, you then know its original data source is a To Do list and that it uses a generic 'Task' type. You could use the source type to distinguish between departments, for example referring to a To Do item from sales as SALES, consulting services as SERVICE, and so on. You're not obliged to do it this way, but it's a handy way to organize your work and add flexibility to your solution, which helps when you want to process appointments differently once they are planned in Dime.Scheduler.
When creating a task, you need to provide a unique external identifier in the `TaskNo` field, and you also need to reference a job using that job's unique external identifier in the `JobNo` field. So the value of the `Job No` field must be the same in both actions (and so must the Source App and Source Type fields).
To keep this flow simple, we set up the following conversion:
| To Do | Dime.Scheduler |
| -------- | ----------------------------- |
| Title | Description (Job/Task) |
| Title | Short Description (Job/Task) |
| Status | Status (Task) |
| Due Date | Expected Response Date (Task) |
| Body | Body (Task) |
| Id | Free text field #1 |
The jobNo variable truncates the To Do item's Id value, which is longer than the character count allowed for the Job No field in Dime.Scheduler. That's why we store the full To Do item Id in a Free Text field. We'll use this value in the next part to write appointment information back to the original To Do item.
:::note
If you're following along, this is how the jobNo variable is set: `TODO_` + `if(greater(length(triggerOutputs()?['body/title']),45),substring(triggerOutputs()?['body/title'], 0, 45), triggerOutputs()?['body/title'])`.
:::
Save the flow and test it by creating a new To Do item in the specified folder.
To check the status of this flow, go to the 'Run history' section of the flow and click the last run:
When you open the open task list, a new record referring to the To Do item should appear. If nothing shows up, check the status of the step in the Power Platform, or the logs in Dime.Scheduler.
Congratulations, you've added tasks to Dime.Scheduler and you're ready to plan them. Take any task, find an available resource, and drag and drop it onto the right date and time. Done.
---
## The Power Platform connector
The Microsoft Power Platform is a set of low-code tools for building apps, workflows, AI
bots, and analytics. Power users and citizen developers can assemble applications by
clicking and dragging, and wire them into their existing business systems.
The Power Platform connector plugs Dime.Scheduler straight into that ecosystem.
## Connect
You need a Power Platform subscription and a [Dime.Scheduler API key](/administration/apikeys).
Create the key, paste it into the connection window in the Power Platform, and select the
right Dime.Scheduler environment (production or sandbox).

## Actions and triggers
The connector runs both ways. **Actions** send information to Dime.Scheduler; **triggers**
fire when something happens in Dime.Scheduler. Beyond the built-in triggers, you can also
use the [webhook connector](/integrations/webhook/intro).
---
## Triggers
Use triggers to run a Power Automate flow whenever an appointment is scheduled. Dime.Scheduler invokes the flow directly from these appointment events:

## Source App
A trigger needs a name and a source app:

The source app identifies which back-office system the resources, tasks and appointments belong to. You can point a connector at another system, such as Business Central, to handle ancillary tasks like sending emails, and reuse the same source app identifier as the BC administration.
When an appointment is planned that maps to this source app, the Power Automate flow runs.
## Alternative: Power Automate HTTP trigger
Instead of the triggers we provide, you can use the [webhook connector](/integrations/webhook/intro) to invoke the **generic HTTP trigger** in Power Automate:

---
## Authentication
The webhook connector publishes planning data to an endpoint you control. Authentication is how Dime.Scheduler proves to that endpoint *who is calling* - so the receiver can reject random POSTs from the internet and only act on real Dime.Scheduler traffic.
Pick the authentication type the receiver supports. If you control both sides, prefer the strongest option the receiver can implement.
:::tip
The webhook connector is set up the same way every other connector is. If you haven't configured a connector before, start with the [Connectors](/administration/connectors) page for the common steps.
:::
## Supported types
- **None** - no credentials. Use only when the receiver is on a private network, behind another authentication layer (a gateway, an IP allow-list), or for quick tests. Never in production over the public internet.
- **Basic authentication** - a `login` and `password`, sent on every request. Simple, but only as secure as the transport: HTTPS is non-negotiable, otherwise the credentials are sent in clear text on every call.
- **MS Entra ID** - the right default when both sides live in Microsoft 365 / Azure. Dime.Scheduler obtains a token from Entra ID and presents it to the receiver, so neither side ever exchanges a long-lived shared secret.
- **OAuth2** - for receivers that expose any OAuth2-capable identity provider. The supported grant type is *client credentials*, in two flavours:
- **Client secret basic** - credentials go in the HTTP Authorization header (the safer flavour - prefer it when the receiver accepts both).
- **Client secret post** - credentials go in the request body.

## Picking one
OAuth2 and MS Entra ID are the right defaults for anything reaching across the public internet. They avoid sending a credential on every call and let you rotate secrets without redeploying the receiver. Use Basic only when the receiver does not speak OAuth2 yet, and use None only when network-level controls do the authentication job for you.
---
## Configuration(Webhook)
The webhook connector has the simplest configuration of any connector in Dime.Scheduler, because the receiving side does all the interpretation work. You give Dime.Scheduler one thing - a URL - and from then on every appointment change fires a POST to that URL with the full appointment as JSON.
:::tip
The webhook connector is set up the same way every other connector is - through the [Connectors](/administration/connectors) page. If you haven't configured a connector before, start there for the common steps.
:::
## Setting it up
Add a new connector of type **Webhook**, then set the **HTTP POST URI** to your endpoint. The URL must be publicly accessible from Dime.Scheduler and able to accept a POST request with a JSON body.

That is the whole setup. Anything beyond this is on the receiving side: persistence, parsing, error handling, whatever workflow you want to run when an appointment arrives.
## What lands at your URL
Every transaction on the planning board fires the connector. The body is the full appointment schema, so the receiver has everything it needs to act on without making a return trip to Dime.Scheduler. The fields include:
- Subject and body
- Start and end time
- Assigned resources
- The work item (task and job)
- Visual indicators
- Custom fields
- … and more
A common pattern is to wire the webhook into Power Automate, which can then fan the appointment out to email, Teams, Slack, a database, anywhere Power Automate connects:

## How records get tagged: the source app
The source app is the identifier that says "this record came from system X". You set it on the connector, and from then on every appointment that goes through this connector carries that tag. It matters for two reasons:
- When records flow *into* Dime.Scheduler (from a back-office system), the source app is what tells Dime.Scheduler which connector to use when the planner makes a change. The connector and the inbound source must agree on the same source-app value.
- When multiple systems share one tenant, the source app is what stops records from colliding - two unrelated "SO100" job numbers from two different sources stay distinct because they carry different source apps.
If the webhook is feeding a system that already lives in Dime.Scheduler under another connector (BC, for example), reuse that source app so both connectors fire on the same appointments - useful for splitting concerns ("BC handles the data, the webhook handles the email notification"). For a fundamentally new target (a CRM, a custom service), pick a unique descriptive name; it will stay with those records for their lifetime.
## When to reach for Power Automate instead
You can get the same result with the Dime.Scheduler connector in Power Automate. Behind the scenes, Power Automate registers a new connector in Dime.Scheduler the moment you start a flow with one of Dime.Scheduler's triggers:

Use the webhook connector when you want a single URL receiving raw JSON and your own code processing it. Use [Power Automate triggers](/integrations/powerplatform/triggers) when the no-code/low-code surface and the connectors Power Automate already offers (Outlook, Teams, SharePoint, hundreds more) save you building the receiver yourself.
---
## Introduction(Webhook)
import UsersSvg from '/img/assets/integrations/webhook/webhook-logo.svg';
Dime.Scheduler ships with first-class connectors for the systems most customers use - Business Central, NAV, Exchange. The webhook connector is the answer for everything else. It is the generic way to push every appointment change out of Dime.Scheduler, as JSON over HTTP, to *any* application that can stand up an endpoint and receive a POST.
That covers a lot of ground: a custom-built ERP, a CRM that no standard connector targets, an internal microservice that wants to react to planning events, a Power Automate flow, a Slack notifier, a Tetris game. Whatever sits on the other side of an HTTPS URL can become part of the planning loop.
## What gets published
Every time someone changes an appointment on the planning board (create, update, delete), the connector fires a POST to the configured URL with the full appointment payload as JSON. The receiver decides what to do with it - persist it, transform it, forward it, ignore it. Dime.Scheduler does not need to know.
Unlike the BC or NAV connectors, the webhook connector is intentionally generic: it does not try to interpret the payload, link it back to a record on the receiving side, or run a back-office workflow on success. The contract is simpler. Dime.Scheduler delivers the message; the receiver does the rest.
## Where it fits in the publishing flow
Dime.Scheduler routes every transaction by the appointment's Source App against the [registered connectors](administration/connectors.md). The webhook connector takes its turn in that same flow - register it with a source app, and every appointment whose source app matches will land at its URL.
The standard connectors (BC, NAV) then route further by Source Type to run the right back-office workflow. The webhook connector stops at the HTTP POST and hands control to the receiving system.
## Where it goes from here
What you build on the other side is up to you. A receiver that turns appointments into Outlook calendar items, a flow that posts them to Slack, a data lake that ingests every change for analytics, an experimental UI that visualises planning as something else entirely. Like, say, a game of Tetris:
---
## Transaction Errors(Webhook)
When a webhook delivery fails, the connector tags it with a stable error code so you can look up exactly what went wrong and how to fix it. Codes never change once issued - new ones are only ever added. Use this page to decode a code you've seen on a transaction.
A code shows up in two places at runtime:
- On the `Transaction.Message` column for failed rows, prefixed in square brackets (for example `[DSWBHK020] Status code: 502.`). See the [Errors page](/administration/transactions/errors).
- In NLog / Application Insights logs as `errorCode={ErrorCode}` on the structured log line.
Each code falls into one of three categories:
- **Retried** codes are picked up automatically by the retry job (3 attempts, exponential backoff with jitter, then the row stays `Failed` for the next pass).
- **Fix configuration** codes need a connector or partner setup change first. The in-pipeline policy does not retry them, but the DB-retry job picks `Failed` rows up on the next pass.
- **Undiagnosed** failure shapes should be sent to support so a rule can be added.
In each section below, 💡 explains what the code means and 🛠️ tells you what to do about it.
## Quick lookup
| Code | Goes to | Status | Retry |
|---|---|---|---|
| `DSWBHK001` | [Webhook not allowed](#webhook-not-allowed) | `BackOfficeNotAllowed` | fix config |
| `DSWBHK002` | [Empty or malformed URL](#empty-or-malformed-url) | `WrongUrl` | fix config |
| `DSWBHK003` | [Wrong connector type](#wrong-connector-type) | `InvalidBackOfficeType` | fix config |
| `DSWBHK010` | [Partner endpoint timeout](#partner-endpoint-timeout) | `Failed` | retried |
| `DSWBHK020` | [Partner HTTP failure](#partner-http-failure) | `Failed` | retried |
| `DSWBHK030` | [Circuit breaker open](#circuit-breaker-open) | `Failed` | retried |
| `DSUNK001` | [Undiagnosed](#undiagnosed) | `Failed` | undiagnosed |
## Webhook not allowed
The connector is flagged as not allowed to send appointments to a back-office.In the connector settings, enable **Send appointment to web service**.
## Empty or malformed URL
The connector's web-service address is empty or malformed.Set a valid `https://...` URL for the webhook endpoint in the connector settings.
## Wrong connector type
The connector type cannot be cast to `Webhook` - typically a stale row from an older connector definition.Recreate the connector with type **Webhook**. The status column is the signal; raw text is suppressed in the transaction message.
## Partner endpoint timeout
The partner endpoint did not respond within the request timeout (`TimeoutException`).Check partner endpoint availability and response time. The retry job will pick up `Failed` rows after the configured TTL.
## Partner HTTP failure
A non-transient HTTP failure (typically a permanent 4xx other than 401/403/429), or a transient 429/5xx after retries were exhausted. The raw status code and message are included in the transaction row.If the status is 4xx, fix the request shape (e.g. payload schema, content-type, authentication scope) - these don't self-heal. If 5xx, check the partner endpoint.
## Circuit breaker open
The per-(tenant, connector) circuit breaker is open - prior failures already exhausted the in-pipeline retries, and this call was short-circuited before reaching the partner.Self-healing - the breaker half-opens after its break duration, and the DB-retry job re-picks `Failed` rows. No action needed unless the underlying partner endpoint is still unavailable.
## Undiagnosed
No diagnostic rule matched the exception. The exception is recorded verbatim on the transaction row and logged as `errorCode=DSUNK001` so it can be triaged into a new rule.If this fires repeatedly, send the transaction ID and error message to Dime.Scheduler support so a typed rule can be added.
## Cross-cutting codes
These are emitted by the shared connector layer and may also surface on webhook transactions when the failure happens before the delivery code runs. Their definitions live with the [Business Central transaction errors](/integrations/bc/transaction-errors):
- `DSCFG001` - `BackOfficeNotAllowed` (shared, mirrors `DSWBHK001` semantics).
- `DSCFG002`-`DSCFG008` - connector-configuration validation failures (argument, null reference, invalid system cast, malformed URL, rejected credentials).
- `DSHTTP001` - generic HTTP transient (the shared transient rule before it's specialised into `DSWBHK020`).
- `DSDB001`-`DSDB004` - database / persistence transients during transaction-row writes.
---
## Actions(Zapier)
Where [triggers](/integrations/zapier/triggers) are narrow, actions are broad. They push data into Dime.Scheduler from the other apps in your Zap, covering seventeen item types across three verbs: create or update, set a single attribute, and delete.
Which verb you want depends on what you know about the record. **Create or update** is the workhorse when the record may or may not exist yet. **Set attribute** changes one property on something that definitely exists, without resupplying the rest of it. **Delete** removes it.
## Create or update item
`createOrUpdateItem` is the main action for managing scheduling data. It creates the record if it does not exist and updates it if it does, which is decided by the source app, source type, and identifier you supply.
### Supported item types
| Item Type | Description |
|-----------|-------------|
| **Appointment** | Scheduled time slots assigned to resources |
| **Task** | Work items with job details and scheduling information |
| **Resource** | People, vehicles, or equipment that can be scheduled |
| **Job** | Collections of tasks (projects or work orders) |
| **Category** | Organizational classifications for tasks, jobs, or resources |
| **Time Marker** | Time period markers for scheduling and tracking |
| **Pin** | Important location or item markers |
| **Container** | Groups for organizing related tasks or appointments |
| **Assignment** | Resource assignments to appointments |
| **Notification** | Alerts and messages for users or resources |
| **Action URL** | Links to external resources or systems |
| **Caption** | Custom labels and descriptions in different languages |
| **Filter Group** | Filter option organizers |
| **Filter Value** | Individual filter values for data filtering |
### Appointment
Create or update complete appointments, including their scheduling details.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `sourceApp` | string | Source application name |
| `sourceType` | string | Source type identifier |
| `jobNo` | string | Job number |
| `taskNo` | string | Task number |
| `resourceNo` | string | Resource to assign |
| `subject` | string | Appointment title |
| `body` | string | Description |
| `start` | datetime | Start date/time |
| `end` | datetime | End date/time |
| `category` | string | Category name |
| `timeMarker` | string | Time marker |
| `importance` | integer | 0=Normal, 1=High, 2=Low |
| `locked` | boolean | Lock the appointment |
**Example Use Case:** Create appointments from CRM opportunities, customer bookings, or project management tools.
### Task
Create or update tasks, along with their job details and scheduling information.
**Key fields:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `sourceApp` | string | ✓ | Source application |
| `sourceType` | string | ✓ | Source type |
| `jobNo` | string | ✓ | Parent job number |
| `taskNo` | string | ✓ | Task number |
| `shortDescription` | string | ✓ | Brief description |
| `description` | string | | Detailed description |
| `category` | string | | Task category |
| `timeMarker` | string | | Time marker |
| `duration` | string | | Expected duration |
| `importance` | string | | Task priority |
| `status` | string | | Current status |
| `requestedStartDate` | datetime | | Requested start |
| `requestedEndDate` | datetime | | Requested end |
**Example Use Case:** Import tasks from project management systems, ticketing systems, or field service applications.
### Resource
Create or update resources, including their contact information, skills, and availability.
**Key fields:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `resourceNo` | string | ✓ | Resource identifier |
| `sourceApp` | string | | Source application |
| `sourceType` | string | | Source type |
| `resourceName` | string | | Full name |
| `displayName` | string | | Display name |
| `email` | string | | Email address |
| `phone` | string | | Phone number |
| `mobilePhone` | string | | Mobile number |
| `department` | string | | Department |
| `teamCode` | string | | Team identifier |
| `homeAddress` | string | | Home address |
| `homeAddressGeoLat` | string | | Latitude |
| `homeAddressGeoLong` | string | | Longitude |
**Example Use Case:** Sync employee data from HR systems, manage field technician profiles, or update resource information from Active Directory.
### Job
Create or update jobs, including their customer information and project specifications.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `sourceApp` | string | Source application |
| `sourceType` | string | Source type |
| `jobNo` | string | Job number |
| `shortDescription` | string | Brief description |
| `description` | string | Detailed description |
| `name` | string | Job name |
| `category` | string | Job category |
| `customerNo` | string | Customer number |
| `customerName` | string | Customer name |
| `customerAddress` | string | Customer address |
| `customerPhone` | string | Contact phone |
| `customerEmail` | string | Contact email |
| `siteAddress` | string | Work site address |
| `siteAddressGeoLat` | string | Latitude |
| `siteAddressGeoLong` | string | Longitude |
| `importance` | string | Priority level |
| `startDate` | datetime | Planned start |
| `endDate` | datetime | Planned end |
**Example Use Case:** Import work orders from ERP systems, create jobs from CRM sales orders, or sync project data.
### Category
Create or update categories to organize and classify tasks, jobs, or resources.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Category name |
| `displayName` | string | Display name |
| `color` | string | Color code (e.g., "#FF5733") |
**Example Use Case:** Organize work types, prioritize tasks, or create visual distinctions in the scheduler.
### Time Marker
Create or update time markers for scheduling and time tracking.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Time marker name |
| `color` | string | Color code |
**Example Use Case:** Mark special time periods, holidays, or shift types.
### Pin
Create or update pins to mark important locations, tasks, or resources.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Pin name |
| `color` | string | Pin color |
**Example Use Case:** Highlight priority items, mark VIP customers, or flag urgent tasks.
### Container
Create or update containers to group and organize related tasks or appointments.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Container name |
| `handleDate` | string | Date handling settings |
| `handleLocked` | string | Lock handling settings |
**Example Use Case:** Group related work items, organize project phases, or batch similar tasks.
### Assignment
Create or update resource assignments to appointments.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `sourceApp` | string | Source application |
| `sourceType` | string | Source type |
| `appointmentId` | string | Appointment identifier |
| `resourceNo` | string | Resource to assign |
| `appointmentGuid` | string | Appointment GUID |
**Example Use Case:** Assign resources to work, manage multi-resource appointments, or handle resource changes.
### Notification
Create or update notifications to send alerts and messages.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `sourceApp` | string | Source application |
| `sourceType` | string | Source type |
| `appointmentId` | integer | Associated appointment |
| `type` | integer | Notification type |
| `code` | string | Notification code |
| `text` | string | Message text |
| `date` | string | Notification date |
**Example Use Case:** Send alerts for appointment changes, notify about urgent tasks, or communicate with field workers.
### Action URL
Create or update action URLs to link external resources.
**Key fields:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `sourceApp` | string | ✓ | Source application |
| `sourceType` | string | | Source type |
| `uriType` | string | | URI type |
| `uri` | string | | The URL |
| `description` | string | | URL description |
| `requestType` | integer | | Request type |
| `default` | boolean | | Set as default |
**Example Use Case:** Link to external documentation, connect to back-office systems, or provide quick access to related information.
### Caption
Create or update captions for custom labels in different languages.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `context` | string | Context |
| `sourceTable` | string | Source table name |
| `fieldName` | string | Field name |
| `language` | string | Language code |
| `text` | string | Caption text |
**Example Use Case:** Provide multi-language support, customize field labels, or localize the interface.
### Filter Group
Create or update filter groups to organize filter options.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Filter group name |
| `columnNo` | string | Column number |
| `dataFilter` | string | Data filter expression |
**Example Use Case:** Organize filtering options, create custom views, or categorize data.
### Filter Value
Create or update filter values for data filtering.
**Key fields:**
| Field | Type | Description |
|-------|------|-------------|
| `group` | string | Parent filter group |
| `value` | string | Filter value |
**Example Use Case:** Define filter options, create selection lists, or organize data hierarchies.
## Set attribute
**Action:** `setAttribute`
**Description:** Sets or updates a specific attribute on an existing appointment, task, or resource. Reach for this when you want to change a single property without touching the rest of the item.
### Appointment
#### Category
Assign a category to an appointment
#### Container
Add appointment to a container
#### Content
Update subject and/or body text
#### Importance
Set importance level (0-2)
#### Locked
Lock or unlock the appointment
#### Planning quantity
Update planning quantity
#### Time Marker
Assign a time marker
#### URI
Add or update a link
### Task
#### Container
Assign task to a container
#### Filter value
Add filter value to task
#### Locked
Lock or unlock the task
#### URI
Add or update a task link
### Resource
#### Calendar
Assign working calendar
#### Capacity
Set resource capacity
#### Filter value
Add filter value to resource
#### Location
Update GPS tracking location
#### URI
Add or update a resource link
**Example Use Cases:**
- Lock appointments after confirmation
- Update appointment categories based on status
- Set resource locations from GPS tracking apps
- Update importance levels based on external triggers
## Delete item
**Action:** `deleteItem`
**Description:** Permanently removes items from Dime.Scheduler. This operation cannot be undone, so handle it with care.
:::danger[Warning]
Deletion is permanent. Deleted items cannot be recovered. Use with caution.
:::
### Supported delete operations
The following item types can be deleted:
- **Appointment Container** - Remove container assignment
- **Category** - Delete a category
- **Container** - Remove container (ungroups items)
- **Filter Group** - Delete filter group
- **Filter Value** - Remove filter value
- **Job** - Delete job (removes all tasks and appointments)
- **Notification** - Remove notification
- **Pin** - Delete pin
- **Resource** - Delete resource (removes all assignments)
- **Resource Calendar** - Remove calendar assignment
- **Resource Filter Value** - Remove filter value from resource
- **Task** - Delete task and associated data
- **Task Container** - Remove task container assignment
- **Task Filter Value** - Remove filter value from task
- **Time Marker** - Delete time marker
Deletions are worth treating more carefully than creates. A Zap that mirrors deletions from another system will remove planned work here, and the appointment a planner spent the morning arranging is gone with it. Filter deliberately, and test against sandbox.
## Getting source app and source type right
Most actions require `sourceApp` and `sourceType`. Together they record where a record came from, and they are what Dime.Scheduler uses to route it and to recognise it again on the next update.
- `sourceApp` names the system, for example `SALESFORCE` or `CUSTOM CRM`.
- `sourceType` names the kind of record, for example `OPPORTUNITY` or `WORKORDER`.
This is the single most common thing to get wrong. Set the pair inconsistently and the Zap still reports success, but the record is either invisible where the planner expects it or created afresh each run instead of updating the existing one. Choose a pair per integration, write it down, and reuse it everywhere in that Zap.
## Choosing an identifier
Appointments, tasks and resources each carry three identifiers, and they are not interchangeable:
- The **GUID** is globally unique and stable, so it is the one to store in an external system.
- The **number**, such as `appointmentNo` or `taskNo`, is the human-readable reference for anything a person will read.
- The **ID** is Dime.Scheduler's internal identifier, for operations back against Dime.Scheduler.
## Order matters
References must exist before the things that point at them. A job before its tasks, a filter group before its values, an indicator before you assign it.
A Zap that creates a task and its job in the wrong order fails with an error that reads like a validation or permissions problem, which sends people looking in the wrong place. If an action fails on a field that looks correct, check whether its parent exists yet.
## Formats
Every datetime field uses ISO 8601: `YYYY-MM-DDTHH:mm:ssZ`, for example `2025-10-07T14:30:00Z`. Zapier converts between formats automatically, but the data still has to reach it in a standard form, and time zones are worth checking rather than assuming.
Booleans and numbers are similarly strict. A field arriving as the string `"yes"` where a boolean is expected fails validation.
## When an action fails
The Zap history carries the actual error message, so start there rather than guessing. Then work down the likely causes in order: a required field that is empty, a value in the wrong format, a reference whose parent does not exist yet, and finally an API key that has expired or been revoked.
---
## Practical examples
Use these recipes as starting points for automating scheduling workflows with the Dime.Scheduler Zapier connector. Each example shows the trigger, the action, the field mapping, and the kind of customer scenario it fits.
## Appointments from Google Calendar events
**Scenario:** Automatically create appointments in Dime.Scheduler when events are added to a specific Google Calendar.
### Trigger
**App:** Google Calendar
**Event:** New Event
### Action
**App:** Dime.Scheduler
**Event:** Create or Update Item
**Item Type:** Appointment
### Field mapping
| Google Calendar Field | Dime.Scheduler Field | Notes |
|----------------------|----------------------|-------|
| Summary | `subject` | Event title |
| Description | `body` | Event description |
| Start Time | `start` | Start date/time |
| End Time | `end` | End date/time |
| Calendar Name | `sourceApp` | Use "Google Calendar" |
| Event ID | `appointmentNo` | Unique identifier |
### Configuration
```text
Source App: Google Calendar
Source Type: Calendar Event
Job No: [Set a default or map from event]
Task No: [Set a default or map from event]
Resource No: [Extract from event description or set default]
Subject: {{summary}}
Body: {{description}}
Start: {{start_time}}
End: {{end_time}}
Importance: 1
```
### Why you would build this
This fits organizations that book customer appointments through Google Calendar and want those bookings reflected in Dime.Scheduler for resource planning, without re-entering anything by hand.
---
## Tasks from Asana
**Scenario:** When a new task is created in Asana, create a corresponding task in Dime.Scheduler.
### Trigger
**App:** Asana
**Event:** New Task
### Action
**App:** Dime.Scheduler
**Event:** Create or Update Item
**Item Type:** Task
### Field mapping
| Asana Field | Dime.Scheduler Field | Notes |
|-------------|----------------------|-------|
| Task Name | `shortDescription` | Brief title |
| Task Notes | `description` | Detailed description |
| Task ID | `taskNo` | Unique identifier |
| Project ID | `jobNo` | Link to job |
| Assignee Email | Use to lookup `resourceNo` | May require intermediate step |
| Due Date | `requestedEndDate` | Deadline |
| Priority | `importance` | Map to 0-2 scale |
### Configuration
```text
Source App: Asana
Source Type: Task
Job No: {{project_id}}
Task No: {{task_id}}
Short Description: {{task_name}}
Description: {{notes}}
Category: {{project_name}}
Importance: {{priority}} (mapped: High=2, Medium=1, Low=0)
Requested Start Date: {{start_date}}
Requested End Date: {{due_date}}
```
### Handling the assignee lookup
Create a lookup table in Airtable or Google Sheets to map Asana assignee emails to Dime.Scheduler resource numbers. Add a "Lookup" step before the Dime.Scheduler action.
---
## Notify a resource when their appointment changes
**Scenario:** Send an email or SMS notification to the assigned resource when their appointment is modified.
### Trigger
**App:** Dime.Scheduler
**Event:** Updated Appointment
### Action 1
**App:** Formatter by Zapier
**Event:** Date/Time Format
**Purpose:** Convert appointment times to readable format
### Action 2
**App:** Email by Zapier (or Twilio for SMS)
**Event:** Send Email
### Field mapping
| Dime.Scheduler Field | Email Field | Notes |
|---------------------|-------------|-------|
| `resourceName` | To Name | Who receives the email |
| Look up resource email | To Email | May require lookup step |
| `subject` | Email Body | Part of message |
| `start` | Email Body | Formatted start time |
| `end` | Email Body | Formatted end time |
| `jobName` | Email Body | Job details |
### Email template
```text
Subject: Your appointment has been updated
Hi {{resourceName}},
Your appointment "{{subject}}" has been updated with the following details:
Job: {{jobName}}
Task: {{taskName}}
Start Time: {{formatted_start}}
End Time: {{formatted_end}}
Location: {{siteAddress}}
Please confirm receipt of this update.
Best regards,
Scheduling Team
```
### Why you would build this
Keep field technicians, sales reps, or service personnel informed of schedule changes in real time, with no manual notifications to send.
## Making these hold up in production
The examples above are deliberately minimal. A few habits are what separate a Zap that survives contact with real data from one that quietly stops working.
**Start with one Zap and get it reliable** before chaining more onto it. Debugging two interacting Zaps is more than twice the work of debugging one.
**Name Zaps for what they do.** "Create DS appointment from Salesforce opportunity" tells the next person what breaks if they disable it. "Salesforce to DS" does not.
**Always populate `sourceApp` and `sourceType`.** They are what records where the data came from and what lets Dime.Scheduler recognise the same record on the next run. This is the single most common omission, and it fails quietly: the Zap succeeds and the record is either invisible or duplicated on every update.
**Decide what happens in the awkward cases** before they happen rather than after. Required data missing from the source, dates arriving in an unexpected time zone, a record that already exists, a parent record that does not exist yet. Each of these is a filter or a lookup step, and each is much cheaper to add now than to diagnose from a Zap history in three months.
**Check the Zap history weekly** and turn on failure notifications. Zaps fail silently by design: the workflow simply stops, and nobody downstream necessarily notices that appointments stopped arriving.
## Read more
- [Actions](/integrations/zapier/actions) and [triggers](/integrations/zapier/triggers)
- [Source app and source type](/develop/api/global/sourceapp), the two fields the advice above keeps returning to
- [Troubleshooting](/integrations/zapier/troubleshooting)
---
## Installing the Zapier connector
Setting up the connector is mostly a matter of authenticating once. After that, Dime.Scheduler behaves like any other app in a Zap. This page covers finding the connector, connecting it to your instance, and getting the first Zap running.
## Find the connector
The connector is publicly listed, so there is nothing to request or install. Log in to [zapier.com](https://zapier.com) and search for **Dime.Scheduler**, either in the app directory or while building a Zap. It is also at [zapier.com/apps/dime-scheduler/integrations](https://zapier.com/apps/dimescheduler/integrations).
## Create the Zap
Click **Create Zap**, pick a trigger from whichever app should start the workflow, then add an action and search for Dime.Scheduler.
The connector offers three actions: create or update an item, set an attribute on an existing item, or remove an item. Which one you want depends on whether the record already exists, which is worth settling before you start mapping fields.

## Authenticate
The first time you use the connector, Zapier asks you to sign in.

Two values are needed.
| Field | Description |
| --------------- | ---------------------------------------------- |
| **API key** | A Dime.Scheduler API key |
| **Environment** | `sandbox` or `production` |

Create the key under `Administration → API Keys`. Remember that the full key is displayed only once, at the moment you generate it, so copy it into your secret store straight away. See [API keys](/administration/apikeys).
The environment is part of the connection rather than a per-Zap setting, so a Zap inherits whichever environment its connection was created with. Build and test against **sandbox** first: a Zap that duplicates records or loops is far cheaper to find there than in a live tenant. When you are ready to go live, create a second connection pointed at production rather than editing the first, so the tested one stays intact.
Once the credentials are accepted, the authentication window closes and you continue building.

## Configure the action
Pick the item type, then map the fields from your trigger onto the Dime.Scheduler fields.
Pay attention to **source app** and **source type** here. They identify where a record came from and how it routes, and getting them wrong is the most common reason a Zap reports success while the record never turns up where a planner would look for it. See [actions](/integrations/zapier/actions).

## Test, then enable
Run the test and check the result before switching the Zap on.
Test with data that resembles reality rather than Zapier's sample payload. Samples arrive with every field populated; real records often do not, and a Zap that depends on an optional field being present will only fail once it is live.

When the test does what you expect, enable the Zap.
If something does not work, [troubleshooting](/integrations/zapier/troubleshooting) covers the common failures.
---
## Zapier connector
Most customers need Dime.Scheduler to talk to something the standard connectors do not target: a CRM, a form builder, a chat tool, a spreadsheet. Building that integration properly means an API client, a deployment, and someone to maintain it. The Zapier connector is the answer when that cost is out of proportion to the problem.
It puts Dime.Scheduler on the Zapier platform alongside several thousand other applications, so an integration becomes a workflow you configure rather than software you write. The connector is publicly listed: search for Dime.Scheduler when creating a Zap, with no invitation needed.
## What travels in each direction
The connector works both ways, but not symmetrically, and the asymmetry is the first thing to design around.
**Into Dime.Scheduler**, the connector is broad. It creates and updates appointments, tasks, resources, jobs, categories, time markers, pins, containers, assignments, notifications, action URLs, captions, filter groups and filter values: seventeen item types in all. It can also set individual attributes on an existing record without recreating it, and delete records.
**Out of Dime.Scheduler**, it is narrow. Three triggers, and all three are about appointments: created, updated, and removed. There is no trigger for a change to a task or a resource. If your workflow needs to react to one of those, Zapier is not the tool and the [webhook connector](/integrations/webhook/intro) is.
The triggers are webhook-based rather than polled, so they fire within seconds rather than on a schedule. Each one delivers the full appointment payload, including job details, resource information, timing and custom fields, which usually saves a follow-up lookup to enrich the data.
## How it fits together
A Zap is a trigger plus one or more actions. Something happens in a source application, Zapier captures it, maps the fields to what the target expects, and the connector calls the Dime.Scheduler API to create, update or delete. The reverse runs the same way with a Dime.Scheduler appointment as the trigger.
Everything the connector does goes through the same [public REST API](/develop/api) you could call yourself. Zapier is a hosted client for it, not a separate integration path, so anything the connector can do is also available to code, and anything it cannot do is generally not a Zapier limitation.
## Authentication and environments
The connector authenticates with an [API key](/administration/apikeys) and asks which environment to use. Both **sandbox** and **production** are available, and choosing between them is part of setting up the connection rather than a setting you change later.
Build against sandbox. A Zap that loops or duplicates is cheap to discover there and expensive to discover in a live tenant.
## What you need
- An active Dime.Scheduler subscription, production or sandbox.
- An API key with the permissions the Zap requires.
- A Zapier account. Free and paid tiers both work; the tier decides how many operations you can run per month.
The connector itself is included with your subscription.
## Where this goes wrong
Two failure modes account for most of the support traffic, and both are worth knowing before you build rather than after.
**Source app and source type.** Records written into Dime.Scheduler carry these two values, and they decide how a record is identified and routed. Get them wrong and the Zap reports success while the record never appears where the planner expects it. See [actions](/integrations/zapier/actions) for how to set them.
**The updated-appointment trigger fires on every change.** Every one, including changes nobody cares about. A Zap that notifies a channel on each update gets muted within a week. Put a Zapier filter directly after the trigger to narrow it to the changes that matter.
## Where to go next
Start with [prerequisites](/integrations/zapier/prerequisites) for the accounts and keys, then [installation](/integrations/zapier/install) to build the first Zap. [Triggers](/integrations/zapier/triggers) and [actions](/integrations/zapier/actions) are the reference for what each one carries, [examples](/integrations/zapier/examples) shows complete workflows, and [troubleshooting](/integrations/zapier/troubleshooting) covers what to do when a Zap does not fire.
For a guided version of the whole thing, see [automate notifications with Zapier](/cookbook/zapier) in the cookbook.
---
## Prerequisites(Zapier)
Three things are genuinely required before you can build a Zap, and they take a few minutes between them.
## What you need
**A Zapier account.** Free or paid both work. The tier decides how many operations you can run per month, which only becomes a consideration once a Zap is handling real volume. [Sign up](https://zapier.com/sign-up) if you do not have one.
**A running Dime.Scheduler instance.** Production or sandbox, either is fine.
**An [API key](/administration/apikeys).** Create it under `Administration → API Keys` ([production](https://app.dimescheduler.com/keys) / [sandbox](https://sandbox.app.dimescheduler.com/keys)) and give it a name that identifies what it is for, such as "Zapier integration". A key named after its purpose is one you can revoke later without wondering what will break.
:::warning
The full key is displayed once, at the moment you generate it. Copy it into your secret store immediately, because there is no way to retrieve it afterwards. Treat it like a password: never commit it to version control, and regenerate it if it is ever exposed.
:::
## What will make it go more smoothly
Neither of the following is required, but skipping them is where most of the avoidable rework comes from.
**A working grasp of the data model.** Zaps map fields between systems, and mapping them correctly means knowing how jobs, tasks, resources and appointments relate. [How it works](/user/basic-concepts) covers the terminology and how the pieces fit together in about ten minutes, which is considerably less than debugging a Zap that creates tasks with no job to hang from.
**A sandbox to build in.** Point your first connection at the sandbox instance and use representative test data. A Zap that loops, duplicates, or deletes the wrong thing is a curiosity in sandbox and an incident in production. Move to production once the Zap does what you expect, by creating a second connection rather than editing the tested one.
## What you might add later
Zapier's value is mostly in what sits between the two ends. Spreadsheet tools for lookup tables, communication apps for notifications, and the other business systems in your stack all become available once the connector is in place.
Familiarity with REST APIs, JSON, ISO 8601 dates, and Zapier's own concepts (triggers, actions, filters, paths) helps when a Zap misbehaves, though none of it is needed to build one.
Next: [installing the connector](/integrations/zapier/install).
---
## Triggers(Zapier)
A trigger is what starts a Zap. The connector offers three, and all three are about appointments: created, updated, and removed. There is no trigger for a change to a task, a resource, or a job, so a workflow that needs to react to one of those wants the [webhook connector](/integrations/webhook/intro) instead.
All three are webhook-based rather than polled, so they fire within seconds of the event rather than on a schedule.
## New appointment
`newAppointment` fires when an appointment is created.
It carries the full appointment, so in most cases you do not need a follow-up lookup to enrich the data before acting on it.
| Field | Type | Description |
| ------------------ | -------- | ---------------------------------------------- |
| `id` | string | Unique identifier for the appointment |
| `appointmentNo` | string | Human-readable appointment number |
| `appointmentGuid` | string | Global unique identifier |
| `jobNo` | string | Associated job number |
| `taskNo` | string | Associated task number |
| `resourceNo` | string | Assigned resource number |
| `subject` | string | Appointment subject or title |
| `body` | string | Appointment description or notes |
| `start` | datetime | Appointment start date and time |
| `end` | datetime | Appointment end date and time |
| `importance` | integer | 0 = normal, 1 = high, 2 = low |
| `locked` | boolean | Whether the appointment is locked |
| `planningQuantity` | string | Planning quantity for the appointment |
| `unitOfMeasure` | string | Unit of measure, for example `H` for hours |
| `createdAt` | datetime | When the appointment was created |
| `updatedAt` | datetime | When the appointment was last modified |
## Updated appointment
`updatedAppointment` fires when an existing appointment is modified. It returns the same fields as above, with `updatedAt` reflecting the modification.
This is the trigger to treat with care, because **any** change fires it. A resize on the planning board, a category swap, a subject correction: all of them. A Zap that posts to a channel on every update will be muted by its recipients within a week.
Put a Zapier filter directly after the trigger and narrow it to what actually matters, whether that is a particular category, a resource, or a change in the start time.
## Removed appointment
`removedAppointment` fires when an appointment is deleted. It returns the same fields plus one:
| Field | Type | Description |
| ----------- | -------- | -------------------------------- |
| `deletedAt` | datetime | When the appointment was deleted |
This is the trigger most often left unbuilt, and the omission is quiet. A workflow that creates a calendar event on `newAppointment` but ignores `removedAppointment` leaves cancelled work sitting in people's calendars, and nobody reports it for weeks. If you handle creation, handle removal.
## Choosing an identifier
Three identifiers arrive with every trigger, and they are for different jobs:
- `appointmentGuid` is the one to store in an external system, since it is globally unique and stable.
- `appointmentNo` is the human-readable reference, for a message or a spreadsheet a person will read.
- `id` is for operations back against Dime.Scheduler.
Picking the wrong one usually surfaces later as a lookup that cannot find the record it just wrote.
## Dates and duplicates
Every datetime field uses ISO 8601. Zapier converts these automatically for downstream apps, but time zones remain worth checking, since an appointment that lands an hour out is easy to miss and hard to explain.
Duplicate deliveries are unlikely rather than impossible. If a repeated event would be damaging, for example one that charges a customer or sends a message, add deduplication on the receiving side rather than assuming exactly-once delivery.
Triggers themselves are instant and unmetered by Dime.Scheduler, but the downstream actions in your Zap are not. Rate limits on the services you call are usually what bites first on a busy planning day.
---
## Troubleshooting(Zapier)
import AreaTroubleshootingLanding from '@site/src/components/AreaTroubleshootingLanding';
Use this page to diagnose and fix the issues you are most likely to hit when working with the Dime.Scheduler Zapier connector. Each section lists the symptoms to look for and the steps to resolve them.
## Authentication
### API key not accepted
**Symptoms**
- "Invalid API key" error when testing connection
- Authentication fails during Zap setup
**Solutions**
1. **Verify the API key**
- Ensure there are no extra spaces when pasting
- Check that the key hasn't been revoked or expired
- Generate a new API key from [Administration → API Keys](https://app.dimescheduler.com/keys) if needed
2. **Confirm environment selection**
- Verify you're using an API key from the matching environment (Sandbox vs Production)
- Disconnect and reconnect the Dime.Scheduler account in Zapier if needed
---
### Environment mismatch
**Symptoms**
- Connection works but data doesn't appear
- "Resource not found" errors
**Solution**
Your API key is environment-specific. Ensure you selected the matching environment in Zapier (Sandbox vs Production). Disconnect and reconnect if you need to change environments.
---
## Triggers
### Trigger not firing
**Symptoms**
- Zap doesn't run when appointments are created/updated/deleted
- No new data appears in Zap history
**Solutions**
1. **Check Zap status**
- Ensure the Zap is turned ON (not paused or draft)
- Review error messages in the Zap history
2. **Re-register webhook**
- Webhooks are automatically created but may need to be refreshed
- Toggle the Zap off and on to re-register the webhook
3. **Verify environment**
- Ensure appointments are being created in the same environment your Zap is monitoring
4. **Check filter criteria**
- Confirm appointments meet any filter criteria you've set in the Zap
---
### Missing data in the trigger output
**Symptoms**
- Trigger fires but some fields are empty or null
- Expected data doesn't appear in subsequent steps
**Solutions**
Some fields may be empty if they weren't populated in Dime.Scheduler:
1. **Check the source data**
- Verify the appointment in Dime.Scheduler has the fields populated
- Some fields like `resourceName` require the appointment to have an assigned resource
2. **Use conditional logic**
- Add a Filter step in Zapier to only process appointments with required data
- Handle empty fields gracefully in your Zap logic
3. **Map alternative fields**
- Use multiple identifier options (e.g., try `appointmentGuid` if `appointmentId` is empty)
---
## Actions
### "Required field missing"
**Symptoms**
- Action fails with "required field missing" message
- Zap shows error in history
**Solutions**
1. **Review required fields**
- Tasks: `sourceApp`, `sourceType`, `jobNo`, `taskNo`, and `shortDescription` are required
- Resources: `resourceNo` is required
- Appointments: Check the specific requirements for your item type
2. **Check data mapping**
- Ensure required fields are mapped to data from the trigger
- Use the Zap history to identify which field is missing
- Provide default values if trigger data might be missing
---
### Duplicate items created
**Symptoms**
- Multiple appointments/tasks/resources created when only one was expected
- Data appears duplicated in Dime.Scheduler
**Solutions**
1. **Prevent Zap loops**
- Bidirectional sync can create loops (Zap creates item → Trigger fires → Zap creates another item)
- Add filters to skip items from specific source apps
2. **Use unique identifiers**
- Provide unique identifiers like `appointmentNo`, `taskNo`, or `jobNo`
- The connector will update existing items if the identifier matches
3. **Add filter conditions**
- Use the `sourceApp` field to identify and skip items created by your Zap
---
### "Item not found"
**Symptoms**
- Set Attribute or Delete Item actions fail
- Error says the appointment/task/resource doesn't exist
**Solutions**
1. **Verify the item exists**
- Confirm the item is in Dime.Scheduler
- Check you're using the correct environment (Sandbox vs Production)
2. **Check identifiers**
- Ensure you're using the correct identifier field and it's not empty
- Try alternative identifiers (e.g., `appointmentGuid` instead of `appointmentId`)
3. **Add delay for timing issues**
- If creating then immediately updating an item, add a 1-2 second delay step
- The item might not be fully indexed yet
---
## Data formatting
### Date and time format errors
**Symptoms**
- "Invalid date format" error
- Dates appear incorrect in Dime.Scheduler
**Solution**
Dime.Scheduler expects ISO 8601 format: `YYYY-MM-DDTHH:mm:ssZ` (e.g., `2025-10-07T14:30:00Z`). Use "Formatter by Zapier" to convert dates to ISO 8601 format in UTC timezone before sending to Dime.Scheduler.
---
### Boolean field errors
**Symptoms**
- Fields like `locked`, `useFixedPlanningQuantity` not working correctly
- "Invalid boolean value" error
**Solution**
Boolean fields expect `true` or `false` (lowercase), not "Yes/No" or "1/0". Use "Formatter by Zapier" to convert values if needed.
---
### Number field errors
**Symptoms**
- "Invalid integer" or "Invalid number" errors
- Fields like `importance` not accepting values
**Solution**
Ensure proper data types: integers require whole numbers (0, 1, 2), decimals allow fractional values (1.5, 2.75). Remove non-numeric characters using "Formatter by Zapier" if needed. Note that `importance` must be 0 (Low), 1 (Normal), or 2 (High).
---
## Performance
### Zap running slowly
**Symptoms**
- Delays between trigger and action
- Data takes a long time to sync
**Solution**
Remove unnecessary steps and add filters early in the Zap to skip processing. Check Zapier's status page if issues persist - slow performance is often from other apps in your Zap.
---
### Hitting rate limits
**Symptoms**
- "Rate limit exceeded" errors
- Actions fail during high-volume periods
**Solution**
Use Zapier's "Delay" action to space out API calls or reduce the frequency of scheduled Zaps. If you consistently hit limits, contact Dime.Scheduler support to discuss increasing your rate limits.
---
## Common mistakes
### Using the wrong item type
Ensure you understand the difference between item types:
- **Appointment**: A scheduled time slot assigned to a resource (shows on the calendar)
- **Task**: Work that needs to be done (may or may not be scheduled yet)
- **Job**: A collection of tasks (like a project or work order)
- **Resource**: A person, vehicle, or equipment that can be scheduled
---
### Forgetting source app and source type
Always populate `sourceApp` and `sourceType` fields to track data lineage:
- `sourceApp`: The name of the system (e.g., "Zapier", "Salesforce", "Google Sheets")
- `sourceType`: The type of record (e.g., "FormSubmission", "Opportunity", "Booking")
This enables filtering and helps maintain data integrity.
---
## Getting help
### Debugging tools
Use **Zap History** to view detailed error messages and data from each step. Use **Test Mode** in the Zap editor to verify data flow between steps before enabling the Zap.
---
## Still stuck?
If you're still experiencing issues:
1. **Check Zapier's Status Page**
- Visit [status.zapier.com](https://status.zapier.com) for any platform issues
2. **Review Dime.Scheduler API Documentation**
- [API Reference](https://sandbox.api.dimescheduler.com/docs/v1/swagger.json)
- Verify field names and required data
3. **Contact Support**
- Provide your Zap ID (found in Zap settings)
- Include screenshots of error messages
- Share the full error message from Zap History
- Describe what you're trying to accomplish
4. **Zapier Community**
- Search or ask questions at [community.zapier.com](https://community.zapier.com)
- Other users might have solved similar issues
## Avoiding most of this
Nearly every problem on this page traces back to one of four habits not being in place.
Build in **sandbox** and promote to production by creating a second connection rather than editing the tested one. Add a **filter immediately after the trigger** so the Zap only processes what it should, which prevents both noise and rate-limit pressure. Populate **source app and source type** on everything you write, since their absence is what produces duplicates and records nobody can find. And **check the Zap history** on a schedule, because a failed Zap does not announce itself: the workflow simply stops and the people downstream assume the work stopped instead.
## Read more
- [Prerequisites](/integrations/zapier/prerequisites), for connection and API key problems
- [Actions](/integrations/zapier/actions) and [triggers](/integrations/zapier/triggers), for field-level questions
- [Source app and source type](/develop/api/global/sourceapp), the cause of most duplicate and missing records
- [Transaction errors](/administration/transactions/errors), to see what Dime.Scheduler recorded on its side
---
## Agenda
The **Agenda** is a calendar surface that lets a resource - or anyone with the URL - consult the planning of one or more resources without opening the full planning view. It runs at `https://app.dimescheduler.com/agenda` and supports four views (Day, Week, Month, List), a rich appointment details sheet, multi-resource filtering, kiosk/embed mode, and a set of keyboard shortcuts.

## Toolbar
A single toolbar drives the entire agenda. Most controls are context-aware: controls that don't apply to the current view disable themselves rather than disappear, so the layout stays stable when you switch views.
### Date navigation
- **<** / **>** shifts the visible range by one period (one day, one week, or one month, depending on the active view).
- **Today** jumps back to today.
- The date picker lets you jump to an arbitrary date.
### Resource filter
The resource tag input at the centre of the toolbar accepts one or more resources. Adding tags narrows the agenda to those resources; an empty input means "all resources". The choice is reflected in the URL via `?resources=A,B,C` so the filtered view can be bookmarked or shared.
### Group by resource
Day view only. When two or more resources are visible, the **Group by resource** toggle (columns icon) splits the day into one column per resource. Outside day view (or when fewer than two resources are present) the toggle is disabled.
### Zoom
Day and week views only. The two zoom buttons step through four density levels that pair grid height with snap interval:
| Level | Slot duration |
| ----- | ------------- |
| 1 | 1 hour |
| 2 | 30 minutes |
| 3 | 15 minutes |
| 4 | 5 minutes |
Zooming in increases the row height and decreases the slot duration in lock-step.
### Time range
Day and week views only. The time range input bounds the visible hours of the day (e.g. 07:00 - 19:00). Outside those hours the grid is hidden.
### Pin to now
Day and week views only. When enabled, the agenda auto-scrolls to the current time on load and again every minute. Useful for monitors that sit on a wall and need to follow "now" without user input.
### Weekends
Week, month, and list views only. Toggles whether Saturday and Sunday are rendered.
### View switcher
The view switcher on the right swaps between **Day**, **Week**, **Month**, and **List**. The selected view is reflected in the URL via `?view=` so links survive a refresh.
### Refresh and collapse
- **Refresh** (circular arrow) re-fetches appointments. Also bound to `R`.
- The chevron at the right end of the toolbar collapses the toolbar to a thin strip; click the chevron in the strip to bring it back.
## Keyboard shortcuts
Shortcuts work whenever focus is not inside an input or the appointment details sheet:
| Key | Action |
| -------- | ----------------- |
| `←` | Previous period |
| `→` | Next period |
| `T` | Today |
| `D` | Day view |
| `W` | Week view |
| `M` | Month view |
| `L` | List view |
| `R` | Refresh |
Modifier-key combinations (Ctrl/Cmd/Alt) are ignored so they don't collide with browser shortcuts.
## Appointment details sheet
Clicking an appointment opens a side sheet with the full record:
- Subject, body and category accent.
- Start and end (formatted in the user's locale).
- Assigned resources. Each resource name is clickable - clicking adds or removes that resource from the agenda's filter, so you can quickly pivot the agenda onto the resources of the appointment you're inspecting.
- Linked task and job numbers.
- Category and time marker, with their colour swatches.
- Site address (when the linked job has one).
- Planning quantity and priority/importance.
- Created/last-modified metadata, with the user who made the change.
## Multiple resources
Resources can be supplied through several mechanisms (in priority order):
1. `?resources=A,B,C` query string - the canonical form.
2. Legacy `?resource=A` query string - still honoured, mapped to the new `resources` list.
3. The URL path `/agenda/` - the original form, still works.
4. The `data-resourceno` attribute on the host element - used when the agenda is embedded in another page.
The toolbar always emits the canonical `?resources=...` form when the filter changes, so URLs converge after the first interaction.
## Kiosk and embed mode
For wallboards, embedded views inside back-office systems, or any case where the chrome should be hidden, append `?hideToolbar=true` to the URL. The toolbar disappears entirely (no collapsed strip) and the calendar fills the viewport edge to edge.
Combine this with the canonical query string parameters to hard-code the desired view:
```text
https://app.dimescheduler.com/agenda
?view=day
&resources=LINDA,JOHN
&minTime=07:00:00
&maxTime=19:00:00
&slotDuration=00:15:00
&pinToNow=true
&groupByResource=true
&hideToolbar=true
```
## Configuration parameters
All toolbar settings can be set up-front via the query string and are written back to the URL when the user changes them in the toolbar. The full list:
| Parameter | Type | Notes |
| ----------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `view` | string | `day`, `week`, `month`, `list`. Defaults to `day`. |
| `date` | string | `YYYY-MM-DD`. Defaults to today. |
| `minTime` | string | `hh:mm:ss`. Defaults to `00:00:00`. |
| `maxTime` | string | `hh:mm:ss`. Defaults to `24:00:00`. |
| `slotDuration` | string | `hh:mm:ss`. Defaults to `00:15:00`. |
| `slotHeight` | number | Pixels per hour. Driven by the zoom buttons; rarely set by hand. |
| `showWeekend` | boolean | Defaults to `true`. Only meaningful in week, month, and list views. |
| `pinToNow` | boolean | Defaults to `false`. Only effective in day and week views. |
| `groupByResource` | boolean | Defaults to `false`. Only effective in day view with two or more resources. |
| `resources` | string | Comma-separated resource numbers. The canonical multi-resource form. |
| `resource` | string | _Legacy._ Single resource number, mapped onto `resources` for backwards compatibility. |
| `hideToolbar` | boolean | Defaults to `false`. When `true`, the toolbar is hidden entirely (kiosk mode). |
Mobile users typically prefer the [mobile app](/user/mobile) over the agenda for daily use - it's purpose-built for the on-the-go workflow.
---
## Data-driven security
Dime.Scheduler secures access in two layers. The first layer, **role-based access control (RBAC)**, uses roles and user actions to define what a user can *do*. This page covers the second layer: **data-driven security**, which controls what data a user can *see and plan*. A simple matching algorithm decides which tasks and resources appear for each user, and this page explains how to set it up.
## Filter groups & filter values
Filter groups and filter values are how you enforce data-driven security across planners, tasks and resources. A **filter value** is a requirement or restriction you set on one of those entities. A **filter group** is simply a logical grouping of related filter values.
Here are some examples of filter groups and their values:
- Region
- EMEA
- LATAM
- APAC
- Skill
- Servers and networks
- Basic PC knowledge
- Programming
- Department
- Administration
- Production
- Sales
When you plan tasks across geographical regions or multiple companies, the customer may want a dedicated planner for each region. The same goes for assigning planners to specific types of work such as room bookings, ship crews, product assembly, or servicing. These are exactly the scenarios filter values are built for.
You can assign filter values to three entities:
- Users
- Tasks
- Resources
A filter value on a task acts as an eligibility requirement for users and resources. A user must meet a task's requirements to plan it (or even see it). For resources, the requirements determine who is qualified to perform the task.
Dime.Scheduler runs these calculations automatically, so a user only sees the tasks and resources they are allowed to plan. From there, the user plans their open tasks freely. Finding the right resource for each task is a separate concern: as explained in the [resource filters](user/components/resourcefilters.md) section, Dime.Scheduler does not enforce this automatically but gives you the tools to steer the planner in the right direction.

An example makes the mechanism concrete. Using the filter groups and values above, assume the following entity filter values:
| Type | Name | Regions | Skills | Departments |
| -------- | ---------------- | ----------- | ---------------------------------------- | -------------- |
| User | John Doe | EMEA | Basic PC knowledge, Programming | / |
| User | Jane Doe | LATAM | Basic PC knowledge | Sales |
| Resource | Hank Dover | LATAM | Basic PC knowledge, Servers and networks | / |
| Resource | Bill Jensen | EMEA, LATAM | Basic PC knowledge | / |
| Task | Install software | EMEA | Basic PC knowledge | Administration |
Now it is time to plan the "Install software" task. Two levels of data-driven security come into play:
- A user must be authorized to plan the task and must be able to plan at least one resource. This level is automatic and outside the user's control.
- After that, the planner decides which resource to assign to the task.
The user, and optionally the resource, must meet the requirements the task sets. This check runs at filter group level. In this example, the task has requirements in three filter groups:
- The task will be performed in the "EMEA" region
- The task requires a minimum knowledge (skill) of "Basic PC knowledge"
- The task will be done in the "administration" department
Looking at the users, both John Doe and Jane Doe have the required skill (Basic PC knowledge). But Jane Doe can only schedule tasks for the "sales" department and the "LATAM" region, so she is ruled out. John Doe passes every check, even though he has no assigned department filter value. As explained in the [management by exception principle section](#management-by-exception-principle), a filter group is ignored when a user or resource has no assigned values for that group. As a result, Jane Doe never sees the task in her open task list, while John Doe does. The same calculation runs for resources: a user only sees resources they are allowed to see, based on the filter values of both the user and the resources. John Doe will not see "Hank Dover" because their regions differ (EMEA vs LATAM), whereas Jane Doe sees both resources.
The planner decides how and when to plan the task, and to whom to assign it. Unlike the link between user filter values and the task filter, the user can choose whether or not to honor the requirements on the task. A resource may not be qualified for the task, so it is up to the planner to build a realistic plan.
## Categories and time markers
Data-driven security is not limited to filter values and filter groups. Categories and time markers indicate the status of an appointment, which you can also use to enforce security rules. For example, you can filter out appointments with the category "Completed" so users cannot modify the planning of work that is already done.
## Management by exception principle
The data-driven security algorithm for filter values, categories and time markers follows the **management by exception** principle. When no values are specified for a given filter group, the user automatically has access to all values in that group. This keeps things easy to maintain: when new values are added to a group, the user automatically gets access to them, with no extra permission management. The same principle applies to categories and time markers.
## Read more
- [Resource filters](user/components/resourcefilters.md)
---
## Authentication & Authorization

Dime.Scheduler gives administrators granular, role-based access control over who can use which features. On top of that, a [data-driven mechanism](user/authentication/datadrivensecurity.md) validates each user against the data they are allowed to work with.
Securing the application comes down to two distinct areas: authentication and authorization.
## Authentication
**Authentication** verifies that a user really is who they claim to be. Dime.Scheduler supports two [identity providers](user/authentication/types.md): **Azure** and **forms login**.
Azure is managed by another system, which you will likely use for other applications too. Forms login uses credentials that exist only in Dime.Scheduler. Forms login is the quickest way to get started, since it needs no extra configuration or integration, whereas Azure may need to be set up first.
## Authorization
**Authorization** enforces security clearance: it decides what an authenticated user is allowed to do. This is where [**roles** and **user actions**](user/authentication/roles.md) come in. Dime.Scheduler groups features together and protects them with user actions. The administrator assigns user actions to roles, and roles to users. On top of that, [data-driven security](user/authentication/datadrivensecurity.md) restricts access by assigning **filter values**, **categories** and **time markers** to users, which are then compared against tasks and resources.
As noted [earlier](user/profiles-layouts.md), authorization is always granted at the user level. Depending on the setup, different users can see very different projections of Dime.Scheduler. Some user actions are broad, such as "Global Administrator", which unlocks every feature. Others are narrow, such as "Plan Project", which protects the Gantt chart.
:::info
Role-based access control (user actions and roles) defines what users can ***do***. Data-driven security defines what users get to ***see***.
:::
Two users with the same roles can still see different sets of data in the grids and planning boards. The reverse holds too: users with very different roles can see the same data in the components. What they can do with that data depends on their roles, and therefore on their user actions.
## Read more
import DocCardList from '@theme/DocCardList';
---
## Role-based access control (RBAC)
Role-based access control lets you decide exactly what each user can do in Dime.Scheduler. You build roles from protected features, then assign those roles to users and user groups.
## Roles, user actions and user (groups)
User actions are the logical grouping of protected features in Dime.Scheduler. Dime Software defines them, and you cannot modify them.
On their own, user actions do nothing. They only take effect once you bundle them into roles and assign those roles to users and user groups.
A role is simply a collection of user actions. You assemble roles however you like, and you decide which users (and groups) get which roles.
Roles and user actions are independent of each other. Overlaps are fine: a user gets access to all distinct user actions across every role assigned to them.
The diagram below shows the relationship between roles and user actions:

In this example, there are:
- Three roles, each with its own set of user actions
- Two roles assigned to user groups
- One role assigned to a user directly
User 1 receives all user actions from role 3 and role 2. The two roles overlap, but that makes no difference. The result is:
- Update appointment (role 2)
- Paste appointment (role 2)
- Personalization (role 2, role 3)
- Open details (role 2)
## Read more
- [Roles setup](/administration/user)
---
## Authentication Types
Dime.Scheduler supports two ways for users to sign in. Pick the one that fits how your customer manages identities.
### Microsoft Entra ID
Users sign in with their Microsoft credentials, and Azure passes a set of claims to Dime.Scheduler. Dime.Scheduler stores no credentials of its own and works only with the data the administrator has granted. The Entra ID App Registration needs nothing more than the user's email address to authenticate.
:::tip
Users can grant consent individually, or the administrator can grant it on behalf of the whole organization.
For **production**, use [this consent link](https://login.microsoftonline.com/organizations/v2.0/adminconsent?client_id=22e311a5-ec38-4d0c-887b-37f1e60efc54&response_type=code&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&redirect_uri=https%3A%2F%2Fapp.dimescheduler.com%2Fconsent).
To grant access to users on the **sandbox**, use [this consent link](https://login.microsoftonline.com/organizations/v2.0/adminconsent?client_id=c0ad9485-cc69-43ef-8f75-2cf9365abd66&response_type=code&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&redirect_uri=https%3a%2f%2fsandbox.app.dimescheduler.com%2fconsent).
:::
This works much like signing in to other sites with a Facebook or Google account. There are real benefits for everyone. Users avoid keeping separate credentials for yet another application (and the password resets that come with it), and security improves because identity management rests with Microsoft, who can devote far more resources to it than any single vendor.
### Forms
Forms authentication is a standard, password-protected system that adheres to industry security standards. Administrators create users manually in the provided setup view. Each new user receives a welcome email and is prompted to set their password on first sign-in.
If a user forgets their password, there is no cause for concern. The login view includes a link to securely [reset it](user/guides/reset-password.md).
## Read more
- [User administration](administration/user/users.md)
---
## User Groups(Authentication)
User groups let you manage many users as a single entity instead of one by one. A user can belong to several groups, and the administrator decides which groups each user joins.
Suppose an organization wants to group users by region, like this:

Users in a group usually share common traits, so they tend to need the same **privileges** and **data**.
Rather than assigning filter values, categories and time markers to each user individually, assign them to a user group. When a user joins a group, that group's specifications are automatically allocated to the user. These allocations accumulate across groups. In the example above, "User 1" belongs to both groups, so this user can do everything members of group 1 and group 2 can do, such as planning tasks and resources in both the EMEA and LATAM regions.
## Sharing data
Profiles and layouts can be shared in two ways: with **everybody** (public) or with a **user group**.

Every member of a group has access to the profiles and layouts shared with that group. Remove a user from the group and that access is gone.
## Security
### Roles
Managing similar users through user groups also simplifies security, because you can assign roles to a user group. Every member of the group is granted the user actions assigned to it, and loses them as soon as they leave the group. If the user was also assigned to that role directly (or through another group), they may notice no difference at all.
### Data-driven security
You can also apply data-protected security by assigning filter values, categories and time markers to user groups, which are then compared against tasks and resources. For more about data-driven security, see [Data-driven security](user/authentication/datadrivensecurity.md).
## Read more
- [Data-driven security](user/authentication/datadrivensecurity.md)
- [User groups setup](administration/user/usergroups.md)
---
## How it works
## What Dime.Scheduler is designed to do
If you look at the visual diagram below, you'll notice that Dime.Scheduler hinges on the data input of other systems. These systems - typically centralized and mission-critical - provide the necessary data to allow planners to schedule the organization's resources and work.

While Dime.Scheduler is technically able to run without those systems, its power is truly unleashed when it is combined and integrated with the existing technological landscape. As a result, **Dime.Scheduler is only concerned with planning**, nothing else. The main artifact that is produced are appointments, which merely represent what work will be done (the task), by whom (the resource) and when. However, as simple as that may be, it can be tremendously powerful when that information is used by the connected systems, because it can yet another level of automation in the company, providing heaps of benefits.
## How it is designed
Dime.Scheduler is built with performance and scalability in mind. Therefore, it is based on a loosely coupled architecture that separates the main planning application from the connected plugins, which are responsible for interacting with external applications.
As far as the user is concerned, the application consists of nothing more than an application accessible via a web browser.
**Real-time updates** are sent to and from the back-office systems, which means users can keep on working on the latest state of the system - eliminating the need to refresh components or pages manually.
As soon as the user has been authenticated, the application will retrieve the profiles and (defaults) layouts and will apply the security filters so the user gets to see a **tailored version** of Dime.Scheduler. Users and administrators have tremendous amounts of configurable elements at their disposal so it is possible that users will get to see vastly different sets of components and data.
The previous paragraph brings us to a key tenet: **_it's all up to the users_** (and administrators for that matter). Dime.Scheduler is used in a wide array of industries where planning processes can vary greatly. Even within an industry it is likely that organizations will have different requirements and planning methods. That is why Dime.Scheduler offers a heap of components - with their own distinct feature set - and lets the users handle the rest by themselves. In that regard Dime.Scheduler is more like a framework than it is an application that defines the way of working.
The story continues when data is synchronized from and to the back-office systems: organizations may implement their own business logic and their own (vertical) solutions. Dime.Scheduler ultimately provides the framework and the building blocks for the planning process, it is up to the users to decide how they wish to work.
## Key terms
The gist of the above is that Dime.Scheduler extends ERP, CRM or LoB systems with a graphical solution for the planning of tasks and resources. All the features Dime.Scheduler offers are tools for the planner to create and maintain an accurate schedule for the resources who need to carry out those tasks.
The concepts below introduce you to Dime.Scheduler's lingo and how they relate to each other. More detail follows further on in the manuals.
### Resources, types and filters
A **resource** is a representation of an entity that is able to perform tasks and is subjected to a schedule. As explained in the last section, a resource can be anything like a consultant or a moving lift.
A **resource type** arranges resources in logical groups. An example of a resource type in the construction business could be "excavator" or "bulldozer". Just as with the resources, a resource type can be anything, which gives you tremendous flexibility in the planning process.
**Task and resource filters** allow you to define requirements for the resource(s) to carry out a task. For example, in order to execute the "Translate user manual to Spanish" task, a resource would for instance need to possess a C2 CEFR level for Spanish.
Change the task's requirements below and watch which resources still qualify:
### From job to appointment
Traditionally, back-office systems are used to create the work orders and the subsequent tasks that must be completed. Using the **bidirectional synchronization** mechanism, the data is fed into Dime.Scheduler and made available to the user. All the planner needs to do is to find a resource and an appropriate time to carry out the task.
The tasks that are yet to be scheduled are called **open tasks**. Some tasks may be related to the same work order, which is called a **job**. For instance, a job could be to service the HVAC of a large government building. When you break down the assignment, you could identify smaller units of work (i.e. tasks) such as replacing filters, cleaning the coils, inspecting the settings, etc.
As soon as a task is scheduled (i.e. the task has been assigned to a resource on a given date), then it is converted to an **appointment**. This conversion will always be synchronized with the back office using the connectors, just like every subsequent transaction. An appointment contains the data for a particular event, whereas a **planned task** also contains information about the resources that are assigned to the appointment.
### Categories and time markers
Categories and time markers are part of the **indicator** set that identify and label appointments. They can be considered as a reflection of the _status_ of an appointment on a given point in time. Such a status can be easily leveraged to enforce business logic in the back-office systems.
### Profiles and layouts
Profiles and layouts are not fundamental to the planning process but they are fundamental for Dime.Scheduler. In fact, they are part of what makes Dime.Scheduler so flexible. As will be covered in depth in the [components section](user/components/introduction.md), profiles and layouts enable the user to create **personalized workspaces**, which introduces a whole new level of flexibility as profiles and layouts can be reused and shared with other profiles, layouts and user groups.
### Users and user groups
Dime.Scheduler has a granular and data-driven security system. Needless to say, **users** are the basic entities that represent physical users. Users can [log in](user/authentication/types.md) with their Windows or Azure credentials, or they can use the forms authentication that Dime.Scheduler provides.
[**User groups**](user/authentication/usergroups.md) are containers that coalesce users and then acts as a proxy, which makes security management much smoother. It also allows users to collaborate with other users of the group by sharing profiles and layouts.
### Roles and user actions
Dime.Scheduler applies **Role Based Access Control (RBAC)**. Users needs to have security clearance to be able to use protected features. A (static) list of security rules - also named **user actions** - is enforced in the application. An example of a user action is the 'edit appointment' action which protects all editing appointment capabilities.
A **user role** is a group of user actions and can be assigned to users and user groups. Administrators can create user roles as they like and assign user actions. This implies that user actions are not directly assigned to users. Instead, actions are assigned to user roles, and user roles are assigned to users and user groups.
Assign roles to a user below and watch which actions they unlock:
### Links and Action URIs
In contrast to an action URI, links are static and not contextual. They are good old links to other resources accessible via the internet.
An action URI is a dynamic link with placeholders that is populated by properties of tasks, jobs and appointments. For example, this allows you to view the details of a job in Microsoft Dynamics NAV or Microsoft Dynamics 365 Business Central in just one click from Dime.Scheduler.
## Read more
- [Roles and user actions](user/authentication/roles.md)
- [User groups](user/authentication/usergroups.md)
- [Components](user/components/introduction.md)
- [Filter values](user/authentication/datadrivensecurity.md)
- [Profiles & Layouts](user/profiles-layouts.md)
---
## Date Picker
The date picker is the shared date control for the workspace. Several components are scoped to a date range (the [planning board](user/components/planningboard/index.md), the [capacity pivot grid](user/components/pivot.md), the [planned tasks grid](user/components/plannedtasks.md)), and without a shared control the planner would have to set the same range on each of them every time the focus shifts to a different week. The picker solves that by being the single source of truth: change it here, and the components that listen update with it.
It's two-way as well as shared. When the planner steps a planning board forward by a week or zooms a pivot grid to a quarter, that change flows back to the picker. The display always matches whatever range is currently in play.

Colors vary with the theme but the scheme is constant: the darkest shade marks the start and end of the selected range, the lightest shade fills the days in between, and a separate accent marks today.
## Picking a range
A range always has a start and an end (the same day on both is fine, the result is still a range). Two gestures get there.
The first is freehand. Click the first day, move the mouse (no button held) to the last day, click again to confirm. A tooltip during the move shows the duration of the prospective range, which helps when the planner is aiming for "exactly two weeks" rather than a specific end date.

A range can span multiple calendar months. Click the month-shift arrows during selection and the calendar walks forward without losing the active start day.

For a single day, click the same day twice.
The second gesture is the week column. The number on the leftmost column of each row is not a label; clicking it selects the whole week as a range. The fastest way to ask "what's planned this week?"

The selected range applies to every listening component immediately.
## Navigating dates
The date-shift arrows next to the month and year nudge the visible calendar one step at a time. For larger jumps the picker has two quick-jumps: click the month to choose a different month, click the year to choose a different year.


Both keep the current selection intact, so the planner can browse to a future month without losing the range they've already started building.
## Two-way binding with the workspace
The picker is not just an input. The connection runs both directions: any component that holds its own notion of a date range pushes that range back when the user changes it. Stepping a planning board to the next week updates the picker; switching the planning board's view from day to week updates the picker; setting a default start date in the [profile settings](user/profiles.md#settings) updates the picker. Whichever side initiates the change, the date picker and the listening components stay in sync without the planner having to mediate.
## Read more
- [Planning board](user/components/planningboard/index.md)
---
## Details(Components)
The details component is the inspector for whatever the planner currently has selected. Grids and the planning board only show a handful of columns at a time, by necessity, but the planner often needs to know one more field about a particular record without expanding the grid layout or leaving the current view. The details component answers that: select something, look at the panel, see every field the team has decided is worth showing.
The list updates automatically when the selection changes. Open tasks, planned tasks, appointments, picked in any grid, on the planning board, in the Gantt chart, or in the route sequence grid, all feed the same panel.

## How fields show up
The grid has two columns. "Property" names the field; "Value" holds the actual value for the selected record. The properties stay the same for the entire session: they are the fields the admin chose for the team via the [pane layout](administration/settings/panelayouts.md) setup. The values are what change from one record to the next.
That separation is what makes the panel useful. The team agrees once, in the pane layout, what counts as relevant context for the team's work. After that the planner inspects records without making any column decisions, because the columns are already decided.
The list can grow long when the layout is broad. Records routinely have no value for a given property, which produces a lot of empty rows. The fifth button on the toolbar toggles those empties on or off so the planner can choose between completeness (every property, even the blanks) and density (only what is filled in).

## Reference
The component has limited state by design. The two columns cannot be sorted or filtered (the property column is fixed; the value column has one value at a time), so the only stateful control is the hide/show-empty toggle. The pane layout itself is the place that persists what shows up here.
## Read more
- [Grids](user/features/grid.md)
- [Pane Layouts](administration/settings/panelayouts.md)
---
## Gantt Chart
The Gantt chart is the planning surface for projects. Where the [planning board](user/components/planningboard/index.md) handles one-off tasks dropped onto a resource's day, the Gantt chart deals with multi-task work that has shape: phases, dependencies, deadlines, baselines, and dozens or hundreds of related steps.
A construction project is the textbook example. Architectural design has to finish before construction starts, the foundation has to be poured before walls go up, and walls have to go up before the roof. Each phase contains its own breakdown of work, each task has resources, each task has a deadline. Without a Gantt view, the project manager has no way to see the shape of all that, no way to reason about what slips when a dependency moves, and no way to commit a coherent plan to the rest of Dime.Scheduler. The Gantt chart solves exactly those problems: it shows the project as a single tree of tasks on a timeline, lets the project manager reshape that tree freely, and only then materializes the result as appointments on the planning board.
## What ends up here
Projects and their tasks are created in the back-office application; Dime.Scheduler plans them, it does not create them. You bring a project into the chart in one of two ways:
- **From the Gantt itself** with the load-project icon , which opens a window listing every available project [^1].

- **From another component's context menu.** Project tasks in the [open tasks](user/components/opentasks.md), [planned tasks](user/components/plannedtasks.md), and [route sequence](user/components/routesequence.md) grids carry an "Open project" item that drops you straight into the Gantt with the project loaded. The "Task Type" column distinguishes planning tasks from project tasks at a glance.

A loaded project unfolds across three areas: a task list on the left (the work breakdown), a timeline on the right (the bars), and a toolbar above with views, dates, baselines, and the sync controls that protect your changes.
## Reading the project
Looking at a project is a separate skill from editing it, so the toolbar opens with a row of tools dedicated to *seeing*.

A zoom-to-fit button squashes the entire project into the visible range, useful as a reset after deep navigation. View buttons jump the start of the visible range to today, the start of the current week, or the start of the month, and switch between time scales: day, week, month, quarter, year. A local date picker filters the timeline without affecting the rest of the workspace. Two shift buttons walk the timeline forward and back. A row-height button accommodates dense projects.
When the noise of all the tasks gets in the way, several lenses help the project manager focus on what matters.
### Critical path
The critical path is the chain of tasks that, if any one slips, pushes the project's end date. The longer the critical path, the more fragile the schedule, so it doubles as a risk readout: a long red line means tight slack everywhere.

### Progress
Progress evaluates each task against today's date and draws a red line per task: a line to the right of "today" means the task is ahead of schedule, a line to the left means behind. Project managers update the percentage in the task editor or by dragging the small circle on a selected task.

### Baseline
The baseline is the original plan, frozen at sync time. Toggle it on and the baseline bars appear underneath the current ones; that's how a project manager sees a slip without having to remember what the plan used to look like. Without baselines, there is no reliable way to answer "how far have we drifted?"

### Task groups
The task-groups toggle shades the work-breakdown structure so phases are obvious at a glance. Useful in long projects where the hierarchy alone is hard to follow scanning down the list.

### Rollup
Rollup controls whether subtask detail aggregates up to the summary bar. The button on the toolbar applies the project-wide default; the per-task Rollup checkbox in the task editor overrides it for one task at a time.
### Locate project
After scrolling through dates or filtering the date range, you can lose sight of the overall project. The crosshair icon snaps the view back to the project's start date without disturbing the view mode or zoom level.
## Reshaping the plan
The Gantt chart treats every edit as local: nothing is committed to Dime.Scheduler until the project manager explicitly syncs. This is the whole point of the chart. It gives the manager a sandbox to try ideas, undo them, and only push the result when the plan holds together.

The synchronization icon goes red the moment anything changes. Click it to commit; click reload to throw the changes away; or use undo/redo for local rollback. Once a project is synced, those changes leave the Gantt's reach, so undo no longer applies.
### Moving and editing tasks
Most edits happen one of two places: on the timeline directly, where you can drag a bar to move it, drag its right edge to resize, or drag the small triangle on a selected task to set progress; or in the task editor, which holds the full set of fields.

The editor's tabs map to the things a project manager actually needs to control:
- **General** carries the start/end dates, duration, effort, and baseline state. Duration is elapsed calendar time; effort is the hours of work. The two are linked but not equal, and the [scheduling mode](#scheduling-mode) decides which one drives the other. Painting a room over two 8-hour days alone is 16 hours of both duration and effort. Bring a friend in and the duration drops to 8 hours but effort stays at 16, because two people each worked 8.
- **Successors** lists what depends on this task. Same data the [dependency lines](#dependencies-and-constraints) on the timeline encode, just in tabular form.
- **Resources** lists the resources assigned to the work, with the percentage each contributes. See [Resources and appointments](#resources-and-appointments) for how that becomes real planning.
- **Advanced** is where rollup, manual scheduling, constraints, and scheduling modes live.
- **Note** is a rich-text scratchpad for anything that doesn't fit a field.
### Scheduling mode
The relationship between duration, effort, and resource assignment isn't one-size-fits-all. Four scheduling modes let the project manager pick what stays fixed:
- **Normal** schedules from start/end dates and the project/resource calendars. Default, and what most project managers expect.
- **Fixed Duration** locks the start and end and lets effort flex with the resource assignment. A meeting works this way: it lasts an hour regardless of how many people attend, but the total person-hours grow with the headcount.
- **Effort Driven** locks the effort and lets duration shrink as more resources join. Painting that room is the canonical example: more painters, same total work, less calendar time.
- **Dynamic Assignment** locks both duration and effort and adjusts each resource's assignment percentage to make the math work. The project manager says "this takes 40 hours over a week and I'm putting three people on it"; the chart figures out the percentage of each person's time that costs.
The Advanced tab also exposes a **Manually scheduled** flag (which insulates a task from incoming dependencies and constraints) and the per-task **Rollup** toggle described above.
### Dependencies and constraints
Real projects aren't lists of independent tasks. To express "roof goes on after walls", draw a line from the source task's anchor to the dependent task's anchor. A tooltip validates the link as you drag, and the line is rendered with an arrow once you drop it.

Double-click the line (or open the task editor) to set the dependency **type** (start-to-finish, finish-to-start, finish-to-finish, start-to-start) and the **lag**, the gap in days between the tasks. Delete the dependency from the task editor, the line's editor, or the task's context menu.

Where dependencies wire tasks together, **constraints** pin a single task to a date. The Advanced tab offers six flavors: Must start on, Must finish on, Start no earlier than, Start no later than, Finish no earlier than, Finish no later than. Each pairs a type with a target date. If a later edit would break the constraint, the chart prompts: cancel the change, drop the constraint, or move the task to satisfy it.

### Splitting tasks
Not every task runs in one continuous stretch. Split a task from the context menu to break the bar into segments, each with its own start and end. Useful when a five-day task is actually two hours every morning for two weeks; the segments are what get materialized as appointments later.

## Working with task groups
The hierarchical breakdown is what makes a Gantt chart manageable at all. A construction project might have a Construction phase, with a Foundation sub-phase, with Excavation and Pour Concrete as leaf tasks. Hundreds of tasks become navigable once they're grouped. The structure also gives the project manager a powerful lever: editing a group cascades down.
Push the start date of a phase out by two weeks because the architect was off sick, and every leaf task in the phase moves accordingly. Where dependencies exist between siblings, they fire too. In the example below the start of the group has moved; without a dependency, only the directly affected task shifts:

With a dependency between the first and second task, the second moves with the first:

The cascade is the feature, but it is also the danger: small changes at the top can rearrange a lot below. Use it deliberately.
The project itself is the top-most group. You can move the project's start date and the entire tree shifts; you can edit a few project-level fields (start date, calendar, description) but most are managed by the back office.  The icon in the task list distinguishes projects from tasks, and the project editor mirrors the task editor with a tighter set of editable fields:

The greyed-out boxes you see when picking a date are non-working days from the project's calendar. Purely informational in the picker; in the timeline itself they affect task duration.

## Resources and appointments
The whole point of planning a project is to put real people on real tasks. The Resources tab on the task editor is where that happens: pick resources, set each one's percentage of involvement. A 100% assignment is full-time; 20% is one day a week.

When the project syncs, Dime.Scheduler reads those assignments and creates appointments on the planning board automatically. The conversion takes two inputs: the task's dates (or segments) for the appointment boundaries, and the calendars (project + resource) for the actual hours of work that fit inside those boundaries. A task that nominally runs Monday through Friday becomes appointments only on the days and hours the resource actually works. That is how the project stays an abstract plan while the planning board stays concrete.


Generated appointments carry an anchor icon so the planner knows they came from a project, not from a manual schedule.

Planners can still nudge those appointments on the planning board, but the changes are flagged on the task assignment with a warning icon and a grid of every change.


The project manager has the last word. The next sync can override the planner's local edits, with a confirmation prompt to make sure that override is intentional.

Before and after:


## Export
When a project needs to leave the application, a printout for a meeting or a PDF to email, the PDF export button takes a snapshot of the chart and renders it through the export server [^3].

The dialog picks the schedule range (full project, custom dates, or just the visible part), the row range, and the usual print parameters: page format, orientation, pagination, header. Because the export essentially renders the chart as it stands, it helps to maintain a dedicated [layout](user/layouts.md) tuned for printing.

## Reference
The Gantt chart is stateful: column selection, view, baseline toggle, rollup, and so on are saved as a layout and shared with other users like any other [grid](user/features/grid.md).
## Read more
- [Planning board](user/components/planningboard/index.md)
- [Grids](user/features/grid.md)
- [Calendars](administration/settings/calendars.md)
[^1]: When there are cross-project dependencies, Dime.Scheduler loads the entire graph, including projects you did not select.
[^3]: See [the troubleshooting page](/user/troubleshooting/user) for the caveats of the export service.
---
## Indicators(Components)
The indicators component does double duty: it is the legend for the colors a planner sees on the [planning board](user/components/planningboard/index.md), and it is the source for changing those colors. Categories, time markers, pins, and [tags](/user/features/tags) all live here, grouped into a single accordion panel.
Without it, the colors on the board mean nothing to a planner who is new to the workspace, and changing the category on a handful of appointments means opening each appointment card in turn. The component solves both: glance at the panel to read the colors, drag from it to apply them.
## A legend the planner can also use
The panel lists every indicator and its color, organized into three sections that collapse and expand. New planners use it to learn the color scheme; veteran planners use it to remember what an obscure category means.

The same panel is the fastest way to set those values on an appointment. Drag an item from the categories or time markers list and drop it on an appointment in the planning board.

Validation runs during the drag. The drop target must be an appointment; if it is, a green check icon appears in the tooltip and the drop proceeds. A red cross means the target is invalid (a non-appointment cell, for example) and the action is cancelled.

Security still applies on top of the visual validation. Locked appointments, and records the planner does not have permission to update, refuse the drop.
## Bulk update for many at once
Drag-and-drop is the right tool for one appointment, but tedious for fifty. Select multiple appointments on the planning board first, then click the bulk-update icon next to a category or time marker in the indicators panel: the value is applied to every selected appointment in a single round-trip.

## Tags behave differently
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases.
:::
The panel has a **Tags** section that drags and bulk-updates like the rest, with one important difference: dropping a category or a time marker *replaces* the current value, because an appointment only has one of each. Dropping a [tag](/user/features/tags) **adds** it to the tags the appointment already carries. Drop three tags on the same appointment and it ends up with three.
Removing them again is a separate gesture: right-click the appointment and choose **Remove tags** to clear all of them, or open the appointment card to take off just one.
Tags marked as hidden by the administrator do not appear in this panel at all.
## Other ways to set the same thing
Drag-and-drop and bulk update from this panel are the indicators-centric paths. Two others reach the same effect from elsewhere in the workspace:
- The [appointment card](/user/components/planningboard/appointments#appointment-details-1) on the planning board, useful when the planner is already editing that one appointment.
- The bulk-update actions in the [planned tasks grid](/user/components/plannedtasks#updating-appointments-in-bulk), useful when the planner is selecting appointments by query rather than by clicking on the board.
All four routes write the same underlying change; pick whichever fits the gesture the planner is already making.
## Read more
- [Planning board](user/components/planningboard/index.md)
- [Indicators setup](administration/settings/indicators.md)
---
## Introduction to components
import UsersSvg from '/img/assets/user/components/components-header.svg';
import DocCardList from '@theme/DocCardList';
Components are the building blocks you assemble into a workspace. Each one delivers a distinct set of features for planning, and you pick the ones that matter for the job at hand. Most components are also **stateful**: you can store their settings and share them with other users. A stored configuration of a component is called a **layout**.
Add as many components (and their layouts) to a workspace as you need, and arrange them however you like. A saved workspace is called a **profile**. Like layouts, profiles can be shared with other users.
The available components are:
- **Open Tasks**: shows tasks that need to be planned.
- **Planned Tasks**: shows planned tasks in a grid for easier searching.
- **Task Details**: shows detailed information about a selected task (open or planned) or appointment.
- **Gantt Chart**: allows to plan projects using a Gantt chart including dependencies, constraints and multiple planning modes.
- **Map**: shows the map, location of addresses, position of resources and assets, calculated route, travel time and travel distance.
- **Resource Filters**: allows to filter resources on the planning boards.
- **Calendar**: you use the calendar to select a custom date range (other than the presets you find on the planning board) for the planning boards.
- **Category**: shows a legend of the categories, which color means what?
- **Notifications**: shows notifications about a selected task or appointment.
- **Route Sequence**: visualize and change the sequence of a selected route on the map.
- **Capacity**: pivot grid to report on the capacity of the resources.
Every component ships with a standard setup. Dime.Scheduler applies it whenever the profile has no default layout for that component. Think of a component as a placeholder for a set of features you can configure and save. The example below shows two Open Tasks grids, each with its own state. They share the same type, so they offer the same information and capabilities, but they differ in the customizable parameters on each component. The next sections cover those configurable elements in detail.

This granularity (profiles, layouts and components) lets you build a workspace that matches your preferences. Layouts are **reusable**, for instance: you can apply the same layout to a component in another profile, like this:

Many components are connected to support the planning process. A key example is drag and drop: you drag open tasks and drop them on the planning board. Each component's page covers the interactions that originate from it. For now, keep in mind that they exist and that the component type, regardless of the layout applied to it, determines its capabilities.
## Quick start
## Read more
- [Profiles and layouts](user/profiles-layouts.md)
---
## Map
The map plots jobs, tasks, appointments and resources on a geographic surface. A grid is great for "who, when, what", but blind to the most important variable for any planner who sends people on the road: *where*. The map adds that variable and quietly turns the rest of the planning view into a logistics tool.
Without it, the planner picking a resource for a customer site has to guess who's nearest, and the planner sequencing a day of stops has to keep the geography in their head. With it, the same decisions are visible at a glance, and Dime.Scheduler can compute distance, travel time, and route lines on top.
## What appears on the map
The map listens to the rest of the workspace: select an entity anywhere (open/planned tasks, planning board, Gantt, route sequence) and its location, if available, shows up here as a pin. Select several and you get several pins, with the zoom adjusted to fit them all. You can even select across components, useful when the planner is weighing whether a resource handling a planned task could also take an open task nearby.

The pin a record gets depends on the entity behind it.
### Jobs
Jobs hold the address that drives every task and appointment they spawn. Any appointment or task backed by a job with a valid address is a candidate for the map; anything without coordinates is silently skipped. See [Geocoding](administration/settings/geocoding.md) for what it takes to give an address coordinates.
### Resources
Resources can carry two locations at once:
- A **home address**, which marks HQ or where the resource starts the day.
- An **actual or current address**, fed by an external tracking system or by the [mobile app](/user/mobile).
When live tracking is active, the actual location shifts as the resource moves [^1]. An urgent service call comes in; the planner checks the map and dispatches the resource that's already five minutes away instead of the one back at the depot. Without that view, the same decision is a phone call and a guess.
:::info[Sharing live locations]
Any system can post resource locations through our [API](/develop/guide/resourcegpstracking), directly or via back-office systems such as BC/NAV. The mobile app does it out of the box.
:::
A toggle on the map switches between resource home addresses and live locations, or hides resources from the map entirely. Switching modes clears the current resource markers and renders the other set; routes that involve resources are cleared too, since their geometry no longer applies.

Hover any pin to see a tooltip with the resource's display name. To leave the names permanently visible, use the "Sticky popup" icon in the upper-left corner.

When nothing else is selected and the planner clicks a resource on the planning board, the map navigates and zooms to that resource automatically.
### Marker colors
Pins are color-coded so the eye can group at a glance:
- **Blue** for open tasks
- **Green** for planned tasks and appointments
- **Purple** for resources
The back office can override these defaults via the pin indicator, which is how teams give each vehicle type, order priority, or skill set its own color scheme. Useful when the planner has dozens of pins on the screen and needs to tell categories apart without hovering.
## Routing
Pins are the start of the story; routes are why the map earns its keep. Dime.Scheduler computes the road between the selected stops, with distance and travel time, by handing the geometry off to PTV Group, a long-standing routing engine.

### Route profiles
Real-world routing isn't one-size-fits-all. A 38-ton truck cannot take the same line as a delivery van; an emergency vehicle has options a passenger car does not; a bicycle has its own set of allowable streets. **Route profiles** are the presets that tell the routing engine which parameters to use: vehicle type, weight, dimensions, tunnel restrictions, low-emission zones, load type, and a long tail of others.
PTV ships a standard set to start from (Default, Fast car, Slow car, Delivery, Emergency, Fast truck, Slow truck, Bicycle, Pedestrian) and Dime.Scheduler lets you customize beyond that. The active profile is picked up from the user profile first, falling back to the global application setting. Set it per planner where the work demands it; set it globally where one profile fits the whole team.
### Automatic and manual routes
With [automatic route calculation](/administration/setup/map) on, the route redraws every time the planner selects a new task or appointment, in the order it was selected. With it off, the planner clicks the pins one by one to compose the route deliberately, or uses [bulk select](#selecting-in-bulk) to grab a region.
Turn on the [route-sequence view](/administration/setup/map) (globally or per profile) to render the order as numbers in each pin. Manage that order in the [route sequence component](user/components/routesequence.md), which doubles as a list view of the same trip.
Clear a route with the route button , or by deselecting the tasks and appointments that built it; the pins go away and the route with them.
## Optimizing the round trip
:::info[Status: Preview]
The round-trip optimizer is currently in preview. Functionality and behavior may change between releases. Not recommended for production yet.
:::
A daily route for a resource is the classic Traveling Salesman Problem: start at home, visit every stop, finish back at home, minimize total time. Dime.Scheduler treats this as a first-class concept (a **round trip**) with the resource's home/actual address as the fixed start and end, and the waypoints in between as the variable.
To create one, select a cell on the planning board and press **R** (or right-click and choose **Show daily route**). The map renders the trip; the [route sequence grid](user/components/routesequence.md) lists the stops with cumulative travel and working time.
Hand the trip to the optimizer with the sparkle button, available when **Route optimization** is enabled in [preview features](/administration/setup/preview#solver--optimization) and the tenant has the **Advanced map** add-on. The solver returns a new order that minimizes travel time and distance; the route on the map and in the grid update in place. Optimizing alone does not write back to the planning, so the planner can experiment freely.
Add more appointments to the round trip, pull in open tasks, reoptimize. When the daily route is right, confirm it to push the new order to the planning board.
## Selecting in bulk
Picking pins one by one is fine for a few stops and tedious for a planning region. Bulk select lets you grab everything inside an area with `SHIFT + drag` (which also zooms to the area) or `SHIFT + CTRL + drag` (same selection, no zoom).

A small filter on the map decides which marker types are eligible: open tasks, planned tasks, resources, appointments. All four are on by default, but it pays to narrow them when the planner only wants resources or only wants tasks. The setting lives in the browser's local storage, so it carries across profiles.
## Following task containers
A task container bundles related tasks. The map respects that bundle: select one task from a container in the open tasks grid and every other geocoded task in the same container shows up on the map automatically. Most useful in scenarios with built-in dependencies (a load task at the depot plus the unload task at the customer), where seeing the geographic spread of the bundle helps the planner spot inefficient pairings before they're committed.
## State
The map stores its settings in the browser's local storage, not the user profile, so they apply across profiles on the same machine:
- Sticky popups on/off
- Show traffic
- Show truck attributes
- Resource location mode (home vs actual)
- Bulk select marker types
## Shortcut keys
| Shortcut | What it does |
| ------------------------- | ------------------------------------------------------------- |
| `SHIFT` + drag mouse | Bulk-select markers in the dragged area and zoom to it |
| `CTRL` + `SHIFT` + drag mouse | Bulk-select markers in the dragged area without zooming |
## Read more
- [Indicators](administration/settings/indicators.md)
- [Map configuration](/administration/setup/map)
- [Action URIs](user/features/actionuri.md)
- [Route sequence component](user/components/routesequence.md)
- [Geocoding](administration/settings/geocoding.md)
[^1]: When activated in the license and sent by the back office.
---
## Notifications
The notifications component is how the back office talks to the planner. Customers go on hold, equipment fails, addresses develop access restrictions, and any of those facts can wreck a schedule if the planner doesn't know about them before committing. Without a place that surfaces those notes inside Dime.Scheduler, the planner has to switch systems to find them, or worse, misses them entirely.
The component answers that by surfacing every relevant note as a row in a grid, scoped to whatever the planner is currently looking at. The notes themselves are owned by the back-office application, which means the source of truth doesn't move and nothing in Dime.Scheduler edits them; the component just makes sure the planner sees them at the right moment.

## Following the selection
When no task or appointment is selected, the grid lists general notifications, the ones the back office wants every planner to see regardless of context: company-wide announcements, system alerts, holidays.

Pick a task or appointment in any grid, on the planning board, or in the Gantt, and the grid narrows to the notifications attached to that one record. Switch selection and the grid follows. The pattern matches the [details component](user/components/details.md): the planner moves their attention, the contextual components update with it.

## Severity at a glance
Every notification carries one of three severities: **Information**, **Warning**, or **Error**. The grid itself shows them with their full text, but the [planning board](user/components/planningboard/index.md) renders a more compact signal: a small colored bar on the left of the appointment that turns the severity into a visual scan.
An informational note:

A warning:

An error:

When an appointment has more than one notification, the bar shows a marker for each severity present, so the planner can tell at a glance which categories of notes are stacked on it without having to open the appointment:
 at least one error
 at least one error and one warning
 at least one of each
A bar by itself isn't the whole story (the planner still needs to click through for the text), but it is enough to make sure nothing slips by unnoticed.
## Task notifications
Tasks carry notifications too, with the same severity model. The difference is that they are not rendered as a colored bar in the [open tasks](user/components/opentasks.md) grid the way appointments are on the planning board. They still surface in this component when a task is selected, so the planner sees them before they drop the task onto a resource.
## Reference
The grid is stateful, like the other [grids](user/features/grid.md), so column selection and layout persist across sessions and can be shared.
## Read more
- [Planning board](user/components/planningboard/index.md)
- [Open Tasks](user/components/opentasks.md)
- [Grids](user/features/grid.md)
---
## Open Tasks
import { FaCalendarAlt, FaEraser, FaArrowLeft, FaArrowRight, FaSearch, FaSave, FaPlus, FaCog } from 'react-icons/fa';
The open tasks component is the planner's inbox. Every task the back-office application sends to Dime.Scheduler that still needs scheduling shows up here, in one filterable grid, until it lands on a planning board.
Without it, a planner has to chase work through the back-office system, spreadsheets, and ticketing tools, and every gap there means a missed appointment, a double-booking, or the wrong resource on a job. The grid solves that by surfacing every unplanned task in one place, decorated with the metadata the planner needs to decide who should do it and when. What a given planner sees is still gated by security and their own filters, but the principle is the same: nothing that needs planning is hidden from the person doing the planning.
:::tip[Open tasks, defined]
An **open task** is a task that has not yet been (entirely) planned. Budgets can require a certain amount of working time before a task drops off the list, which is useful when one task is meant to be split across several appointments.
:::
## What ends up here
Two kinds of tasks share the grid, because they belong to two different planning workflows:
- **Planning tasks** are independent units of work. Drag one onto a planning board and you have created an appointment.
- **Project tasks** belong to a larger project and are managed by the [Gantt chart](user/components/gantt.md). They cannot be dragged onto the planning board directly. To schedule one, open its project from the row's context menu and plan it from the Gantt.
The "Task Type" column tells them apart at a glance. Add it to the grid if you work with both kinds.
## How planning happens
Planning a task is a single gesture: drag a row, drop it on a time slot on the planning board, release. The drop position fixes two things at once, the resource (which row you drop on) and the time (where on the row), and those two together are everything an appointment needs.

The gesture scales. Select several rows (the row checkbox, CTRL-click, SHIFT-click, or the header checkbox for the whole page) and drop them as a group. By default the tasks line up *consecutively* on the same resource; hold CTRL while dropping and they go on *simultaneously* instead, which is what you want when several resources do the same work at the same time.
Containers extend the gesture further. A task container bundles related tasks (a classic example is loading a lorry at the depot and unloading it at the customer site), so planning any task in a container automatically plans every other task in it. The planner does not have to remember the relationship; the data does. When the situation calls for ignoring the container for once, hold SHIFT *before* you start dragging to plan only the task in hand.
## How the component helps you decide
A drop on the wrong resource is still a drop, so the component does what it can to make the right decision the easy one.
Tasks arrive carrying filter values from the back office: skills required, regions covered, departments involved. The filter icon next to a row turns those values into a one-click resource filter on the planning board.

Click it and the board narrows down to the resources eligible for the work. If a [resource filter component](user/components/resourcefilters.md) is on the workspace, it picks up the same values automatically. When a filter is active, the clear-filter button turns red so the state stays visible:
Some constraints come from the back office and are stricter than a hint. **Time windows** are enforced when the application is [configured](/administration/setup/planning#respect-allowed-date-constraints) to do so: drop outside the allowed window and the planning is rejected outright.

**[Requested dates](/administration/setup/planning#applying-requested-dates)** go further still. When the feature is enabled globally, the requested date overrules the planner's drop position, so an agreed commitment with a customer stays intact without anyone having to remember it. **[Requested times](/administration/setup/planning#applying-requested-times)** work the same way for the hours of the day, leaving the date flexible. The pattern is the same in both: the back office encodes a promise; the grid carries it through to the schedule.
## Side actions
The row's context menu also offers a couple of jumps that are not about planning at all, but about reaching the data behind a task:
- **Task links and action URIs** open the document, the web page, or the back-office record the task points to. Static URLs and dynamic action URIs (links with placeholders that the system fills in from the task data) both appear in the same submenu.
- **Open project** loads the project that owns the task into the [Gantt chart](user/components/gantt.md). Only active for project tasks.


## Reference
Because the grid hands every row to drag-and-drop by default, copying cell text needs an explicit mode switch on the toolbar. See [Copy data](user/features/grid.md#copy-data) on the grid reference for how it works.
| Shortcut | What it does |
| -------------- | ---------------------------------------------------------------------------------- |
| `CTRL` + drop | Plans the selected tasks **simultaneously** on the resource, not consecutively |
| `SHIFT` + drag | **Ignores task containers** for this drag, so only the task in hand gets planned |
## Read more
- [Data-driven security](user/authentication/datadrivensecurity.md)
- [Grids](user/features/grid.md)
- [Action URIs](user/features/actionuri.md)
---
## Pivot
The pivot grid is the planner's overview lens for capacity. The [planning board](user/components/planningboard/index.md) shows what's scheduled, appointment by appointment, but it cannot answer "did we balance the week?", "are some resources overbooked while others are idle?", "can we still take on more work?" Those questions need totals, comparisons, and aggregation across time and groups, which is exactly what the pivot grid does.
It compares the **actual schedule** on the planning board against the **projected capacity** maintained in the back office. Slice that comparison by any combination of resource attributes, time buckets, and aggregators the situation demands, save the result as a reusable layout, and the planner has a load-vs-capacity dashboard customized for their own work.

The component is built from three areas: a configurator on the right where the planner shapes the report, the pivot table itself in the middle, and a top bar with state-management controls.
## Shaping the report
Reports come from dragging fields onto axes. The left column of the configurator lists every available field in three groups, kept apart only to help the planner find what they want:
- **Resource fields** like department, type, team
- **Date fields** like day, week, month, quarter
- **Value fields** like planned quantity, capacity, load, variance
Every field added to an axis becomes a new level. A common report puts a resource grouping (department, then resource type, then resource) on the rows, time buckets (quarter, week, day) on the columns, and load on the values. The result is a multi-level capacity report where the planner can collapse to the top level for the boardroom view and expand to the leaf for the operational view.

### Rows, columns, values
The **row labels** sit on the vertical axis. The first item in the list is the outermost group, the second nests inside it, and so on. This configuration...

...produces this report:

**Column labels** work the same way for the horizontal axis...


...and the **values** column at the intersection holds the aggregates:


### Sorting and filtering
The cog wheel on each field opens a context menu that sorts and filters that axis.

**Label filters** operate on the axis itself: report on one resource type, or one week, and the pivot drops everything that does not match. **Value filters** operate on the aggregated numbers instead, and apply at the lowest level of the pivot (capacity per resource per day) before rolling up. That distinction matters: a value filter like "load above 100%" finds individual overbooked resources, not departments whose averaged load exceeds 100%.
### Reordering, totals, layout
Reorder labels and values by dragging within the configurator, or via the field's own menu.


The cog wheel in the configurator header opens layout-wide settings:

Subtotals and grand totals can be placed at the top or bottom of their group, separately for rows and columns. Row labels can also use an outline layout (one column per label, sortable per level) or a compact layout (a single tree column that collapses and expands, sortable only on the first level).
## Reading the report
The pivot table itself looks and behaves like the other [grids](user/features/grid.md), with a few differences. Configuration lives in the configurator, not the column headers, and the table is multi-dimensional, so the [+] and [-] icons walk between levels.

Every group aggregates the data beneath it, so the higher the level, the more averaged the picture. The grid uses color to flag risk: when capacity exceeds 100%, the **Load** and **Variance** cells turn red. That cue alone is usually enough to spot the day, week, or resource that needs attention. Double-click a cell to drill down into the underlying records and see exactly which resources are over-allocated:

The top bar adds two pivot-specific buttons that collapse or expand every row and column in one click, and a date picker that scopes the report. The pivot's date picker behaves like the [date picker component](user/components/datepicker.md), but local: when it has a value, it overrides whatever date range the date picker component or planning board would otherwise push to the pivot.
## How capacity is measured
Four aggregators give the report its meaning. Two come straight from the data; two are derived.
- **Actual** reports the total planning quantity of a resource's appointments in the cell's time bucket. Planning quantity is normally hours (a one-hour appointment counts as one), but it can be any unit, fed by a conversion factor from seconds in the appointment's duration.
- **Load** is the projected capacity for the resource in that same bucket. Maintained in the back office and pushed to Dime.Scheduler. For predictable resources (a lorry's payload is ten tonnes), load is straightforward; for less predictable ones (a consultant), it requires more thought. Load must use the same unit as planning quantity, or the comparison is nonsense.
- **Capacity** divides actual by load, giving a percentage of utilization.
- **Variance** subtracts actual from load, giving slack in the underlying unit.
Capacity and variance answer the planner's headline question. Below 100% capacity with positive variance means there is room for more; above 100% with negative variance means the resource is overbooked. The red highlighting on those cells turns the answer into a visual scan.
### Multi-day distribution
Multi-day appointments raise a wrinkle: how should their capacity be reported across the days they span? Three modes, picked in the [application setup](/administration/setup/planning), let the answer match the industry:
- **None**: all the capacity lands on day one of the appointment.
- **Constant**: the full capacity repeats on every day.
- **Even**: the planning quantity is divided across the days.
A worked example. A rental customer takes equipment at 9 AM and returns it 56 hours later. The same booking is reported very differently:
| Mode | Day 1 | Day 2 | Day 3 |
| --------- | ----- | ----- | ----- |
| None | 56 | 0 | 0 |
| Constant | 56 | 56 | 56 |
| Even | 18 | 18 | 18 |
For rental, constant is the only mode that tells the truth, because the equipment is unavailable for every one of those days, not just the first. Even mode would mislead a planner into a duplicate booking on days two or three. The mode is a global setting precisely because the right answer is industry-dependent rather than per-report.
## Connections to the workspace
The pivot does not load on its own when you drop it into a profile; hit refresh on the top bar to populate it. From then on, it picks up its date range from any [date picker](user/components/datepicker.md) or [planning board](user/components/planningboard/index.md) in the same profile, unless the pivot's own date picker has a value (in which case the local one wins).
The pivot also listens to the [resource filters component](user/components/resourcefilters.md) the same way the planning board does, so the planner can query the capacity of just the resources matching a particular skill, region, or qualification.
## Reference
Like the other grids, the pivot is stateful. Save a layout to turn an ad-hoc report into a reusable template; share it with the team to give everyone the same view. The saved template holds the rows and columns, sorting and filtering, aggregates, totals configuration, the row-label layout, and the collapsed/expanded state of the configurator. The only thing that does not persist is in-place sorting done on the table itself (rather than in the configurator); for permanent sorting, configure it in the configurator.
## Read more
- [Grids](user/features/grid.md)
---
## Planned Tasks
The planned tasks component is the planning, in grid form. Every appointment the planner has scheduled, with the resource it's assigned to and all the metadata the back office sent along, listed as rows. Where the [planning board](user/components/planningboard/index.md) is the calendar UI for *making* the plan, this is the query UI for *interrogating* it.
That distinction matters because a calendar view, however well-designed, cannot answer questions like "how many appointments do we have for customer X this week?", "show me everything tagged urgent across departments", "bulk-change the category on the 40 appointments for project P". The planning board has no group-by, no bulk-edit toolbar, no filter on arbitrary columns. The planned tasks grid does. As a side benefit, the grid can also push selections back to the planning board, which makes it just as useful for navigating the plan as for analyzing it.
Apart from the unique features below, the component behaves like every other [grid](user/features/grid.md) in Dime.Scheduler.
## Updating appointments in bulk
Once one or more rows are selected, a row of contextual action icons appears on the toolbar:
The first three are bulk updates. Each one opens a modal that applies a single value to every selected appointment [^1]:
- **Importance** for marking a batch of appointments more or less urgent.
- **Category** for retagging.
- **Time marker** for relabeling.
These are protected operations. The corresponding `importance bulk update`, `category bulk update`, or `time marker bulk update` user action must be assigned to one of the planner's user roles for the icon to do anything.
## Driving the planning board
The other two contextual icons are the planning board's side of this grid's purpose. Because every planned task points to a resource, selecting rows and clicking tells every planning board in the profile to show only the resources behind those selected appointments. The companion button clears the filter; it turns red while a filter is active so the state stays visible.
This is what makes "show me the planning of every resource on project P" a single gesture: group the grid by project, select the project's rows, hit filter, look at the resulting board.
The filter coexists with the standard [resource filter](user/components/resourcefilters.md) and with the column filters in the planning board's resource grid. When more than one is in play they combine: a resource must satisfy *every* active filter to appear. That stacking is powerful, and confusing the first time you encounter it. The rule of thumb: if a planner expects to see resources and the board is empty, an unintended filter is usually the cause.
### Navigating to an appointment
For one-off lookups, double-click a row to jump to its appointment on the planning board. The board adjusts its date range if the appointment falls outside the current window, and the appointment itself flashes yellow briefly so the eye can find it. Resources that sit on a different page of the resource grid are not pulled in automatically, so the planner may need to filter the resources or page through to find them.

### Persistent highlighting
A double-click jumps once. **Highlight** marks a set of appointments persistently across the planning boards in the profile, the way the Gantt chart's [critical path](user/components/gantt.md#critical-path) marks a chain of tasks. Select one or more planned tasks and two buttons appear in the top bar: to turn on the highlight, to clear it.
Highlighted appointments stay marked until the planner clears them, which makes them useful for following a thread of work across navigation: pick the appointments that share a customer, highlight them, then navigate the board freely; the highlighted ones stay easy to spot.

As with the resource filter, the resource may still sit on a different page; use the resource-filter button on this grid to bring the highlighted set together. The [planning board's highlight features](/user/components/planningboard/toolbars#highlight-appointments) cover what else changes on the board when a highlight is active.
## Scoping the data
The planned tasks grid does not apply a default date filter, so it shows every appointment by default. Filtering by date through the start/end columns works but is awkward when the planner wants to flip between weeks. The [date picker](user/components/datepicker.md) component is the shortcut: set a range there and the grid scopes to it, alongside whatever column filters are also active.
## Side actions
When a selected planned task is a project task (visible in the "Task Type" column), the row's context menu carries an **Open project** item that loads the project into the [Gantt chart](user/components/gantt.md). Same gesture as in the open tasks and route sequence grids.
## Reference
The grid is stateful: layouts (columns, filters, sorting, page size, the date scope) save and share like any other [grid](user/features/grid.md).
## Read more
- [Grids](user/features/grid.md)
- [Open tasks](user/components/opentasks.md#how-the-component-helps-you-decide)
- [Action URIs](user/features/actionuri.md)
[^1]: Bulk update relies on the planning board, so the active profile must contain at least one planning board for the actions to take effect.
---
## Working with appointments
import ReactPlayer from 'react-player'
import { FaCalendarAlt, FaCompressAlt, FaBox, FaEraser, FaArrowLeft, FaArrowRight, FaSearch, FaSave, FaPlus, FaCog, FaFilePdf, FaAngleDoubleRight, FaAngleDoubleLeft, FaArrowAltCircleRight, FaArrowAltCircleLeft } from 'react-icons/fa';
## Viewing appointments
### Visual indicators
Each appointment is rendered according to its own properties. Alongside the subject, a few visual indicators tell the planner more about the appointment at a glance.
The video below shows several of these indicators in action:
#### Category
Shown as the color of the appointment. The back-office application can drive this value, and you choose what each color means when you set up the application (for example order type, status, or truck load).

#### Time marker
Shown as the colored horizontal bar on the appointment. The back-office application can drive this value, and you choose what each color means when you set up the application (for example order type, status, or truck load).

#### Importance
Shown as the exclamation mark or arrow at the start of the appointment bar. The back-office application can drive this value, and you choose what it means when you set up the application (for example importance, priority, or urgency).

#### Tags
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases.
:::
Shown as a row of small icons at the top of the appointment bar, each in its own color.

Tags are the only indicator that is not single-valued: where an appointment has exactly one category and one time marker, it can carry as many tags as you like, and they render side by side. That is what makes them the place for everything that does not fit in the one category slot - urgent, night shift, waiting on parts. Hovering an icon shows the tag's name.
Because several tags share one line, they are read by shape as much as by color. An appointment with four icons is telling the planner four separate things without spending a single character of the subject.
Read more about [tags](/user/features/tags), including how to apply and remove them.
#### Notification
Shown as the colored vertical bar on the appointment. It signals that the appointment has notifications (coming from the back office) in the notifications component.

The color depends on the severity level of the notification:
- Red in case of errors
- Orange for warnings
- Blue for info messages
Each severity present is represented in the notification bar. This gives the planner an instant overview of which types of message exist for the selected record.
This  has at least one error message. This  has at least one error message and one warning message. Finally, this  has at least one error, one warning, and one informational message.
#### Multi-assignment
When an appointment has more than one resource, this indicator marks it:

#### Linked appointment
The chain icon means the appointment was generated automatically by the Gantt chart. When a Gantt task has a resource assigned to it, Dime.Scheduler creates a planning for that task and resource, and any change to the task is then reflected in the planning board. The icon is a warning that changes you make to this appointment will be overwritten by the project's planning.

### Tooltips
Different tooltips appear depending on what you are doing with the appointment:
- When dragging the appointment across the planning board:

While you drag, the appointment validates whether the drop is allowed. For example, when the appointment is bound to a task with requested dates, or when a resource type only allows one of its resources to be assigned to an appointment.
When the drop is invalid, the tooltip changes:  and the drop is rejected.
- When hovering over the appointment:

- When hovering over an empty time slot:

### Appointment details
Selecting an appointment on the planning board updates the [details component](user/components/details.md) with the appointment's information. This lets you scan its properties quickly without opening the [editor](#the-appointment-editor). You can also show the same grid in a separate window with the `Details` item in the context menu.
## Manipulating appointments
An appointment (or event) is the graphical representation of a task allocated to a resource on the planning board. You can interact with appointments in several ways, covered below.
| Capability | Description |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| [Creating manual appointments](#creating-manual-appointments) | Create appointments manually in the planning board |
| [Drag & drop open tasks](#drag-and-drop-open-tasks) | Plan tasks through drag and drop |
| [Modifying appointments on the planning board](#modify-appointments-on-the-planning-board) | Modify the duration, dates and resources on the planning board directly |
| [Modifying appointments via the editor](#the-appointment-editor) | Update appointment details via the editor |
### Creating manual appointments
In the default Dime.Scheduler flow, jobs and tasks come from the back-office applications, are converted into appointments in Dime.Scheduler, and are then sent back to the back-office applications. Planners sometimes need appointments that are not backed by a task or job. These are called **manual appointments**, and you can create them in two ways.
The first is through the **_context menu_**. Right-click an empty cell in the planning board and select the 'New' option to open the appointment editor. Enter a subject and the record can be created. The default duration depends on the view you are working in: a time slot equals one hour in the day view, and one day in the other views. Dime.Scheduler takes the start date you selected and adds one time slot to calculate the end time. You can change any of these values afterwards in the appointment editor.
The second is a **_drag create_** operation. Click and hold, then drag the mouse to the right. When you release, the appointment editor opens. As with the context menu, the resource and dates are filled in from the surrounding context. The only required field is the subject, which unlocks the save button.
The screen capture below shows both:

### Modify appointments on the planning board
You can manipulate appointments by dragging and dropping the record on the planning board.
To change the duration, drag the appointment from either side using the handle at its edge:

When the duration is correct but the dates are not, drag the appointment to a different date:

The drop location does not have to be on the same resource. Drag the appointment to another resource to assign it there, and on drop the appointment is reassigned. Hold the `SHIFT` key at the start of the drag operation to switch the assignment planning mode from '_replace_' to '_append_'. In append mode, dropping the appointment on another resource adds that resource to the appointment's assignments, meaning the resources will perform the task together.

Keep in mind that this operation does not create a new appointment. It adds the resource to the assigned resources list, and the underlying appointment stays the same.
### The appointment editor
The appointment editor (also called the appointment card) holds all the details of an appointment, split across several tabs. It is available only in the planning board component, and you can open it in the following ways:
- For existing appointments:
- Using the context menu on an existing appointment and selecting the `Edit` item
- By double clicking on the appointment
- For new appointments:
- Using the context menu on an empty slot and selecting the `New` item
- After creating a new (manual) appointment with the drag and drop feature
The first tab, appointment details, groups the record's metadata. The second tab focuses on the resources for that appointment. All subsequent tabs are **appointment templates**, which the administrator manages.
A combobox also reveals additional options for creating a recurring appointment. Read more about [recurring appointments](user/features/recurring-appointments.md).
#### Appointment Details

At the top you can modify the following fields:
- Category
- Time marker
- Importance
- Ignore calendars
The **_categories_** and **_time markers_** a user sees when opening the combobox depend on their permissions, since administrators can assign categories and time markers to users and user groups. There is also a visual aid above the editor's submenu, showing the category and its color across the appointment card.
The fourth item, the **_ignore calendars_** button, appears only when the administrator has switched on [calendar mode](/administration/setup/planning).
In the appointment details tab you can change:
- Date and time
- The subject and body of the appointment
You can also view the job or task the appointment is linked to and check the capacity of the assigned resources.
#### Resources
In the resources tab, select resources from the different resource types to link multiple resources to a single appointment. When it is allowed, that is [configured in setup](administration/settings/resources.md#resource-types), you can select more than one resource from a resource type combobox.

#### Appointment Templates
As noted earlier, the appointment card may have additional custom tabs. These come from the [setup](user/features/appointment-templates.md) of Dime.Scheduler and are specific to your organization. Check with one of the application managers to learn what each of those fields is for.
### Drag and drop open tasks
The last way to manipulate appointments is through the [open tasks grid](user/components/opentasks.md#how-planning-happens). Drag a task from the grid and drop it on the planning board, and Dime.Scheduler converts the task into a scheduled appointment. This conversion brings a few additional features.
#### Data mapping
The conversion maps data: some fields from the task entity are copied to the appointment entity, and the appointment keeps a reference to the original task. A task is also re-evaluated every time a planned task is created, updated, or deleted. This evaluation determines whether the task is still 'open'.
Properties such as importance and planning quantity are copied to the appointment. The task entity also has a 'Subject' and 'Body' field (not shown in the open tasks grid) that are populated using [field templates](administration/settings/fieldtemplates.md). These templates use placeholders that are replaced with data from the task and its underlying job.
#### Requested dates
Tasks can carry requested dates, which only take effect when the global configuration enables this [mode](/administration/setup/planning#applying-requested-dates). Requested dates enforce the start and end date of a planned task. When the configuration is enabled and a task has a "requested start date" and/or "requested end date", the planner is overruled. If the requested dates are open-ended (no end date), the original duration, also specified on the task, is applied.
These rules do not apply to manual appointments, which makes sense given there is no task to enforce them.
#### Requested times
Requested times work almost the same as [requested dates](#requested-dates), with a slightly narrower scope. Instead of setting the date and time, this feature sets only the times of the appointment. Like requested dates, it must be activated in the [global configuration](/administration/setup/planning#applying-requested-times).
When both requested dates and times are activated and a task has both sets of data, the 'Requested date' fields are used instead of the times.
### Enforce date window constraint
Beyond the automatic date and time assignment in the open tasks list, the same validation rules can apply on the planning board. When the [configuration](/administration/setup/planning#respect-allowed-date-constraints) is set and a task specifies '**earliest allowed**' and '**latest allowed**' dates, the planner has freedom within that frame.
When you plan outside the time window, the tooltip warns you, and dropping outside the window is rejected. This adds an extra layer of validation. Imagine how you would feel if your planner dispatched you, a proud owner of a hot dog cart, to the Yankee stadium only to find the game finished three hours ago.

These boundaries are enforced even when tasks specify requested dates. That can be a problem, because it can spiral the task into a deadlock, so check the data before sending it to Dime.Scheduler.
These rules do not apply to manual appointments, which makes sense given there is no task to enforce them.
---
## Context menu
Right click an appointment or an empty slot on the planning board to open the context menu, which gives the planner quick shortcuts for working with appointments. Two factors determine which functions a user sees:
- Security clearance
- Whether the click lands on an existing appointment or an empty slot
The full list of menu items is described in the table below:
| Function | Description | Protected by user action | Context |
| --------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------ | -------------- |
| Go to today | Navigates the planning board to the current day | N/A | All |
| Open in day view | Changes the view to the day view and scrolls to the selected cell | N/A | All |
| Details | Opens a window with the appointment details | | Appointment |
| New | Creates a new appointment, uses the selected slot as the start date and the profile's range to determine the duration | New | Planning Board |
| Edit | Updates the selected appointment | Edit | Appointment |
| Delete | Removes the selected appointment | Delete | Appointment |
| Cut | Cuts the selected appointment | Cut | Appointment |
| Copy | Copies the selected appointment | Copy | Appointment |
| Paste | Pastes the selected appointment on the selected time slot on the planning board | Paste | Appointment |
| Special paste | Opens a sub menu with special paste options | Paste | Appointment |
| Toggle calendar mode | Toggles the calendar mode of the selected appointment | Bypass calendar | Appointment |
| Quick actions | Quick actions to postpone or expedite appointments by a day, week, or month | Edit | Appointment |
| Split appointment | Cuts an appointment in two separate appointments | Split | Appointment |
| Disconnect assignment | Either removes an assignment or creates a new appointment for the selected assignment | Split | Appointment |
| Links | Opens a sub menu with links | N/A | All |
| Action URI's | Populates the underlying URI's placeholders and executes the URI | Action URL | All |
A second context menu lives in the **resource grid** of the planning board. Right click a cell in the resource grid to open it. The options are limited to the _resource URIs_ for the resource in the menu's context. Unlike action URIs, resource URIs don't contain placeholders, so they are static links to locations relevant to that resource, such as a link to the resource card in the back-office system.
## Copy and paste
Dime.Scheduler offers a set of copy and paste features that work the way you would expect.
They are not limited to a single planning board: you can copy and paste appointments across pages and across planning boards. Copying and pasting multiple appointments is also supported. The interval between the appointments in the selected range stays intact, while the date and resource are set according to the current mouse position.

### Cut & copy paste
**Cutting** an appointment removes the original record and replaces it with the pasted appointment in the selected time slot. A cut appointment is shown shaded until you paste it.

When you **copy** instead, a new appointment is created and the original stays in place. You can reach both features two ways: through the shortcut keys and through the context menu.
### Special paste
There are three special paste actions:
- Paste but maintain the same resources
- Paste but maintain the time
- Paste but maintain date and time
The first action respects the selected date and time but pastes the appointment on the resource that was assigned to the cut or copied appointment. The other two invert this logic: they apply the selected resource but not necessarily the selected date and/or time slot.
### Copy/Paste shortcut keys
Here is an overview of the keyboard shortcuts for the copy and paste features in the planning board:
| Function | Shortcut key |
| ------------------------------------- | ------------ |
| Cut | CTRL + X |
| Copy | CTRL + C |
| Paste | CTRL + V |
| Special paste: maintain resources | SHIFT + R |
| Special paste: maintain time | SHIFT + T |
| Special paste: maintain date and time | SHIFT + D |
## Split appointments
Appointments can be split in three different ways:
- Keep the appointment but remove the selected assignment
- Create a separate appointment for the selected assignment
- Split the appointment in two
These actions are available through the context menu or via shortcut keys:
| Function | Shortcut key |
| --------------------- | ------------ |
| Remove assignment | SHIFT + 1 |
| Disconnect assignment | SHIFT + 2 |
| Split appointment | SHIFT + 3 |
A split does not necessarily produce two even parts. As with the copy and paste functions, the cut-off point is the mouse position at the moment you open the context menu or enter the keyboard combination. Keep in mind that the snap interval setting may adjust the final value. For example, with a snap interval of 1 hour, splitting an appointment at 10:15 is adjusted to 10:00.
The end dates of the appointments do not change when calendar mode is switched on. The total working time stays the same, divided across two separate appointments.
## Drag and drop validation
How freely an appointment can be dragged and dropped on the planning board depends on the application settings, the user's security level, and the specifications of the resources, resource types, tasks, and appointments. The sections below explain each validation rule:
- Appointments must not be locked
- Linked (Gantt) appointments must not exceed the task boundaries
- Appointments must stay inside the boundaries of the allowed dates
- Appointments must take the resource type 'allow multiple selection' setting into account
### Locked
A locked appointment cannot be manipulated. The lock is usually set in the back-office system; it cannot be set inside Dime.Scheduler itself. You can recognize locked appointments by their shaded stripes.
### Linked appointments
Linked appointments come from the integration with the Gantt chart. When a resource is assigned to a task and the Gantt chart is synchronized, appointments are created in the planning board.
You can usually modify these appointments freely, with one caveat: a linked appointment cannot exceed the dates of the underlying task. The sketch below shows the boundaries. An appointment may occur anywhere at any time as long as it stays inside the date boundaries (the start and end date of the task).

Dragging an appointment outside the boundary will not pass the test:

### Open task constraints
The application configuration contains a setting called ["Apply allowed date constraints"](/administration/setup/planning#respect-allowed-date-constraints). When this setting is activated, the planning board applies an extra validation rule.
It is very similar to the previous rule and is the same rule as [the date window constraint](/administration/setup/planning#respect-allowed-date-constraints). When a task specifies "latest allowed" and "earliest allowed", appointments can only be planned inside that time frame.
### Resource type
Moving an appointment to another resource, or adding an assignment with SHIFT + drag, brings in one more validation rule. Every resource belongs to a resource type, and a resource type has a property called "allow multiple selection". When this switch is on, you can assign more than one resource of that type to an appointment. When it is off and a planner tries to add a second resource of that type to an appointment, validation fails and the change is not stored.
## State
This component is stateful. The following elements are stored:
- View
- Columns with order, sorting and filters
- Page size
- Appointment height
- Ignore calendars mode
## Standard view settings
You specify the planning boards' standard view settings via the settings cogwheel in the profile area. These settings are stored in the user profile and are used when the application launches, when you open a profile and when you add a planning board to the profile.

These settings overlap with the **start date** and **range** of the planning board component. You can select the same values here, including a custom value. The start date field accepts a positive or negative value relative to the current day (for example, -1 to show the planning from yesterday when the application starts). The range field accepts positive values only. The menus in the planning board do the same thing, except that they are stateless, unlike their counterparts in the profile area.
The **snap interval** defines the minimum increment by which you move appointments on the planning board, whether you move the whole appointment or just its start and end time. It makes drag and drop easier and keeps your planning consistent.
By default Dime.Scheduler shows all 24 hours on the planning board. Use the sliders to set the **start** and **end time**.
These settings apply immediately to all planning boards in the profile, but the profile itself is not updated automatically. Save the profile explicitly to persist the changes.
## Containers
Containers let you group appointments and plan the group as a whole. A [dedicated article](user/features/containers.md) walks you through the feature.
## Links
Contextual links are available in the links table. Standard connectors like the one for BC automatically generate items that link back to the original record in Business Central. As with everything else in Dime.Scheduler, you can add your own links too, for example to SharePoint documents or intranet pages.
:::tip[Shortcut]
Select an appointment, and hit 'L' on your keyboard to open the contextual links for the appointment and its associated task and job.
:::
---
## Planning board
import ReactPlayer from 'react-player'
import { FaCalendarAlt, FaCompressAlt, FaBox, FaEraser, FaArrowLeft, FaArrowRight, FaSearch, FaSave, FaPlus, FaCog, FaFilePdf, FaAngleDoubleRight, FaAngleDoubleLeft, FaArrowAltCircleRight, FaArrowAltCircleLeft } from 'react-icons/fa';
The planning board is the heart of the application. It is divided into four functional areas:
- The scheduling grid
- The resource grid
- The top bar
- The bottom bar
You can spot each area at a glance:
At the center is a paged grid that holds all the resource information. Planners use it to filter, sort and visualize everything they need. Each resource in the grid also has its own planning timeline, which shows the appointments the user is authorized to access. Planners create, update and delete appointments in this timeline, either directly or indirectly. The top and bottom bars round out the component with extra features for navigation and state management.
The pages below describe each of these elements in detail.
import DocCardList from '@theme/DocCardList';
## Shortcut keys
| Shortcut key | Action |
| ------------------------------------------ | ----------------------------------------------------- |
| DELETE | Delete appointment |
| CTRL + X | Cut appointment |
| CTRL + C | Copy appointment |
| CTRL + V | Paste appointment |
| SHIFT + D | Special paste: keep date |
| SHIFT + R | Special paste: keep resource |
| SHIFT + T | Special paste: keep time |
| SHIFT + L | Special paste: keep resource and time |
| SHIFT + 1 | Split: remove resource from appointment |
| SHIFT + 2 | Split: disconnect assignment |
| SHIFT + 3 | Split: split appointment |
| SHIFT + Drag appointment to other resource | Append resource |
| CTRL + Drag | Bulk select appointments |
| SHIFT + Arrow left | Move to the next period |
| SHIFT + Arrow right | Move to the previous period |
| SHIFT + G | Create an appointment container |
| L | Open the links associated to the selected appointment |
## Read more
- [Resource filters](user/components/resourcefilters.md)
- [Open Tasks](user/components/opentasks.md)
- [Planned Tasks](user/components/plannedtasks.md)
- [Grids](user/features/grid.md)
- [Appointment Templates](user/features/appointment-templates.md)
- [Data-driven security](user/authentication/datadrivensecurity.md)
- [Calendar-based appointment duration recalculation](user/features/appointment-recalculation.md)
- [Action URIs](user/features/actionuri.md)
- [Recurring appointments](user/features/recurring-appointments.md)
- [Containers](user/features/containers.md)
---
## Toolbars
import { FaCalendarAlt, FaCompressAlt, FaBox, FaEraser, FaArrowLeft, FaArrowRight, FaSearch, FaSave, FaPlus, FaCog, FaFilePdf, FaAngleDoubleRight, FaAngleDoubleLeft, FaArrowAltCircleRight, FaArrowAltCircleLeft } from 'react-icons/fa';
The planning board's top bar holds the buttons and switches that control what you see and how you work with it. This page walks through each one.
On the left hand side of the bar, you will see something similar to this:

In order of appearance on the image above, these are their functions:
| Capability | Description |
| ----------------------------------------- | ----------------------------------------------------------------------------------------- |
| [Search](#search) | Filters the resource grid's visible columns by the keyword entered in the search box [^1] |
| [Fit to screen](#fit-to-screen) | Shows the planning of the selected date range in one view [^2] |
| [Start Date](#start-date) | Sets the start day |
| [Range](#range) | Sets the range of the visible scheduling view |
| [Views](#views) | Selects the planning view |
| [Appointment height](#appointment-height) | Sets the height of appointments in the planning board |
| [Date range picker](#date-range-picker) | Change the timeline's date range |
On the right hand side of the toolbar, you will see something similar to this:
In order of appearance on the image above, these are their functions:
| Capability | Description |
| -------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Collapse and expand resource rows | Shrinks the appointments to fit on one row or stack overlapping appointments. |
| Toggle containers visibility | Hides or shows the rendering of the containers in the planning board. |
| [Ignore or apply calendar mode](#ignore-or-apply-resource-calendars) | Planning board level configuration to apply or ignore resource calendars |
| [Clear resource filters](#clear-resource-filters) | Clear all resource filters |
| [Shift](#shift-dates) | Go to the previous or next time range |
| [State](#planning-board-state) | Manage the state of the component's state |
| [Export](#export) | Export to PDF |
## Search
The search bar works just like the other search bars in Dime.Scheduler. The text you enter creates a filter across all visible columns in the resource grid. A resource is included in the result set as soon as one of its columns matches the query.
For example, searching for 'Ma' in the following resource grid returns two items:
| Resource Display Name | Resource Type |
| --------------------- | ------------- |
| John Doe | Consultant |
| Joanna Hope | **Ma**chine |
| **Ma**x Power | Consultant |
Even though Joanna Hope's display name contains no 'Ma', the resource type does ('Machine'). The third resource matches the other way around: there's no match in the resource type, but there is one in the display name. Had there been only one visible column, the resource grid would have shown only the third resource, since there's no 'Ma' in Joanna Hope's display name.
| Resource Display Name |
| --------------------- |
| John Doe |
| Joanna Hope |
| **Ma**x Power |
## Fit to screen
This toggle button squeezes the scheduling grid into a single view, so you can see the entire planning of the selected date range without horizontal scrolling.
## Start date
This menu sets the start date of the date range you view on the planning board. Pick one of the presets:
- Today
- Start of week
- Start of month
Beyond these presets, you can always choose a custom start date and date range with the [**date picker component**](user/components/datepicker.md).
Note: when you open or create a planning board - by launching the application, opening a profile, or opening a planning board via `Add new planning board` - the default values come from the ['standard view settings'](contextmenu.md#standard-view-settings) stored in the user profile.
## Range
The range menu sets the visible scheduling range on the planning board through a few presets:
- Day
- Week
- Month
Selecting one of these updates the [date picker](user/components/datepicker.md) range to match. For example, selecting the month range adds a month to the planning board's start date, which may cause the component to load the appointments for the added dates.
The range and view menus work independently, but they pair well: a day range works with the day view, just as a (working) week view works with the month range.
Note: when you open or create a planning board - by launching the application, opening a profile, or opening a planning board via `Add new planning board` - the default values come from the ['standard view settings'](contextmenu.md#standard-view-settings) stored in the user profile.
## Views
The views menu sets the timeline's lowest level of time, in other words, the planning board's time unit. The following options are available:
- Day
- Week
- Work week
- Month
### Day view
The day view shows the timeline on an hourly basis. By default it renders every hour of the day; the user profile lets you restrict the start and end time.

The top level in the timeline header helps you keep track of the date.
### Week view
The week view goes one level higher than the day view. It shows the days (and the day number), grouped by week.

This is a useful view for high-level planning and capacity reporting.
### Work week view
The work week view is essentially the same as the week view, but as the name suggests, the weekends are not included.

### Month view
The month view is useful for planning that spans several months. The days are still the lowest level in the timeline, but it groups by month rather than week:

## Appointment height
You can set the height of the appointments on the planning board, from one line up to five lines. Click this button in the planning board's top toolbar:

When '1 row' is selected, the appointments will look like this:

In contrast, five lines adds a significant height to the appointments:

## Date range picker
This toolbar option is the same as the [date picker component](/user/components/datepicker), made accessible inside the planning board.
## Ignore or apply resource calendars
This toggle button overrides the global resource calendar planning mode, letting you manage the calendar mode at the planning board level. It's an extensive feature with its own dedicated article. Read more in [appointment recalculation](user/features/appointment-recalculation.md).
## Clear resource filters
Resource filters can be set in several places. There's the [component](user/components/resourcefilters.md) that filters the resources in the planning board, filter action columns in the open and planned tasks grids and the Gantt chart, and the option in the planned tasks component to show the resources you selected in the grid. That adds up to two types of resource filter, reached from different components. Here's what each does.
The first is the "Resource Filters" component. It provides a powerful query builder to filter the set of resources on the planning board, so the planner can look for a resource with the skill set or qualifications needed to carry out a task. This is the _resource-first_ approach. The _task-first_ approach works the other way: planners set requirements on the task. For example, only a handful of pilots in the world can land a jumbo jet on St-Maarten's Princess Juliana airport, and a task filter helps you find a pilot in the workforce who is qualified and available. This is what the filter icons at the start of each row in many grids are for. Under the surface, these filters have exactly the same effect as the resource filter component; the difference is that task filters are preconfigured rather than composed on the fly.
The second type is simpler. The planned tasks grid lists appointments and their assigned resources, and the planner can use it to filter out the other resources. This gives a closer look at the planning of those resources without searching for them in the planning board. In short, it's a shortcut to show the resources selected in the planned tasks grid.
When you can't find the resource you're looking for, this button clears all **external** filters, that is, the filters that aren't part of the planning board's own component. Column filters stay applied. Check the button's tooltip: it shows exactly which filters are currently active on the planning board.
## Highlight appointments
From the planned tasks grid, you can select a range of appointments and highlight them on the planning board. If you haven't already, read [persistent highlighting](user/components/plannedtasks.md#persistent-highlighting) first.

Beyond the link between the planned tasks grid and the planning board, you get a few extra capabilities. Two contextual items appear when appointments are highlighted in the planning board: .
The first item toggles the visibility level of the highlighted appointments. It also toggles the editability of the other appointments: to prevent unwanted changes, appointments that aren't highlighted can't be dragged or resized in the planning board. When the highlight feature is activated from the planned tasks grid, the drag-select feature only considers the records selected in that grid, ignoring the rest. In the example below, only two items are selected.
The stop button resets the feature entirely: no appointments stay highlighted and all appointments return to their original state. To activate the feature again, go back to the planned tasks grid.

The visibility toggle button in the scheduler controls the scope of the drag-select feature: when it's on, only the highlighted appointments are considered; when it's off, all appointments on the planning board fall within the dragged area.
## Shift dates
Two sets of arrow icons shift the planning board's time range by the applied unit.
This function is connected to the date picker (and the components connected to it, such as the pivot grid or the Gantt chart). Shifting the planning board back and forth updates the date picker, which in turn updates the other components. This keeps all components pointing to the same dates.
### Shift 1 day
The and icons shift the timeline one day forward or back.
### Shift range
The and icons shift the timeline by the current date range.
The unit varies with the selected date range, which you set through either the date picker component or the range option. The range determines the unit for shifting to the next period. For example, if the date picker shows a five day range, the shift arrows move the visible scheduling range five days earlier or later.
You can also trigger this with the shortcut keys SHIFT + and while working on the planning board. The shortcuts don't work while you're in the open tasks grid.
## Planning board state
Like the other components, the planning board is stateful: you can modify and load planning boards on the fly. For more, see the [grids](user/features/grid.md) documentation.
The planning board has a few more features than the grids, so its state holds more elements. See [the stateful grids section](/user/features/grid#stateful-grids) for details.
## Export
Click the PDF export button and Dime.Scheduler takes a snapshot of the planning board and exports it as a PDF file. A few parameters let you tweak the output [^3]:

The first field selects the schedule range: the complete schedule, a date range, or the visible schedule. You can also set a row range, either 'everything' or just the 'visible range', and add or remove resource fields. The remaining options are print settings: page format, orientation, pagination control, and whether to add a header to the page.
The export runs on an export server. Once the document is ready, a modal window prompts you to save the file. The output closely matches the planning board:

Because the export feature and the planning board are so tightly integrated, we recommend creating a dedicated planning board layout optimized for printing.
## Paging
The bottom bar works like the one in every other grid in the application. It pages the resources, which lets the application scale to many thousands of resources.

The paging toolbar contains the following elements:
- Go to the first and last page
- Go the previous and next page
- Select a page by number
- Refresh the page
- Select the page size
Keep in mind that the resources are paged, not the appointments.
[^1]: This is only available when the administrator has enabled this feature.
[^2]: This is what is called a contextual action. It is only available when certain conditions are met. In this case, this buttons is shown when there are (external) resource filters applied on the grid.
[^3]: See [this FAQ article](/user/troubleshooting/user) for the caveats of this service.
---
## Planning board tooltips
Control what appears in **planning board** appointment tooltips from the dedicated **Tooltip** screen.
The screen has a builder on the left and a **field library** on the right. Drag fields from the library into the active list, reorder them with the drag handles or the up/down controls, and remove items you no longer need. Use **separators** to add visual breaks between blocks of information.
The tooltip itself is enabled application-wide via the **Enable planning board tooltip** setting on the [application setup](/administration/setup/planning) page.
---
## Resource Filters
The resource filter component narrows the planning board to the resources the planner actually cares about right now. In an organization with hundreds of resources, no planner is ever working with all of them at once: a dispatcher handles a single team, a service planner needs people with a specific certification, a regional planner cares only about their region. Without a way to filter, the planner has to scroll past everyone every time.
The filter answers that with two equally important interfaces. A **query builder** for the planner who knows the criteria up front and wants to compose them by hand, and a **one-click filter icon** on the task grids that turns a task's own requirements into the filter automatically. The same filter mechanism feeds both, so any filter assembled one way can be tuned the other way.
Whatever the planner filters on (skills, regions, departments, certifications, anything else relevant to the business) comes from the back-office application. The component just plays with the data it's given.
## Building a query
A filter is built from **rules**, optionally organized into **groups**, joined by **operators**.

A rule has three parts:
- The filter group picks the resource property in question (department, skill, region).
- The rule type picks the operation (equals, does not contain, and so on).
- The filter value picks what to compare against.
A simple example: "department equals Service". When applied, the resource grid drops anyone outside the Service department.

For anything beyond a single dimension, **query groups** package related rules together, and an operator decides how the rules and groups combine. The example below filters down to resources in the Service department who *also* hold either the Accessories or Office Equipment skill:

This is where the query builder earns its place. The planner can express arbitrarily nested ANDs and ORs without leaving the UI; the more specific the criteria, the more targeted the resulting board.
## Saving and sharing a filter
A useful filter rarely stays useful only once. The component is stateful, just like the [grids](user/features/grid.md): save it as a layout, share it with the team, set it as the default so it applies on every login. Defaults live per profile, so a dispatcher's profile can boot with their team's filter pre-applied while a regional planner's profile starts with theirs.
The icons in the top bar match the standard grid pattern: apply, reset, load, save, save-as, manage. Apply commits the current filter to the planning board's resource grid; reset clears it. The reset button is also surfaced directly on every planning board for quick access, so the planner does not have to come back here to clear the filter.
## One-click filters from a task
The query builder is the precise tool. The filter icon on each row of the [open tasks](user/components/opentasks.md) and [planned tasks](user/components/plannedtasks.md) grids is the fast one.
Tasks ship from the back office already tagged with the filter values they need (a service order's skill requirement, a delivery's region, and so on). Clicking the row's filter icon translates those tags into a query and applies it immediately. No typing, no remembering which skills the task demands; the data already knows.

If the result is not quite what the planner expected (too narrow, too wide, missing a constraint), tweak it in the query builder. Adjustments here do not change the task itself, so the next click on the row's filter icon returns to the original query.
## Read more
- [Grids](user/features/grid.md)
- [Data-driven security](user/authentication/datadrivensecurity.md)
---
## Route Sequence
The Route Sequence grid is the list-view companion to the [map](user/components/map.md). The map plots a route as pins and lines; this grid lays the same stops out as rows, with the numbers the planner actually needs to act on: cumulative travel time, distance per leg, time spent at each stop.
Editing a route on the map alone is fiddly: clicking pins to add, dragging lines to reorder, squinting at the legend to read distances. The grid turns those moves into ordinary table operations. Drag a row to reorder, hit delete to remove, group by column to analyze, and in round-trip mode commit the result back to the planning board in a single click.
## How a trip becomes a list
The grid and the map stay in lockstep. Select pins on the map and they appear as rows here; reorder rows here and the route on the map recalculates. Stop numbering on the pins themselves is optional and [configured globally or per profile](/administration/setup/map).
For instance, this route:

becomes this grid:

## Free composition versus round trips
The component runs in one of two modes, and the toolbar shifts accordingly:
- **Free composition** is the default. The planner picks pins on the map and the grid composes the route from the selection. Useful for ad-hoc trip planning where the goal is to inspect or compare a sequence, not commit it.
- **Round trip** mode is committed planning. A round trip starts and ends at a resource's home location and visits the appointments already planned for that resource on a given date. The grid enters this mode automatically when a round trip is created from the [map](user/components/map.md), typically by clicking the round-trip pin on a resource for a day.
Only round-trip mode unlocks the actions that write the result back to the planning or call the optimization solver, because both need a specific resource and date as context.
## Reading the trip
A live summary anchored at the bottom of the grid keeps the trip-wide totals visible no matter how many stops are in view:
- **Distance** total, in kilometers
- **Travel** time between stops, rounded to the nearest five minutes
- **Work** time at the stops, same rounding
- **Total** is travel + work
The summary refreshes every time the trip changes.
Per row, the grid adds route-specific columns to whatever task and job columns the planner already configured:
| Column | What it tells you |
| ----------------- | ---------------------------------------------------------------------------- |
| Distance | Distance between the previous stop and this one |
| Total distance | Cumulative distance from the start of the trip up to this stop |
| Travel time | Travel time from the previous stop to this one |
| Total travel time | Cumulative travel time from the start of the trip up to this stop |
| Working time | Time spent at this stop |
| Total duration | Cumulative travel + working time from the start of the trip up to this stop |
These columns are computed in memory as the route changes, not stored in the database. They behave like any other column: hide them, reorder them, save them as part of the [layout](user/features/grid.md).
### Running totals on free decimals
Any free decimal field among the task and job columns gets a paired **Running Total** column that adds the bound value into a per-row cumulative, the way a spreadsheet would. Same in-memory pattern as the route columns: recalculated as waypoints move, saveable as part of the layout.

## Editing the trip
Reordering is a drag. Select one or more rows (CTRL to multi-select) and drop them at the position you want. The route on the map and the totals at the bottom recalculate immediately.


When multi-selecting, the order of selection decides the drop order:

The map reflects the new order:

Remove a waypoint with the delete button or the `Delete` key. CTRL-select multiple rows and delete clears them all. When a removed waypoint belongs to a [task container](user/features/containers.md), its sibling tasks come out too, keeping the bundle consistent.
## Committing and optimizing
In round-trip mode, two actions in the toolbar bridge between the grid and the live planning:
- **Update planning** writes the current order back to the planning board as the resource's planning for that date. The order on screen becomes the order on the schedule.
- **Optimize route** hands the trip to the routing solver. The solver returns a new order that minimizes travel time and distance; the grid and map update in place. The action is purely a re-ordering of the existing stops, never an add or remove, and it does not touch the planning until **Update planning** commits it. Available when the tenant has the **Advanced Map** add-on and [route optimization](/administration/setup/preview#solver--optimization) is enabled.
Both actions require every appointment in the trip to be enabled and unlocked. Locked or disabled appointments would otherwise be moved silently, which is exactly the kind of foot-gun the requirement is there to prevent.
Two more actions are always available:
- **Action URLs** opens [action URIs](user/features/actionuri.md) for the current selection. Only enabled when every stop belongs to the same source app.
- **Detach route** clears the active route and exits round-trip mode. Use it to start over with a fresh selection.
## Side actions
When a stop is a project task, its context menu carries an **Open project** item that loads the project into the [Gantt chart](user/components/gantt.md). Same shortcut as in the open and planned tasks grids.
## Reference
The grid is stateful, just like the other [grids](user/features/grid.md): column selection, order, and totals carry into the saved layout.
Because rows are drag-by-default for reordering, copying cell text needs an explicit switch. The **Toggle drag mode** button on the toolbar flips between drag-to-reorder and ordinary text-selection; see [Copy data](user/features/grid.md#copy-data) for the broader pattern.
## Read more
- [Map application setup](/administration/setup/map)
- [Route optimization setup](/administration/setup/preview#solver--optimization)
- [Map component](user/components/map.md)
- [Grids](user/features/grid.md)
---
## Experimental features
Experimental features let you try new capabilities early and share feedback before they become part of the standard release. Under Dime.Scheduler's release policy, these features are made available to users so we can gather input and refine them.
Experimental features are turned off by default. You manage all feature flags in the corresponding section of the [application setup](/administration/setup).
:::info[More Information]
For more information about upcoming features available in the sandbox environment, see the [Roadmap](/roadmap) page.
:::
## Travel time
Route and travel time calculation have been part of the application from the start. This feature lets you fold travel time directly into the schedule on the planning board.
- Select the trip by holding CTRL and clicking the appointments on the planning board.
- Open the context menu and expand the 'Add travel time to selection' menu item:

- Choose one of two modes:
- Standard: the algorithm first resets the idle time between appointments, then adds travel time between them.
- Retain lag: the algorithm keeps the schedule as is, including any idle or unplanned time between appointments, and adds travel time between them.
## Zoom in and out in planning board
When switched on, you can zoom in and out on the planning board by hitting the CTRL key and zooming in and out with your mouse. This will change the view and date range according to the zoom level.
## Alternating row colors
When switched on, all the grid-based components in the planning view will show alternating row colors.
## Render category in background
When switched on, all grid-based components displaying appointment or task data will render the category color in the background of the rows.
---
## Action URIs
## What is an action URI
An action URI is a web request template that another application can parse and execute. When a user invokes it, Dime.Scheduler replaces the placeholders in the template with values from the record in the action's context, then runs the web request.

To give you a sense of what action URIs make possible, here are a few things customers have built:
- Create new absences
- Create transportation orders from the generated route on the map
- Send e-mails and text messages
- Update the status of an order
- Check the stock level of the required components for the job
## How to invoke action URIs
The following components support action URIs:
- Open tasks
- Planned tasks
- Planning board
- Map
- Route sequence
In most cases, you invoke an action URI from the **context menu** of the selected entity. The map works a little differently.
To launch an action URI on a task in the map, select the pin or pins linked to the task and click the anchor icon. A list appears with all available action URIs to choose from. Because you can select multiple tasks and pins on the map, you can launch a single action URI that covers every selected item at once. This lets you configure your back-office application to run logic in a context where all the items are available.
One thing to keep in mind: action URIs for multiple items are only available when the selected items share the same Source App and Source Type.
## Read more
- [Action Setup](/administration/settings/actions)
---
## Calendar-based appointment duration recalculation
Resources can be subject to a calendar. Administrators manage the default schedule for resources across the organization, and once it's configured, planners see it in the resource zones area of the planning board:

This information is essential for building a feasible plan and keeping resources healthy and productive. It shows the business hours, holidays and other arrangements (such as night or holiday shifts) that apply to each resource. Administrators can define a schedule for every resource through the [calendar setup](administration/settings/calendars.md). Everything in the shaded color marks unavailable time; the white areas are the working hours. This lets the planner read a resource's availability at a glance and adjust the plan accordingly.
Dime.Scheduler can take this a step further and handle it for you. Calendar-based appointment duration recalculation automatically adjusts an appointment's duration based on the resources assigned to it and their calendars, so the schedule stays in line with each resource's real availability.
## How it works
Dime.Scheduler has a built-in calendar system that can automatically update the **total duration** of an appointment or just its **planning quantity**, based on the availability of the resource carrying out the task.
An administrator chooses from three modes:
- Update the **duration** of the appointment
- Only update the **planning quantity**
- Do nothing
:::info[Definitions]
The **duration** of an appointment is the sum of **working time** and **non-working time**.
The **planning quantity** is an expression of the appointment's duration in a unit of measure of choice. Through a conversion metric, time can be converted into some other measure such as capacity or volume. By default, the planning quantity expresses the duration in hours.
:::
Pick a mode and a scenario to see how the algorithm reshapes the appointment around the resource's calendar.
### Extend duration
When the calendar mode is set to "Extend Duration", working time is the independent variable. When a task overlaps with non-working time, the algorithm extends the appointment by that non-working time, so the resource still gets enough working time to finish the job.
The two examples below show the algorithm in action.
- Example 1: the user drags a 2-hour task onto the planning board (blue). There's one hour of overlap (red). After drop, the algorithm adds 1 hour to the appointment (green). The result is a 3-hour appointment: 2 hours of working time and 1 hour of non-working time. The planning quantity only counts working time, so it stays at 2 hours.
- Example 2: a similar scenario showing how the algorithm keeps adding non-working time until the resource can complete its working time. The task needs 2 hours of work, but with 2 hours of non-working time in the way, it takes the resource 4 hours in total to finish.

The impact can be significant when a task can't be finished before the end of the working day. A two-hour appointment may suddenly stretch across several hours or even days, while the working time stays at two hours.

### Update planning quantity
The second mode uses the same algorithm, but it doesn't extend the appointment with non-working time. Instead, it adjusts the planning quantity field to account for the non-working time. Using the same examples:
- Example 1: the user drops an appointment on the planning board. One hour overlaps with a lunch break. The duration isn't extended, but the planning quantity factors in the hour of non-working time and is set to 1 hour.
- Example 2: same situation. The duration isn't extended, resulting in a 2-hour appointment with a planning quantity of 1 hour.

### None
When the calendar mode is switched off, the non-working time is always 0 and the working time equals the total duration. Business hours and all other calendar settings are ignored.
## Configuration
Because this feature reaches across the whole plan and not everyone needs it, it is entirely optional and configurable. A simple example shows why. Suppose the planner needs to schedule a task that requires ten hours of work. After finding a resource for the job, the planner discovers the resource faces a two-hour drive each way, a four-hour commute. The resource agrees to work two hours of overtime to finish the task rather than return for two more hours the next day. If auto-extend couldn't be turned off, the planner would have no way to override this decision in Dime.Scheduler.
Three levels of configuration give you ample control to fine-tune the auto-extend feature:
```text
Global configuration
└── Planning board
└── Appointment
```
These three levels cascade. All three must be set correctly for the recalculation algorithm to apply to an appointment. If any higher level blocks the setting, the algorithm won't run.
### Global configuration
The administrator decides whether auto-extend applies to everyone. If the administrator disables it, no lower-level setting has any effect. In the application setup, the administrator chooses the **adjustment mode**:

Two related settings deal with visualization: whether to show the resource calendars, and which color to use for them.
### Planning board
The second level lives on the planning board itself. A button does the same thing as the global configuration, but scoped to that component only. This means you can run several planning boards in your profile with different calculation modes. The icon won't appear if global configuration has switched off the calendar mode.

You can toggle this button. When it's red, the auto-calculation feature is ignored on this planning board: creating or updating appointments here won't apply calendars, even when the appointments indicate otherwise. The button's value is stateful.
### Appointment
The third level lives on the appointments and tasks themselves. Appointments marked to ignore calendar application show a small orange triangle in the lower-left corner:

There are two ways to change this value: through the appointment's context menu, or through the editor, as shown here:

## Conflict resolution
Appointments with more than one resource present a challenge: two different resource schedules can't be reconciled into a single appointment. The planner must therefore decide which resource to use for the automatic recalculation. When creating or updating such an appointment, a resolution window appears, like this one:

There are three outcomes:
- The planner selects the resource to use for the appointment recalculation
- The appointment ignores calendars altogether (no automatic recalculation)
- Cancel the update
## Read more
- [Planning board](user/components/planningboard/index.md)
- [Configuration hierarchy](administration/appsettings/index.md)
- [Calendar setup](administration/settings/calendars.md)
---
## Appointment Templates(Features)
Appointment templates let you capture **extra data** on appointments beyond the built-in fields - checklists, compliance flags, equipment used, notes per task type, whatever your back-office cares about.
When a template is in use, the appointment card gets one or more additional tabs. Each tab holds a set of fields - text, numbers, dates, yes/no, dropdowns - that planners fill in as they work. The data is sent back to the back-office along with the rest of the appointment.

Different task types can use different templates, so a maintenance job and a sales visit can each show only the fields that matter for them.
## Configure it
For the full setup walkthrough, see [Appointment templates](administration/settings/appointmenttemplates.md).
---
## AI Chat
import { FaCommentDots } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "AI Chat", link: "https://app.dimescheduler.com/chat"}]} />
The AI Chat is a conversational assistant that sits next to the planning view. Users ask questions about the schedule, run quick actions, and let the assistant act on Dime.Scheduler on their behalf. The chat connects an LLM provider to the same toolset the [MCP server](integrations/mcp/intro.md) exposes, so it can search tasks, look up resources, find available slots, create or reschedule appointments, send messages, and run route optimization.
## Enabling the chat
The chat is off by default. To enable it, open [Application Setup](/administration/setup/preview), switch to the **Preview** tab and configure the **AI Chat** section:
1. Toggle **Show AI chat** on.
2. Pick a **Provider**: `OpenAI`, `Anthropic`, or `Azure OpenAI`.
3. Enter a **Model** identifier (e.g. `gpt-4o-mini`, `claude-haiku-4-5-20251001`). Leave blank to use the provider default.
4. Enter the **API key** for the selected provider.
5. For **Azure OpenAI**, also fill in the **Endpoint** (e.g. `https://.openai.azure.com`) and the **Deployment** name.
6. Save.
Until an API key is configured, the chat panel shows a configuration prompt with a link back to the setup page rather than the conversation UI.
:::caution
The API key is stored at tenant level and is used for every user's chat session. Make sure to use a key from a dedicated project or budget so usage can be monitored.
:::
## Conversation surface
Once configured, the chat shows three regions:
- **Sidebar** with all saved conversations. Clicking a conversation reopens it; the **New conversation** button starts a fresh thread.
- **Main window** with the active conversation, message input, and a header with the title and description.
- **Side actions**: Quick Actions, Examples, and Download.
### Quick Actions
Quick Actions are pre-built workflows that bypass the free-form chat. Each one shows a form with the parameters the underlying tool expects, validates the input, and calls the tool with the result. Reach for them when you know exactly which tool you want to run and want to skip the conversational round-trip.
### Examples
The Examples panel lists capability cards with sample questions for each tool category. Clicking an example pre-fills the message input so you can tweak it before sending.
### Download
The download button exports the current conversation as a text file, which is handy for sharing context with colleagues or attaching to a support ticket.
## What the chat can do
The chat uses the in-product MCP toolset, so anything documented under [Model Context Protocol](integrations/mcp/intro.md) is available conversationally. Common patterns:
- _"What's open for this week?"_ - calls the open-tasks resource.
- _"Find a 2-hour slot for John Smith next Monday."_ - uses `find_available_slots`.
- _"Move tomorrow's 14:00 appointment for Jane to 15:00."_ - uses `reschedule_appointment`.
- _"Optimize routes for John, Jane and Lara from Monday to Friday."_ - uses `optimize_field_service` and explains the trade-offs.
- _"Notify John his 14:00 moved to 15:00."_ - uses `send_message`.
When a tool call could change data (creating, rescheduling, or deleting appointments, or sending notifications), the chat asks for confirmation before it executes.
## Read more
- [Application Setup - Preview tab](/administration/setup/preview)
- [Model Context Protocol](integrations/mcp/intro.md)
---
## Containers
import UsersSvg from '/img/assets/user/features/containers/containers.svg';
A container groups tasks and appointments into a single logical unit so the planner can schedule them as if they were one entity. It's a flexible tool for enforcing business rules, and you can use it in many ways: building trips, enforcing work breakdown structures, defining manufacturing sequences, and more.

Containers are to tasks and appointments what roles are to user actions. They bundle records into one unit, letting the application treat the container as a proxy for everything linked to it. Instead of handling records one by one, you act on the whole group at once.
On their own, containers don't impose any behavior beyond visualizing their members and enabling drag and drop at the container level. When you need more, the back-office system can step in and change how the container behaves.
You'll work with containers in the open tasks grid, and they affect both the map and the planning board.
## How it works
The data model of a container is about as simple as it gets. It's a single entity that other entities, such as tasks and appointments, point to in order to define a relationship between them.
```sh
Container: Assemble bikes for Bill (invoice for company - order A01234) and his son (privately billed - order A01235)
├─ Task: Assemble bike for Bill
├─ Task: Assemble bike for Bill's son
```
By default, tasks in the planning board are planned one by one. Even when tasks are linked through service lines, job planning lines, assembly lines, and so on, the planner still has to plan each one individually. Containers add a second way of planning where tasks are planned as a group. The container acts as a proxy task that holds a series of tasks. Appointments and tasks still exist on their own and can be planned that way, but now there's a higher-level entity that links related appointments together.
Take a customer with two open orders that should be executed at the same time. In the back-office system, those two orders may have no direct link. A container solves this, because any appointment and any task can be linked.

When something changes in the planning, the planner can either reschedule each affected appointment manually or reschedule the container, which updates every record inside it.

A container carries no constraints by default. It needs a name and a color, and it holds a collection of tasks and appointments. Which tasks and appointments those are doesn't matter. Containers can span resources and departments, and appointments from any order can be planned through the same container.
## Task and appointment containers
Two entities can use a container: tasks and appointments. Every task eventually becomes an appointment (a planned task), but before a task is planned, planners can already define a relationship between tasks through **task containers**. When you drag those tasks onto the planning board, the resulting appointments are already rendered inside a container.
When the order is created and ready to plan, the container will hold two tasks:
```text
Container: Assemble bikes for Bill (invoice for company - order A01234) and his son (privately billed - order A01235)
├─ Task: Assemble bike for Bill
├─ Task: Assemble bike for Bill's son
```
When scheduled, two additional members will be added to the container:
```text
Container: Assemble bikes for Bill (invoice for company - order A01234) and his son (privately billed - order A01235)
├─ Task: Assemble bike for Bill
├─ Task: Assemble bike for Bill's son
├─ Appointment: Assemble bike for Bill - 2022/05/01 10:00
├─ Appointment: Assemble bike for Bill's son - 2022/05/01 12:00
```
Tasks and appointments can be added to a container at the planner's discretion at any time.
## Map
When you select tasks (across jobs) in the open tasks grid, the map adds pins for the locations of the selected jobs. If a task is connected to other tasks through one of its containers, the linked tasks are embedded on the map automatically. Selecting one task loads its connected tasks too:

Planners who use the map to build trips for their resources gain the most here. With every related task already on the map, it's far easier to work out the most efficient route. Automating this step cuts down on errors and speeds up planning.
## Planning board
### Drag and drop task containers
Once a task container is defined and tasks are assigned to it, planning one task by dragging it onto the planning board plans every other task in the same container. As you drag, the tooltip tells you which tasks are about to be planned. In the example below, dragging a container-backed task pulls the other related tasks (as defined by the containers) onto the planning board:

Dropping that task or set of tasks immediately renders a container on the planning board.

### Creating containers on the planning board
The workflow above is an easy way to link tasks from the outset, but it's not the only one. Dime.Scheduler gives you the full set of tools to manage containers. Select appointments on the planning board and use the shortcut `SHIFT + G`, or the context menu, to create containers and add or remove members:

Give the container a name and a color, and you'll see the following view:

### Multi-resource containers
When a container spans multiple resources, a **vertical handlebar** appears. You can lock or unlock this bar by clicking the lock icon or double-clicking the handlebar. When it's unlocked, you can move the handlebar freely without dragging the container members along. When it's locked, the members move with the handlebar. When a container has multiple members on the same resource, a rectangle is drawn around them. This bar is draggable and moves the container's appointments for that resource along with it.

Validation rules run just as they do for individual appointments. As soon as one appointment fails validation, the entire container drag and drop action is invalid and the container won't drop.
### Single-resource containers
The **horizontal bar** drawn around a resource's container members can also reallocate the resource through drag and drop, exactly as you would with individual appointments in the planning board:

Validation rules run just as they do for individual appointments. As soon as one appointment fails validation, the entire container drag and drop action is invalid and the container won't drop.
### Appointment's context menu
The context menus of both the container and the individual appointments let you add or remove container members.

Either option opens a dropdown so you can choose which container to add or remove a member from (unless the appointment belongs to only one container). An appointment or task can belong to **multiple containers**, which opens the door to more complex relationships between tasks:

### Container's context menu
Right-clicking the horizontal and vertical bars (and the lines that connect to the vertical bar) opens a context menu with several actions for managing the container:

The planner can:
- Show the entire container in the planning board
- Edit the name and color
- In case of the existence of a vertical handle bar, lock or unlock the bar
- Add an appointment or container
- Remove the container
- Remove the container and its members
You can only remove a container from the planning board when it holds no task members. If the container has tasks, the action removes only the appointment members.
### Toggling containers visibility
The button in the planning board's toolbar toggles the visibility of containers in the planning board.
## Application setup
To view and use appointment containers, the 'Enable appointment containers' feature flag in the application setup must be [enabled](administration/setup.md). Toggling this feature loses no data: it simply activates or deactivates the feature in the planning board.
## Read more
- [Open tasks](user/components/opentasks.md)
- [Planning board](user/components/planningboard/index.md)
- [Map](user/components/map.md)
---
## Grids
Grids display large amounts of tabular data and let you fetch, sort and filter it quickly. Many of the components and features in Dime.Scheduler are built on data grids, so they share the same look and feel. This page covers the functions they all have in common.
## Overview
A data grid is an interactive table. Structured data is shown in columns and rows, where each row is a single record and each column is a single property of that record. The shape of the data drives the features you use to interact with it.
As with most things in Dime.Scheduler, you can configure a lot in the grids: which columns to show, in what order, and which sorting, filtering or grouping to apply. The starting point for all of these is the **column menu**. Every column in every grid has one. Hover over a column header and click the arrow that appears:

The menu options behave as their labels suggest, but here is a quick overview:
- Sort a column with "Sort Ascending/Sort Descending", or remove sorting with "Clear Sorting". You can also click a column header directly to sort on it.
- Choose which columns to show by clicking "Columns" and marking or unmarking fields in the list.
- Group the data with "Group by this field".
- Toggle grouping by checking or unchecking "Show in Groups".
- Filter a field by selecting "Filters" and typing or selecting the filter value.
The sections below cover these features in more detail.
## Column selection
### Adding and removing columns
Adding or removing a column is straightforward. Open any column's header menu by clicking the icon, then hover over the "Columns" sub menu to see every column available for that component.

Each grid has its own set of columns relevant to that component. Columns already visible in the grid show a marked checkbox next to them:

To remove a column, uncheck the box next to it. To add one, check the box. Show as many or as few columns as you like.
Note: adding and removing columns involves no server-side interaction. The data is already loaded for *all* the columns of the rows.
### Change the order of columns
Reorder columns by dragging and dropping their headers.

The green arrows show where the column will land. Use this to arrange columns in a sequence that makes sense to you.
## Sorting
Sorting applies to the entire data set, not just the page you are currently viewing. Depending on the grid (and whether it is paged), sorting may require the application to query the database to fetch the correct set.
Keep in mind that the column's underlying data type affects the result. It can surprise you that "100" comes before "20" when a text field is used to represent numerical data.
### How to apply sorting
You can sort in two ways: through the column menu or by clicking the column header.
From the column menu, choose to sort the column in ascending or descending order.

Once sorting is applied, the column shows an icon for the sort order:

If no sorting is applied and you click the column header, the column sorts in ascending order. Click again to toggle to the opposite direction. You cannot clear sorting from the column header; use the "Clear sorting" option in the column's header menu instead.
### Multiple sorting
A grid can have more than one sorter. The order in which the sorters are defined determines the overall result. For example, when you sort by "Job No" and then by "Task No", the data set is first sorted by "Job No". After that pass finishes, a second pass runs on the sorted list and sorts by "Task No".
An example makes this clear. Here is the list of open tasks as it appears in the database:
| Job No | Task No |
| ------ | ------- |
| Job 1 | Task 5 |
| Job 2 | Task 4 |
| Job 1 | Task 3 |
| Job 3 | Task 2 |
| Job 1 | Task 1 |
Sorting jobs in descending order gives this result:
| Job No (DESC) | Task No |
| ------------- | ------- |
| Job 3 | Task 2 |
| Job 2 | Task 4 |
| Job 1 | Task 5 |
| Job 1 | Task 3 |
| Job 1 | Task 1 |
Adding a second sorter on tasks in ascending order gives this result:
| Job No (DESC) | Task No (ASC) |
| ------------- | ------------- |
| Job 3 | Task 2 |
| Job 2 | Task 4 |
| Job 1 | Task 1 |
| Job 1 | Task 3 |
| Job 1 | Task 5 |
The jobs are still in descending order, but the tasks within the same job are now in ascending order. Depending on the type of grid, data may be queried locally. With a local data store, this operation triggers no server-side interaction. With a remote data store (which is the case for all paged grids), the database is queried to fetch the requested page with the right set of parameters: filters, groups and sorters.
### Clear sorting
Remove a sorter from a column with a single click in the column's header menu:

This re-evaluates the query and may return a different data set.
## Filtering
The filter type shown depends on the column's data type. As with sorting, you can define multiple filters on a grid. Only records that match all of the filters are returned.
There are four basic data types:
- Text
- Date
- Boolean
- Number
### Text field
For a text column, you can enter a text query:

Dime.Scheduler then retrieves every record where the column *contains* this text. To require an exact match, tick the checkbox below the text field [^1].
A few symbols have a special function:
| Symbol | Description | Example |
| ------ | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `<>` | The "not" operator that inverses the query | `<>`SO0006 will retrieve all items that *do not contain* the text SO0006 |
| | | The "or" operator | SO0006|SO0005 will retrieve all items that contain the text SO0005 *or* SO0006 |
| & | The "and" operator | SO0006&SO0005 will retrieve all items that contain the text SO0005 *and* SO0006 |
| * | The starts with or ends with operator | \*SO will retrieve all items that end with SO.SO\* will retrieve the items that start with SO. |
| '' | Two single quotes - without a whitespace - implies an empty values filter. | '' |
As the examples show, these symbols can be combined. A query like `<>`SO0006&SO|SF&`<>`SL* is valid. Decoded, it places the following filters on the query:
- Exclude every record that contains SO0006;
- Include all records that contain SO or SF;
- And exclude any record that starts with SL
### Date field
Date fields are a little more involved to query, so the filters let you find records with a date earlier or later than the one you enter:

This list also includes two special filter types: Today and Tomorrow. The key thing to know is that these are dynamic while the others are static, since today and tomorrow change every day.
### Number field
Like the date filter, number filters let you filter on values smaller and greater than the number you enter:

### Boolean field
Boolean fields usually appear as checkboxes in the grids. In the filter menu, this true/false type is shown as "Yes" and "No" options:

## Grouping
Grouping is the odd one out among these features, because it always runs locally rather than at the database level. Dime.Scheduler only groups the data on the current page. As a result, items in the same group may be spread across multiple pages, depending on the size of the data, sorting and filters. Grouping is also limited to one level; nested grouping is not (yet) supported.
To group a column, open its menu and select the grouping item:

Once grouping is applied, you can expand and collapse groups:

As the tooltip indicates, you can collapse the other groups with `CTRL + click`.
To remove grouping, open the column's menu again and select the corresponding item:

## Paging
To handle large amounts of data, Dime.Scheduler pages the grids. Instead of showing everything at once (which would be slow), the grid shows a subset and lets you flip through pages.

1. Navigate to the first page
2. Navigate to the previous page
3. Current page number: type a page number to flip to that page
4. Navigate to the next page
5. Navigate to the last page
6. Refresh the current page
7. Number of records per page: use the arrows or enter a number to change the page size. The grid reloads automatically when this value changes.
8. Shows which set of records is displayed out of the total in the grid [^2].
## Stateful grids
Some grids are stateful, which means you can save templates and share them with coworkers.
Every grid has its own implementation (its buttons and flags may be part of the state), but all of them store at least the following:
- Selection and order of the columns
- Column width
- Column filters
- Column sorting
- Page size
A stateful grid is easy to spot. When these icons appear on the toolbar, the grid is stateful. The buttons are covered below in the order they appear.
For more about layouts, read [the layouts guide](/user/layouts).
### Open layout
Each dock panel holds one component. If that component is a grid, you can apply a layout to it. You can open as many layouts as you want during a session. Click the lens icon to open a modal grid listing every layout available for that component:

A layout is available when it meets at least one of these conditions:
- The layout was created by that user
- The layout was shared with all users of the organization
- The layout was shared with a group the user belongs to
Opening a layout does not modify the active user profile. Even if the profile has a default layout for that component, no changes are made to the profile. This lets you try out different presets on the fly without committing to those settings. State is simply a way to store presets so you do not have to reconfigure grids every time you start the application.
### Reset layout
Once a layout is loaded and applied, you can keep adjusting the component's layout. This is often used for on-the-fly queries to find a particular record. Such a query may lead to a new layout (see [save layout](#save-layout) and [save as layout](#save-as-layout)). When you want to start over, the reset layout button returns the component to its original state, with its own filters, sorters, groupers and so on.
### Save layout
With the right security clearance, you can create and update layouts. The floppy disk icon updates the **active layout**. A few caveats apply:
- You can only update your own layouts, but you can build your own layouts from other users' layouts.
- When the standard layout is configured and then stored, you are redirected to the "Save as" feature, since there is no record yet in the database.
- You can only save default layouts in profiles you own.
- You cannot save layouts on a standard profile; a profile must exist before layouts can be saved.
It is worth stressing that the **active** layout is modified, not necessarily the default layout. When you start Dime.Scheduler with a default profile whose components have a default layout, the active layout equals the default layout. But once you open a different layout, that one becomes the active layout, and saving applies your changes to it rather than to the previous (or default) layout.
### Save as layout
The "Save as" feature works like it does in word processors such as Microsoft Word. Instead of overwriting the current layout, you create a new one. To save the new layout, Dime.Scheduler needs a name, whether it should become the new default layout, and the sharing options. The modal window prompts for all of these:

### Manage layouts
The last state feature lets you rename a layout, mark it as the default for that component, and delete it.

Every component in the profile can have its own default layout. For example, two open tasks grids in one profile may have different default layouts. This is also why a profile must exist before you can store layouts.
The same caveats as in save layout apply: you can only remove your own layouts and set your own default layouts for the profiles.
## Optional feature: Search
Many grids in Dime.Scheduler also have a search bar. It uses the same framework as the column filters but takes a slightly different approach: rather than filtering on one column, it searches all *visible* columns. This makes the filter much broader in scope than a column filter.
Take the following open task grid:
| Job No | Task No | Customer Name |
| ------------------ | ------- | ------------- |
| Job 1 | Task 1 | Customer 1 |
| Job for Customer 1 | Task 2 | Customer 2 |
| Job 3 | Task 3 | Customer 3 |
When you search for "Customer 1", Dime.Scheduler looks in the "Job No", "Task No" and "Customer Name" columns for a value that contains "Customer 1". In this data, two rows match.
By contrast, applying two column filters on "Job No" and "Customer Name" would return nothing, because no record has "Customer 1" in both the "Job No" and "Customer Name" fields. The search bar handles exactly this kind of question: any match in any of the columns counts.
## Export
The icon exports data to Excel files. As with the state functionality, the visible columns and their order are exported to the requested file format.
When you click this button, the file downloads right away. The file name format is `Dime.Scheduler [Grid type] [User Name] [Date].xlsx`.
Here is what to expect inside the file:

## Copy data
You can copy data from the grids with standard text selection: select a cell and drag until the text you want is highlighted.

Then press `CTRL + C` to copy the data to the clipboard. You can copy multiple rows and columns. Depending on where you paste, the software preserves the tabular format.
Some grids also support drag and drop. Because it is hard to tell whether you are dragging a record or selecting text, these grids add a button to toggle the drag behavior. Their default behavior is drag and drop. Click the button to enable text selection mode, which temporarily disables drag and drop. Click the button again to restore the original setting.
## Read more
- [Components](user/components/introduction.md)
- [Layouts](user/profiles-layouts.md)
[^1]: An exact match is therefore not necessary. For case-sensitive database, the casing of the text has a significant impact on the result. For example "My text" is a different query than "my text".
[^2]: Note that this the total number is calculated via the data-driven security algorithm.
---
## News & Announcements
import { FaBullhorn } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[{title: "News", link: "https://app.dimescheduler.com/news"}]} />
The **News** page brings Dime Software's product updates straight into the app: new releases, scheduled maintenance windows, and other platform-wide announcements. It has two sections: **Announcements** at the top and a **Releases** timeline underneath.
## Announcements
Announcements are short messages broadcast by Dime Software to all tenants. Typical use cases include:
- Heads-up about an upcoming scheduled maintenance window.
- Notice that a new platform feature, region, or integration has gone live.
- Reminders about deprecations or breaking changes that need attention before a cut-off date.
Each card shows the title, the publication date, and a body rendered as Markdown, so links, lists, and emphasis all work. When there are no active announcements, the section shows a short placeholder line.
## Releases
The **Releases** section is a vertical timeline of recent Dime.Scheduler product releases. The newest entry is highlighted with a coloured dot, and older entries are grouped under a year pill so you can scan back in time.
Every release card shows:
- The release date.
- A checklist of headline features and improvements shipped in that release.
- A **Read full post** button - when the release links to a long-form blog post - that opens the full release notes in a new tab.
By default the page shows the six most recent releases. To see the full history, click **Show earlier releases** at the bottom.
## Errors
If either section fails to load (for example, when the news service is temporarily unreachable), a red error banner appears in place of that section's content. The other section keeps rendering normally, so a problem with the announcements feed never blocks the release timeline. Refresh the page once connectivity is restored.
## Related
The News page only carries content posted by Dime Software. Tenant-specific reminders - expiring API keys, expiring client secrets - are delivered by email instead. See [Email Notifications](notifications.md).
---
## Email Notifications
import { FaEnvelopeOpenText } from 'react-icons/fa';
import BreadCrumb from '@site/src/components/Breadcrumb';
} items={[]} />
Dime.Scheduler emails administrators ahead of expiry events so credentials never lapse unnoticed. Reminders go out when:
- An [API key](../../administration/apikeys.md) is about to expire.
- A client secret configured in the Application Setup or the [Exchange setup](../../integrations/exchange/azureapp.md) is about to expire.
Each reminder is sent to the **notification email** address configured on the tenant. Set that address and the API key reminder cadence on the **General** tab of [Application Setup](../../administration/setup.md). If no notification email is configured, no reminders are sent.
---
## Recurring appointments
A recurring event is one that happens more than once at a regular interval. The implementation in Dime.Scheduler follows the same logic as Outlook and other calendar systems, so anyone who has set up a repeating meeting before will feel at home.
## Appointment editor
The appointment editor has an extra combobox where the planner builds the pattern. Recurring appointments are off by default, so this is an optional field. If an appointment doesn't need a pattern, leave the repeat field set to 'None' and you're done.

Once you select an interval, a button appears:

This legend button states the recurrence rule as a sentence rather than a pattern. In the example above the rule is simple: a daily occurrence with no end date. The sentence becomes especially helpful when the rule is more complex, such as a two-monthly occurrence on the second weekend day.
The legend button opens a window where you build the **recurrence rule** by choosing the _frequency_, the _interval_ and the _end time_ of the repeat:
- **Frequency**: Daily, weekly, monthly, yearly
- **Interval**: the options vary according to the selected frequency
- **End time**: on date or after a given amount of occurrences.
## Recurring patterns
### Daily occurrences
A daily recurring appointment is the simplest case. As the name suggests, the interval reads as "every x days".

This example creates an appointment every day until the first of January 2024.
### Weekly occurrences
The same logic applies to weekly occurrences (and to all the others). The one extra variable is that besides the frequency, you also choose the days. At least one day must be selected.

This example creates an appointment every Wednesday until the first of January 2024. The appointment you create or update must also comply with this rule. If you set this rule for an appointment that starts on a Monday, Dime.Scheduler moves the original appointment to the first available date in the series.
### Monthly occurrences
You can select one of two modes:
- Day picker
- Presets
For example, you can create appointments on the 1st, 3rd and 25th every other month. Or you can pick every second weekend day, or every third day of the month.

This example creates an appointment on the 1st, 15th and 30th of every month until the first of January 2024. The same caveat as the weekly occurrences applies: every appointment must comply with the recurrence rule, even when the original appointment doesn't.
### Yearly occurences
The yearly recurring appointment closely mirrors the monthly pattern, except for the scope: it works on months rather than days.

You can refine the occurrence rule further, though this is entirely optional. For example, "every 3rd Friday in March, July and November until May 2030" is a perfectly valid recurrence pattern.
## Updating recurring appointments and occurrences
When you create a recurring appointment, all occurrences are created and published instantly. Occurrences appear on the planning board with a special icon:

Modifying a recurring appointment can mean one of two things:
- Update the _entire sequence_
- Update the _occurrence_
An occurrence is a single instance in the series of appointments that make up the recurring appointment. When Dime.Scheduler creates such a sequence, the appointments are created independently yet stay connected. They can move freely on the planning board while remaining linked together, so removing or updating one appointment doesn't break the link with the others in the range. These instances are also called **"exception occurrences"** and are marked with an exclamation mark in front of the recurring icon.

Any action other than the appointment editor that changes a record produces an exception occurrence. For example, changing the duration of an occurrence by drag-and-drop flags that appointment as an exception occurrence automatically. As noted, the recurring appointment itself can only be managed through the appointment editor.
When the appointment is part of a set, Dime.Scheduler asks the planner whether to update the whole series or just this occurrence:

If you update the entire sequence, all appointments (and assigned resources) are evaluated and then updated or removed as needed. Any occurrences (exceptions to the sequence) are reset so the recurrence pattern is applied. However, if the pattern and the duration of the sequence are unchanged, the occurrences' dates are not overridden.
The rule for resetting the recurring appointment is therefore simple: if the update changes the recurrence pattern or the dates (or duration), the recurring appointment is recalculated. If you only update fields like subject, resources or indicators in the editor, the exception occurrences keep their status (exception occurrence), dates and resources, and only the master data is updated.
When you select the entire series, one more thing needs confirming: the start date of the series.

By default the date of the first occurrence in the series is used, but you can override this in two ways:
- Start the recurring appointment on the start date of the appointment you are currently editing
- Select a different date
For example, suppose you update the second occurrence of a recurring appointment, apply the "all" option and check "use appointment's start date". The series shifts to the right by one occurrence. Leave the box unchecked and keep the original date in the start field, and the first occurrence starts on the same day as before.
## Repeat limits
For reasons specific to Dime.Scheduler, a recurring appointment needs an end so the ecosystem doesn't overflow. A never-ending series of appointments in the back office would cause problems, so every recurring pattern must end at some point. You can define the end of a repeatable event in two ways:
- Amount of occurrences
- On date
The first option caps the number of appointments; the second stops the series on a date. The recurring pattern holds all the information on when to create the appointments, and the end repeat setting sets the latest possible date of the final appointment in the series.
You can change this value, and just like the pattern it triggers the system to recalculate the entire series. This may add, update or remove appointments.
Here is an example showing the difference between the two for the `weekly on Monday` pattern (start date is Tuesday 1 January 2019):
| Date | 5 occurrences | Until February 1 |
| ---------- | ------------- | ---------------- |
| 7 January | ✅ | ✅ |
| 14 January | ✅ | ✅ |
| 21 January | ✅ | ✅ |
| 28 January | ✅ | ✅ |
| 4 February | ✅ | ❌ |
## A word about iCalendar
Calendars are everywhere: on your phone, in your work and personal mailbox, on your computer, in your office lobby, on your wall. Now imagine every developer or company that builds a calendar widget used its own proprietary format for storing appointments. The result would be a mess in which sharing calendars and meeting requests, as we do today, would be almost impossible.
As in many other areas, standards and specifications enable interoperability by giving the industry a common understanding of how to communicate between systems. For calendars, the industry standard is the **iCalendar** (or RFC5545) specification, which also defines how to exchange recurring event information.
For example, the rule `FREQ=DAILY;COUNT=10;INTERVAL=2` specifies 10 occurrences that happen every other day. Dime.Scheduler can convert this string into a set of appointments, and it follows the same rules when users create recurring appointments in the appointment editor. In other words, Dime.Scheduler is compliant with the RFC5545 specification.
## Read more
- [Internet Calendaring and Scheduling Core Object Specification](https://tools.ietf.org/html/rfc5545)
- [Planning board](/user/components/planningboard/appointments#the-appointment-editor)
---
## Tags
:::info[Status: Preview]
Tags are currently in preview. Functionality and behavior may change between releases. Not recommended for production-critical workflows yet.
:::
A category tells the planner what kind of work an appointment is. A time marker flags a moment in it. Both answer exactly one question, because an appointment has exactly one of each. Real work is rarely that tidy: a job can be urgent *and* need a night shift *and* require the customer to be called the day before. Tags are how the planning says all three at once.
A tag is a small reusable label with a code, a name, an icon, and a color. A task or an appointment can carry as many of them as it needs, and they show up as icon badges on the appointment bar so the planner reads the whole story without opening anything.
## What tags solve
Before tags, extra context about a job had nowhere to live. Planners squeezed it into the subject line, invented category values like "Urgent - installation" that combined two unrelated ideas, or kept it in their heads. Each of those has the same problem: the information is not structured, so nothing else in the application can use it.
Tags separate the two axes. The category keeps answering *what kind of work is this*, and tags carry everything else: priority, required certification, customer sensitivity, whether parts still need to ship. Because a tag is a real record with a stable code, the back office can send it along with the task, and the planner can add or remove tags on the board without the two getting confused.
## Where tags come from
Tags are defined once by an administrator on the [indicators page](/administration/settings/indicators#tags), alongside categories, time markers, and pins. Each tag has:
- A **code**, the stable identifier that tasks and appointments reference. It is fixed once the tag is created, so renaming a tag never breaks the records that use it.
- A **name**, which is what the planner sees.
- An **icon** and a **color**, which is what the planner sees on the board.
Tags can also be created and maintained from the back office through the [API](/develop/api/indicator#tag), which is the right approach when the tag list mirrors something that already exists in the ERP.
## Applying tags to appointments
There are three ways to put a tag on an appointment, and they all write the same thing.
### From the appointment card
Open an appointment on the [planning board](/user/components/planningboard/index.md). The **Tags** field is a multi-select: pick one or more tags, and save. This is the natural route when you are already editing that appointment. The field is disabled on locked appointments.
### By dragging from the indicators panel
The [indicators panel](/user/components/indicators) lists the tags in their own section. Drag one onto an appointment on the board to apply it.
Note the difference with categories and time markers: dropping a category *replaces* the appointment's category, because there can only be one. Dropping a tag **adds** it to whatever the appointment already carries. That is what makes it fast to build up a combination of tags without losing the previous ones.
To apply a tag to many appointments at once, select them on the board first, then click the bulk-update icon next to the tag in the panel.
### From the back office
Both tasks and appointments accept a list of tag codes over the [API](/develop/api/task#tags). A connector that sends `URGENT;NIGHTSHIFT` on a task sets both tags on it.
A connector that says nothing about tags changes nothing. This matters: an older back office that has never heard of tags keeps sending updates for its tasks, and the tags a planner set by hand survive those updates untouched. Clearing tags from the back office is a deliberate act, done by sending an empty value rather than by omitting the field.
## Removing tags
Right-click one or more appointments on the planning board and choose **Remove tags**. This clears every tag from the selection in one go. Locked appointments are skipped.
To remove a single tag while keeping the others, open the appointment card and deselect it there.
## Tags on tasks, and what happens when you plan them
A task carries its own tags, and they are visible in the open tasks list. When you plan that task by dragging it onto the board, the new appointment inherits the task's tags, the same way it inherits the subject.
From that moment the two are independent. Tagging the appointment does not tag the task, and a later update to the task's tags from the back office does not reach back into appointments that were already planned. This is deliberate: the task describes the work that was requested, the appointment describes the visit that was scheduled, and the two legitimately diverge.
## Reading tags in the workspace
**On the planning board.** Tags render as a row of small icons at the top of the appointment bar, above the subject, each in its own color. Hovering an icon shows the tag's name, so an unfamiliar icon is never a dead end.

The icons stay legible on a dense board precisely because they are icons: five tags cost a single line of pixels, where five words of subject text would not fit at all.
**In the grids.** The [open tasks](/user/components/opentasks), [planned tasks](/user/components/plannedtasks), and [route sequence](/user/components/routesequence) grids all have a **Tags** column that renders the tags as colored chips. The column is hidden by default: switch it on through the column picker if your team leans on tags.
**In the details pane.** `Tags` is available as a field on both tasks and appointments in the [details component](/user/components/details), so it can be added to whatever field layout you use.
If a tag is deleted after it was applied, the records that still reference its code simply render nothing for it. Nothing breaks, and no error is shown.
## Preview limitations
Tags are usable today, but the feature is still growing. Two things to be aware of while it is in preview:
- **You cannot filter, sort, or group by tag.** The Tags column is a read-only visual in the grids. Codes are stored as a single delimited value, so sorting on the raw string would be meaningless, and a proper tag filter has not shipped yet.
- **Only tasks and appointments can be tagged.** Resources, jobs, and projects cannot.
## Read more
- [Managing tags](administration/settings/indicators.md#tags)
- [Indicators panel](user/components/indicators.md)
- [Tag API reference](develop/api/indicator.md#tag)
---
## Create a profile
import ReactPlayer from 'react-player'
This guide walks you through building a profile from scratch: arranging a few components and giving each one a default layout. For the concepts behind [profiles and layouts](user/profiles-layouts.md), see the user manual. Here we go straight to the hands-on steps.
Prefer to watch? Here's the video:
## Getting started
This guide assumes the user has a role with the "Personalization" user action.
Start from a known baseline. The quickest way is to take the standard profile that Dime Software provides and build on it. Navigate to Profile -> Manage, uncheck the "Default" checkbox, and save the changes:

Refresh the page, and you'll be on the standard profile.
## Manage components in the profile
Next, organize the components. In this example we want to manage projects, and the Gantt chart is the perfect fit. It isn't on the standard profile, so add it first.
Go to the "Profile" menu and scroll down to the components section. Locate the "Gantt chart" component, then click and hold it and drag it onto the canvas:

To clear up some space, remove three components from the canvas: Map, Category Overview and Notifications. Click the small icon next to each component. Don't confuse this with the icon on the right-hand side, which closes the entire stack.

With the extra room, you can resize the dock panels to give your main components more space:

One final touch: change the styling. Click the brush icon in the topbar to adjust the color scheme and the theme:

That's enough for now. Save the profile before continuing.
If you're working on a blank profile (shown by the orange warning icon in the top bar), you'll need to give it a name and decide whether to flag it as your new default profile. For existing profiles, nothing extra is required.
To save, go to the "Profile" menu and click "Save". A modal window asks you for a name and whether to mark it as the default profile:

The orange warning icon in the top bar disappears the moment the profile is saved. From now on, the profile you just saved is the active one, and any changes you save are stored in it. You can always check the active profile by expanding the left navigation menu:

Saving also unlocks a range of buttons in the components. For example, the planning board's buttons went from to .
You can now manage layouts for the components in this profile and mark them as the default layout. That's the next section.
## Create default planning board layout
With the profile set up, turn your attention to configuring the components. For this example, work to these requirements:
- Projects are only done by resources in the "Resource" type.
- The planner wants to see a daily planning.
- The work day starts at 09:00 and ends at 18:00.
### Filter planning board
The first requirement is straightforward: add a filter to the "Resource Type" column in the planning board:

Next is the daily planning. You can keep the current view, but it's best to activate the "Daily" view:

There's a second part to the daily planning configuration, but it isn't set in the component itself. Before you get there, save the layout and mark it as the default layout:

You've now set two defaults:
- The "Planning projects" profile is the default profile.
- The default layout for the "Planning board" component instance in the "Planning projects" profile.
### Set range
The standard profiles show a 14-day interval in the planning board. In this example, you need to reduce it to one day. Adjust this in the settings area:

The "Range" combobox sets the default time span in the planning board. Notice how the date picker adjusts to match.
### Set start and end time
In the same window, set the "Start & End Time":

Remember to save the profile: the settings in this fly-in window are stored in it. Go to "Profile" and click Save.
## Conclusion
Refresh Dime.Scheduler, and you'll see exactly the same view as before:
- The color and theme are the same
- The Gantt chart is still available
- The other components, such as the map and notifications, have been removed
- A planning board that shows the hourly planning for one day between 9 AM and 6 PM
You now have a default profile and a default layout. This is just the start: you can create as many profiles and layouts as you want, building a library of presets so you don't have to set up your environment every time you open Dime.Scheduler. This is usually done at go-live, but you can add more profiles and layouts whenever you need them.
## Read more
- [Profiles and layouts](user/profiles-layouts.md)
---
## Logging in for the first time
This guide walks you through your first sign-in to Dime.Scheduler and the one-time setup that follows.
## Login
Open your browser and go to the Dime.Scheduler web application. Ask your application manager for the correct URI. The login screen appears, and its language may vary depending on your local settings.
Sign in with the [login type](user/authentication/types.md) configured for your account. Check with your application manager how your account is set up:

As the screenshot shows, you can sign in two ways:
- **Database login**: enter your username and password, then click Login.
- **Azure/Office 365**: click the Azure / Office 365 button. You are redirected to the Office 365 login screen to enter your credentials. The first time you log in, you are asked to allow Dime.Scheduler to use your credentials, so click Accept. Note that the Office 365 login screen may appear in another language.
## Choose language and time zone
The first time you start the application, and if the user administrator has not already set them, you are asked to choose the **language** and **time zone** you want to work in.
Select both from the dropdown boxes and submit:

You can change these settings at any time from the **_Profile_** area.
---
## Reset password
:::note
This tutorial only applies to Forms users. To reset your password for Microsoft Entra ID, contact your network administrator.
:::
Forgot your password and can't log in? Follow these steps to set a new one. If you're still logged in, log out first.
When you next open Dime.Scheduler, you'll land on the login screen:

Click the 'Forgot password' link:

Enter your e-mail address and click 'Send Password Reset Link'. Within a few moments you should receive an e-mail from support@dimescheduler.com. Check your spam folder if it doesn't arrive.

Click the 'Reset password' link in the e-mail. It takes you to this page:

Enter your new password. Keep the [password requirements](administration/user/users.md#dimescheduler-identity-service) in mind:
- Must be at least 6 characters long
- Must contain a number
- Must contain a lower-case letter
- Must contain an uppercase letter
- Must contain a special character
Click 'Reset'. You can now log in with your new password.
---
## User Guides
import DocCardList from '@theme/DocCardList';
These are short, start-to-finish walkthroughs for planners. They follow the order you meet
them in practice.
[Logging in for the first time](/user/guides/logging-in-for-the-first-time) is the place to
begin, because how you sign in depends on the account type your administrator gave you. If
that account is a forms account and the password no longer works,
[reset password](/user/guides/reset-password) covers the recovery flow. Microsoft Entra ID
accounts are managed by your own IT department, so there is nothing to reset here.
Once you are in, [create a profile](/user/guides/create-profile-layouts) walks you through
building a view that matches how you actually plan, rather than working with the default
arrangement. That is the one guide worth doing deliberately: the profile you build is what
you look at every day.
For task-oriented walkthroughs that cross several features at once, see the
[cookbook](/cookbook).
---
## Using Dime.Scheduler
import { FaCalendarAlt, FaPalette, FaCog, FaBell, FaBook } from 'react-icons/fa';
import { LandingHero, CardGrid, Card, PathwayList, Pathway, Note, CTA } from '@site/src/components/LandingKit';
}
featured
/>
}
/>
}
/>
}
/>
}
/>
The Dime.Scheduler workspace is highly customizable. **Profiles** control which dashboards and panels you see; **layouts** control how each dashboard is arranged. Most users start from a profile their administrator built and tweak the layout from there.
New to the product? Start with [How it works](/user/basic-concepts) for the design philosophy and the key terms you'll see throughout these manuals. Everything else is in the sidebar on the left.
---
## Layouts
## What is a layout
A layout is a snapshot of a component: a reusable template that captures how a component is configured, so it can be shared with other users or applied in other profiles. Like a profile, a layout stores no transactional data. It only stores the settings that make sense for that component. The concept is close to a profile, but with a narrower scope and context.
As explained in more detail in the [grids](user/features/grid.md) section, a layout typically stores:
- Selection and order of the columns
- Column width
- Column filters
- Column sorting
In the [components chapter](components/introduction.md), every component lists the properties it keeps in its state. The [planning board](/user/components/planningboard/toolbars#planning-board-state), for example, also stores the view, appointment height, calendar mode, and more.
## Layout management
Components that support layouts carry these four buttons in the top bar:
- : Load layouts in the component
- : Save the current layout
- : Save the layout as a new layout
- : Manage layouts
These map to the standard grid features. Rather than recomposing a grid every time you start Dime.Scheduler, save the configuration with the toolbar buttons and reapply it whenever you need it.
A layout becomes available to other instances of the same component type as soon as the state is saved. Add a new component to a new user profile, and the layout is ready to use. So a layout works across profiles and users, but only for that one component type. Save the state of an open tasks grid, and the notifications grid will not see that layout, because they are different components.
### Manage layouts
User profiles matter to layouts only at profile initialization. In the management window, users can mark **default layouts** for the components in a profile:

When the profile loads, those default layouts are applied with it. Default layouts are optional: if none is set, the component falls back to the standard layout defined by Dime. As with profiles, users can change the component settings, or load other layouts, at any point during the session.
### Load layouts
You can also load and apply layouts on the fly. The button shows a list of available layouts. The same rule applies: a user only sees layouts that were shared (publicly or through a user group) or that they created themselves. Loading a layout this way does not change the default layout for the component; reload the profile and nothing carries over. As with profiles, the only real difference is the scope of the concept.
## Read more
- [Profiles and layouts](user/profiles.md)
- [Guide](user/guides/create-profile-layouts.md)
---
## Look & Feel
import {FaHome, FaWrench} from 'react-icons/fa';
Get to know the Dime.Scheduler interface so you can find your way around and explain it to a customer. This page walks through the structure of the screen and how the main pieces fit together.
The image below shows the main view:

The view has three parts:
- Side and top navigation menu
- Application window
- Profile area
## Navigation
### Main menu
The side navigation menu guides users through the modules of the application. It also shows the name of the current user and the active user profile. The menu is secured by user roles, so what a user sees may vary depending on their permissions.
The menu has the following items:
- Planning: takes you to the application view with your planning board(s). This is the default view that is shown when Dime.Scheduler is launched.
- Administration: contains references to user management.
- Application setup with general settings and configuration
- Settings: has submenu items to manage master data such as resources, field templates, pane layouts and calendars.
- Exchange settings.
- Agenda has submenu items to show the planning in a read-only mode
- Info: about the license and the tenant
- Transactions: list of modifications to the planning
Toggle the menu button to _collapse_ and _expand_ the side navigation menu. When the menu is collapsed, hover over an item to make its submenu pop out automatically.
At the very bottom of the side navigation menu, two shortcuts let you toggle Dime.Scheduler into full screen mode and log off from the application.
### Links
Just below the side navigation menu sits a section reserved for links. The application manager manages these in the corresponding [setup view](administration/settings/links.md).
## Application window
This is the canvas where planners do their work.
It is the heart of Dime.Scheduler, holding every component and action you need for scheduling: planning boards, open tasks, planned tasks, filtering, map, and more. The rest of this manual covers working in the planning view in detail.
## The profile area
A [user profile](user/profiles-layouts.md) is a key concept that this manual covers in depth. In short, a profile captures the state of the planning canvas (which components are loaded, their height and width, which layout is the default, and so on) and makes it reusable across sessions and users.
You manage the profile mainly through the top bar's profile menu item:

The sections below explain each part and how it works.
### Profile
Manage profiles here. Users can create and update profiles, load profiles on the fly and change the default profile. They can also add new components to the canvas or to existing planning boards that they are allowed to use.
### Event notifications
Receive general system notifications here. When you have new notifications, the icon shows the count.
### Account
Lets you log off.
### Styling
Configure the theme and colors here. These settings are stored as part of the user profile.

The selected theme or color applies immediately, but like the other settings in the profile, it is not saved automatically. To keep the changes, store the profile explicitly.
### Profile settings
The cogwheel icon shows and hides a set of buttons and switches that configure the profile. Later parts of the user manual explain each of these settings in detail.
This slide-in pane has three sections:
- General settings like time zone
- Scheduler settings like start date and date span
- Map settings such as the unit of distance and route profile
## Status icons
The left-hand side of the top bar has space for four icons: .
### Connection
The connection icon shows the connectivity status between the browser and server. An orange or red icon signals a problem with the connection, which means real-time updates may not arrive during the session. This does not necessarily mean the server is down; it just means tasks and appointments may not update automatically. Refreshing the components usually still works. If it does not, there is a bigger issue at hand.
The browser and server automatically try to re-establish the connection. When that fails, a page refresh usually solves the problem.
### Zoom level
Some features, such as drag and drop and rendering resources and appointments on the planning board, rely heavily on coordinates. Users may see degraded performance or incorrect behavior when the browser zoom level is not 100%. The device pixel ratio is a close approximation of the browser zoom level and the scale settings of the user's monitor; it tells us how many of the screen's actual pixels are used to draw a single CSS pixel. When the ratio is not 1, the icon appears in the top bar. This does not change any behavior; it simply informs the user.
### Default profile
Most state-related features are only available when there is an active (or default) profile. The first time you launch Dime.Scheduler, you typically start with the standard profile that Dime Software provides, and you cannot store layouts until you save a profile. The icon in the top bar reminds you of this. As soon as you save a profile and work within its context, the icon disappears and the state features become available.
## Query string parameters
Query string parameters let you change how the application behaves. The following parameters are supported:
| Name | Type | Comment |
| ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `skipProfile` | Boolean | Set to true to skip loading the default profile |
| `waitForResourceFilters` | Boolean | Set to true if the planning board should wait for the default resource filters to be initialized |
| `profile` | String | Choose the name of the profile to be loaded instead of the default - or standard, if there is no default - profile. |
Here is the query string in action: `http://www.dimescheduler.com/?waitForResourceFilters=false&profile=My%20Profile`. The application loads a profile named 'My Profile' and does not wait for the default resource filter to load before fetching the resources and appointments in the planning board.
## Read more
- [Logging in for the first time](user/guides/logging-in-for-the-first-time.md)
- [Profiles and layouts](user/profiles-layouts.md)
- [Links](administration/settings/links.md)
- [Components](user/components/introduction.md)
---
## Editing appointments
Users whose role includes the `Edit` user action can modify an appointment's visual indicators straight from the mobile app. They have two ways to do this: tap the chips at the top of the screen, or tap the icons at the bottom-right.
Either action opens a modal listing the available indicators. When the user picks one, Dime.Scheduler sets the value and publishes it to the connected systems, just as it does elsewhere in the product.
## Permission requirements
Editing requires a user role with edit permissions. If the option to edit appointments doesn't appear, ask your administrator to grant the necessary permissions.
---
## Mobile app

The Dime.Scheduler mobile app gives resources on the road and in the field an easy way to manage their own schedule. Available for iOS and Android, it is built for resources rather than planners. The web app remains the tool for planning work, and the mobile app complements it instead of replacing it. Planners can use the app too.
Prefer to follow along with a video? Watch the walkthrough here:
## What's covered
Here is everything you need to set up and use the Dime.Scheduler mobile app:
- **[Installation](installation.md)** - Download and install the app from the App Store or Google Play
- **[Sign in](signin.md)** - Authentication and backend selection
- **[Scheduling views](scheduling-views.md)** - My schedule, all planning, and appointment cards
- **[Settings](settings.md)** - Configure your mobile app preferences
- **[Location tracking](location-tracking.md)** - Share your location with planners
- **[Editing appointments](editing-appointments.md)** - Modify visual indicators on the go
---
## Installation(Mobile)
Get the Dime.Scheduler mobile app onto an iOS or Android device in a few steps.
## Install the app
Download the app from the App Store or Google Play:
The app runs on both iOS and Android. Tap the badge above for your device's store, or search for "Dime.Scheduler" in the App Store (iOS) or Google Play Store (Android).
## System requirements
- **iOS**: Requires iOS 12.0 or later
- **Android**: Requires Android 6.0 or later
Once you've installed the app, proceed to [sign in](signin.md) to start using it.
---
## Location tracking
The mobile app doubles as a location tracker. Instead of buying expensive, complicated hardware, your field workers can share their location with the planner straight from the app.
:::info
To push a location onto the map in the Dime.Scheduler web app, you need to opt in to the **advanced map license** of Dime.Scheduler.
:::
## Configure tracking times
Location tracking is _disabled_ by default. When you enable it, the app asks for permission to always track your location. The app doesn't actually track your location around the clock. The "always" permission is only what the operating system requires so the app can use location services from the background.
The app sends updates to Dime.Scheduler only when the current time falls in the range shown in the middle of the screen, and only when that moment isn't paused.
Tap the caret icon in the middle of the screen to manage the schedule:
Tap 'Pause tracking' to add exceptions to the regular schedule, periods during which the tracker should not send updates:
## Location services
### Turn on location services
Always allow the mobile app to use your location. The app asks for permission automatically, but you can also grant it from your device settings:
- [Android](https://support.google.com/accounts/answer/6179507?hl=en)
- [iOS](https://support.apple.com/en-us/HT207092)
To repeat: the app does not track your location all the time, only during the times set in the location tracking screen. The 'Always Allow' option is what unlocks background tracking, so the app can report a location even when it isn't open.
### Background service
The app asks the device to run its background service every 15 minutes, but there's no guarantee the device will dispatch the job. Many factors are out of our control: battery level, memory usage, how often and how much you use the app, and more. iOS in particular applies a strict algorithm: not opening the app for a day can lower its score and may suspend the background service entirely until you launch Dime.Scheduler again.
With frequent use, the background service should keep running without interruption.
---
## Scheduling views
## My schedule
The mobile app gives each user three ways to look at the schedule. This page walks through all of them.
Your user account can be linked to a resource in Dime.Scheduler, so the planner sees you as a resource on the planning board. You can view your own schedule when the administrator sets the e-mail address field of the resource to the e-mail address you used to sign in.
:::tip[For Administrators]
To enable the 'My Schedule' view for mobile users, ensure that the email address of the user account matches the email address field of the corresponding resource. For more details about this configuration, see the [resources documentation](/administration/settings/resources#mobile-users).
:::
If that match isn't in place, the 'My Schedule' screen won't appear. Your administrator can fix this at any time; once they do, log out and back in to see the change.
At the bottom of the page, you can navigate between the different views:
- Schedule
- Day
- Week
- Month
## All planning
The All Planning view is the mobile version of Dime.Scheduler's planning board. It lists the resources you have access to, along with their schedules.
To narrow the list, tap the button in the top-right corner. Check the resources you want to see, and uncheck the ones you don't. To save the selection, tap the icon in the top-left corner of the screen.
## Appointment card
Tap any appointment in the My Schedule or All Planning views to see its details. The card has four tabs:
- **Info**: general information on the appointment
- **Map**: shows the location on the map
- **Resources**: shows who's been assigned to the appointment
- **Notifications**: an optional list of notifications related to the order
---
## Settings
The settings screen is where you tailor how the mobile app behaves. Open it from the main menu. The current edition offers the following options:
- **Visible working hours**: Drag this slider to set the time range shown on the planning boards in the mobile app.
The choices you make here affect how information is displayed throughout the app, so you can match the experience to your working patterns.
---
## Sign in
:::info[User Setup Required]
Your administrator will set up your user account and ensure your email address matches the resource you're assigned to. For more information about how user accounts are configured, see the [user setup documentation](/administration/settings/resources#mobile-users).
:::
## Sign in
Open the app after installing it and you'll land on the sign-in screen:
Signing in requires credentials from the administrator who set up the account. To get started:
1. Watch for a confirmation e-mail from Dime.Scheduler.
2. Follow the instructions in that e-mail to set a password.
3. Return to the app and sign in with the e-mail address and new password.
## Select mobile backend
The first time the app is used, or after a reinstall, it prompts for the mobile backend to connect to. Choose **Production** for live use. The sandbox is for evaluation periods, before a production environment is ready.
When the backend and credentials are correct, the app signs in.
Once signed in, you can start exploring the [scheduling views](scheduling-views.md) available in the app.
---
## Organizing the workspace with dock panels
Every component sits inside a **dock panel**. You can stack these panels, resize them, and move them around to build a workspace that matches how you work. Width, height, stacking order, position on the canvas, and the title are all yours to change.
Dock panels are part of the visualization engine that powers Dime.Scheduler's flexible interface. The components inside them can serve radically different functions, but the panels that contain them all behave the same way. The rest of this page shows you how.
## Positioning a dock panel
Click and drag a panel's title to undock it from its stack and move it. While you drag, a shaded area shows where the panel will land if you drop it there.
Here is what you see before the drop:

When you drop the panel, it docks again to a stack. Depending on where you drop it, that can be an existing stack or a new one:
- For a new stack, drop the panel at the top, bottom, left, or right of another stack. The space is then split equally between the two stacks. You can [change the size](#resizing-dock-panels) afterwards.
- To add the panel as a new tab in a stack, drop it over or next to the other tabs in the panel's title bar.
In the example below, the planning board stack is split in two to make room for the open tasks grid:

## Add a component
The same options work for new components. There are two ways to add one:
- Open the profile menu in the top bar and **drag** an item onto the canvas.
- **Select a stack** first, then click the component you want. The component is wrapped in a dock panel and added to the stack you selected. The currently selected stack stands out with a slightly lighter shade of the theme.

## Resizing dock panels
You can also change the dimensions of a dock panel. Hover over the edge of a stack and the cursor changes. Click and hold, then drag to resize the panel in any direction.
Here is how to change the width:

And the height:

## Dock panel actions
Every dock panel has three buttons:
- Edit title
- Maximize
- Close
### Edit title
The first button lets you change the text displayed in the dock panel.
Before:

After:

As the warning in the modal points out, this is a pending change to the profile. Save the profile to keep it.
### Maximize dock panel
Maximizing a dock panel fills the entire canvas with that single component. Minimizing it restores the original view.

Like the previous button, this changes the profile. Save the profile to keep the change.
### Close dock panel
Closing a dock panel removes it and its component from your profile. You can always add the component back later, but any changes you made to the removed dock panel and its component are lost.
There are two close buttons: one removes the dock panel (next to the title), and the other removes the entire stack. Closing a stack closes every dock panel inside it.
Like the previous button, this changes the profile. Save the profile to keep the change.
## Read more
- [Introduction to components](user/components/introduction.md)
- [Grids](user/features/grid.md)
- [Guide](user/guides/create-profile-layouts.md)
---
## Profiles & Layouts
import ReactPlayer from 'react-player'
**Profiles** and **layouts** let users build personalized workspaces and share them with other users and user groups. They are a fundamental part of what makes Dime.Scheduler flexible, and they encourage collaboration because a workspace set up by one planner can be reused by the whole team.
A **profile** captures everything needed to recreate the user interface and its behavior after initialization. Think of it as a snapshot of the planning view's structure. It stores many settings, including but not limited to:
- Color
- Theme
- Sidebar menu collapsed state
- Dock panels and their position, width, height and the default layout of the component that is contained inside
- Planning board presets
- Map presets
- ...and many more
**Layouts**, by contrast, are tightly coupled to components. A layout is a snapshot of a single component: it holds the data needed to turn that component into a reusable template, so it can be applied by other users or in other profiles. It stores many settings, including but not limited to:
- Column selection
- Column width
- Sorting
- Filtering
- ...and many more
Keep in mind that layouts and profiles never store transactional data. They only store the settings that make sense for that component or profile. In that respect they are very similar, but as the next section shows, they differ in scope and context.
## Layouts versus profiles
Profiles and layouts look similar at first, but they serve two different functions. A profile is a snapshot of the planning view, while a layout is a snapshot of a single component within that view. A layout therefore has a much smaller scope: it deals with component settings like column selection, sorting, and filtering.
The example below makes this concrete. The area with the red border is profile territory; the blue area is managed by layouts:

Layouts only become relevant to profiles during **profile initialization**, because users can mark **default layouts** for their components inside a profile:

A default layout is the glue between profiles and layouts: without it, the two have no meaningful connection. Default layouts tell Dime.Scheduler which components have which layouts for which profiles. When a profile loads, its (optional) default layouts are applied. If a component has no default layout, it falls back to the standard layout defined by Dime. As with profiles, users can always change a component's settings (or load other layouts) during the session.
A layout is scoped to a single instance of a component. Every component in the view can have its own settings, no matter how many other instances of the same component the profile contains. It is common to have several open task grids in one profile with different filters, for example to split the data by region, customer, or order status.
Because layouts are snapshots of components, they can be reused in other profiles, and even by other users in their own profiles. Any combination of layouts and profiles is possible. Profiles record which components are captured, where they sit in the view, and which default layouts are defined. The schema below illustrates this: the left column shows the layouts grouped by component type, and the right column shows the contents of two profiles, also grouped by component type.

Layouts and profiles live in two separate catalogs: one for profiles and one for layouts. In this example, the planners have defined a few layouts for the open and planned tasks components to show records from the EMEA region and the LATAM region. In the second profile (orange), not every component has a default layout, so Dime.Scheduler applies the standard layout for the details, map, and notification components.
This system is efficient because it lets you reuse settings across profiles and users. Rather than loading the layout each time you start Dime.Scheduler, flag the record as the default layout for the profile and let it apply automatically.

When this profile is applied, the 'Tasks LATAM' layout is applied automatically to this component instance, and the same happens for every other component in the profile. When you create a new profile later, you can reuse the layouts you built earlier as well as the ones shared by other users.
## Users versus profiles
It helps to be clear on the difference between a _user_ and a _profile_, and how they relate. As explained under [users and user groups](user/basic-concepts.md#users-and-user-groups), a **user** is the physical person who operates the application [^3].
One thing a user can produce is a **profile**: a workspace configured to tackle a specific planning scenario. You might drop a few components, add others, and apply different filters to suit the task at hand. Rather than rebuilding that workspace every time the application launches, the user can save it, along with a set of other settings, to a profile.
Note that security filters are applied to the user running the profile, not to the profile itself. The profile is a template that runs in the context of a user session, so what each user of a profile sees is determined by their own security clearance.
## Authorization
### Manage layouts and profiles
Not everyone can create, update, delete, and load profiles. To store profiles and layouts, a user needs the "**Personalization**" user action. When that user action is missing from all of their roles, the user can only load layouts.
Even an authorized user only sees the profiles shared publicly or with a user group they belong to. Profiles they did not create cannot be overwritten or deleted, but they can serve as the basis for a new profile. It works like a read-only Word document: you can only save it as a new document.
### Data
Sharing a profile or layout does not share its authorization levels. The [data-driven security](user/authentication/datadrivensecurity.md) and [role-based access control](user/authentication/roles.md) mechanisms stay in place. Profiles and layouts only carry settings that ensure a consistent look and feel; they play no part in deciding which data a user sees or what they can do with it.
## Read more
- [Introduction to components](user/components/introduction.md)
- [Grids](user/features/grid.md)
- [Guide](user/guides/create-profile-layouts.md)
[^3]: A user can also be a fictional entity to represent a piece of software that interacts with Dime.Scheduler programmatically.
---
## Profiles
## What is a profile
A profile is a snapshot of the planning view's structure. It captures all the settings needed to recreate the user interface and apply the right behavior when the planning view starts.
You build a profile on an almost blank canvas. A few parts are fixed and cannot be removed: the top bar menu and the navigation bar on the left, shown in the image below. The navigation bar can be expanded and collapsed, but neither can be taken away.

When a user has no default profile, Dime.Scheduler shows a standard planning canvas [^1]. Beyond that, almost everything in the planning view is configurable. You can create as many profiles as you need and share them with other users and user groups [^2].
A profile stores almost everything about the planning view's layout and behavior, much like taking a picture of your view and putting it aside to rebuild later, minus the data.
Stored settings include, but are not limited to:
- Color
- Theme
- Sidebar menu collapsed state
- Dock panels and their position, width, height and the default layout of the component that is contained inside
- Planning board presets
- Map presets
- ...and many more
Profiles are most useful in the planning view. Administration views such as user or resource setup need far less personalization, so profiles do less for you there.
### Settings
The cogwheel on the right hand side of the planning view reveals a panel of buttons and switches. Some control the planning boards on the profile, others control the maps.
Note the plural: planning board**s** and map**s**. No matter how many maps and planning boards a profile contains, all of them follow the profile settings when the profile is first loaded.

For the planning board, these are the settings:
| Configuration | Description |
| ---------------- | ----------------------------------------------------------------------------------------- |
| Start date | Sets the (initial) start date of the planning board(s) |
| Range | Sets the (initial) range of the planning board(s) |
| Snap interval | Sets the (initial) time unit to snap to when dragging and dropping tasks and appointments |
| Start & end time | Sets the (initial) time range to show in the cells of the planning board |
These are the items for the map:
| Configuration | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------- |
| Unit of distance | Sets the unit of distance (km/mile) of the map and route calculation |
| Route profile | Sets the route profile (e.g. truck, fast car) to calculate the route and its duration |
| Show route, distance and travel time | Sets the flag to automatically calculate routes when selecting tasks and appointments |
| Show waypoint sequence | Sets the flag to display the sequence number of the pins in the route |
Besides these functional settings, there are two visual settings: **color** and **theme**.
Click the brush icon to slide in a window with options to style the application to your taste.
| Colors | |
| --------- | --------------------------------------------------- |
| Black |  |
| Sea green |  |
| Red |  |
| Green |  |
| Orange |  |
| Purple |  |
| Blue |  |
| Themes | |
| ------------------------------------------------------- | ------------------------------------------------ |
| Side bar with dark color, top bar with lighter color |  |
| Top bar with dark color, side bar with lighter color |  |
| Top bar with dark color, side bar with dark color |  |
| Top bar with lighter color, side bar with lighter color |  |
All these settings override the _default_ or initial behavior of the components in the profile.
Take the start date setting as an example. This combobox has four options: today, start of the week, start of the month, custom. When you pick a value, every planning board's start date changes to match. As you keep working with the planning board afterward, the profile's setting no longer applies until you change it or reload the profile.
Profile settings define how the application behaves at startup. When a profile loads, its settings apply immediately. Users can still change these values through the settings panel or directly in a component _during the session_.
### Components
Dime.Scheduler provides a set of building blocks, called **components**, that you arrange as you see fit. The components expose the planning capabilities: they are what lets you schedule tasks and resources graphically.
The components are:
- **Open Tasks**: shows tasks that need to be planned.
- **Planned Tasks**: shows planned tasks in a grid for easier querying.
- **Task Details**: shows detailed information about a selected task (open, planned or project) or appointment.
- **Gantt Chart**: allows to plan projects using a Gantt chart including dependencies, constraints and multiple planning modes.
- **Map** [^4]: shows the map, location of addresses, position of resources and assets, calculated route, travel time and travel distance.
- **Resource Filters**: allows to filter resources on the planning boards.
- **Calendar**: you use the calendar to select a custom date range (other than the presets you find on the planning board) for the planning boards.
- **Category**: shows a legend of the categories, which color means what?
- **Notifications**: shows notifications about a selected task or appointment.
- **Route Sequence**: visualize and change the sequence of a selected route on the map.
- **Capacity**: reporting feature on the resource capacity
No component is mandatory, and you can use each one multiple times within a single profile.
The components are covered in depth elsewhere in this user manual. The [introduction to components](user/components/introduction.md) is a good place to start.
### Dock panels
Every component sits inside a dock panel. Dock panels are part of the visualization engine that lets you build tailored profiles. With this framework you can add, remove and edit components and organize them to match the way you work. To learn what you can do with dock panels, read [Organizing the workspace](user/profiles-dockpanels.md).
## Profile management
If you log in for the first time and the administrator hasn't created a workspace for you yet, you can create one yourself. By default the application window looks something like this:

For more on configuring this window, see [Look and feel](user/look-feel.mdx).
Personalization is central to Dime.Scheduler. You can choose a theme and language, and reshape the entire layout of the application window to fit your needs. Every choice is stored in a **user profile**. You can create as many profiles as you want and switch between them at any time. For example, you might build one profile for planning resources in the EMEA region and another for planning tasks in Western USA, then switch between them in a few clicks. All of this requires a user profile to work with.
**_Note_**: you may not be able to manage user profiles if your administrator hasn't assigned you this user action. Ask the administrator to grant you a user role that includes the '**_Personalization_**' user action.
### Consulting the active profile
Default profiles and layouts are worth understanding from the start. When you open the application for the very first time, assuming the administrator created only your user account and nothing else, you will see an orange warning triangle in the top left corner of the top menu bar: .
Hover over it and a tooltip tells you that you haven't selected a **_(default) profile_**.
Expand the navigation menu on the left hand side to see the profile you are currently working on:
While you work on a blank user profile, the application will not let you store component layouts:
### Saving a profile
To store profile settings and component layouts, you first need to save them to a user profile. Use one of these paths:
`Profile -> Save`
...or
`Profile -> Save as`

If you don't have a default profile, either option does the same thing. Once there is an active profile in your session, **Save** overwrites the active profile while **Save as** creates a brand new profile.
A window prompts you for:
- The **name** of the profile
- Whether to mark it as your personal **default** profile
- Whether to **share** it with a user group or with everyone in your organization

Click save and a message confirms the profile was saved. If you marked it as your default profile and refresh the application, this profile loads by default in the areas described in the [active profile section](#consulting-the-active-profile).
To see this in action, change the theme or color and save the profile. No window appears this time, because the application already knows which profile you are working on. Change the theme or color again, but this time choose **Save as**. The same window appears as before. Give it a name, save it, and reload the application.
### Loading profiles
After you reload the application, your default profile loads into your session. You can switch between profiles at any time during a session using this path:
`Profile -> Load`
A window like the following appears:

The user profiles available to you are:
- The profiles you created
- The public profiles
- The profiles that were shared with the user group you are a member of
The grid shows each profile's name, who created or owns it, and whether you marked it as your default.
By now you have at least two profiles in this grid. There are two ways to load one:
- Click the row to select a profile, then click the select button
- Double click the row of the profile you want to load
Load the profile that is **_not_** your default profile.
The application loads the profile along with all its settings. After a few seconds you will see a different profile in your session. Confirm it by consulting the [active profile](#consulting-the-active-profile).
### Customize existing profile
The profile currently loaded into your session is the **active profile**. Your default profile becomes the active profile every time you (re)load Dime.Scheduler; it simply tells the application which profile to start with.
If you followed the steps in the previous section, your active profile is now different from your default profile.
You can update this profile freely. Try it by removing the map and resources component, as shown here:

The map and resources stack disappear from the planning window.
Save the profile and reload the application: the default profile is untouched, but when you reload the second profile, the map and resource filters components are gone, exactly as you set them.
You can do the same with profiles you don't own, with one limit: you cannot overwrite a profile that belongs to someone else. Instead, save a copy of it. The same settings are saved along with your changes, and you become the owner.
### Managing profiles
You can manage your profiles by changing the default and deleting profiles you own. Open this window with:
`Profile -> Manage`
A window like the following appears:

This window is much like the load profile window, except that here you can change the default profile and delete the profiles you own.
#### Change default profile
Change your default profile by ticking the checkbox. A small red triangle marks this pending action:

When you are sure, click OK to save the change.
#### Delete profiles
You can delete profiles you own. Click the delete icon in the row to remove a profile.
As above, you need to confirm with the OK button before the application deletes anything.
## Read more
- [Profiles and layouts](user/profiles.md)
- [Organizing the workspace](user/profiles-dockpanels.md)
- [Guide](user/guides/create-profile-layouts.md)
[^1]: Users are notified of this event with an orange warning icon at the top bar.
[^2]: Given the right permissions that is. See [layout authorization](/user/profiles-layouts#authorization) to read more.
[^4]: When activated and/or included in the license.
---
## Appointment subject & body
The text shown in an appointment's **subject** and **body** on the planning board is built from a template, not pushed verbatim from the back-office. That lets you decide once which fields - job number, customer, address, task description, etc. - get composed into the text every appointment carries, without changing anything upstream.
Templates are built by dragging fields, free text, and line breaks into a layout. The resulting template is applied to every new appointment that arrives from the back-office.
:::info
Changes apply to **new** appointments only. Existing appointments keep the content they were created with.
:::
## Configure it
You set up these templates in the back-office area. See [Field templates](administration/settings/fieldtemplates.md) for the full walkthrough.
---
## Planning board tooltip(Setup)
When a user hovers an appointment on the planning board, a tooltip pops up with details about that appointment. You decide which fields show up, in what order, and under which labels.
Unlike the [appointment subject and body](user/setup/appointment-content.md), the tooltip can pull from the full appointment object graph - the task, the job, the customer, the category, the time marker - not just the flat appointment record. That makes it the right place to surface context that doesn't fit in the subject line.
The tooltip itself is opt-in: it must be enabled in [Application Setup → Planning → Visualization](/administration/setup/planning#visualization). What's configured here determines *what* is shown; the setup toggle determines *whether* it is shown.
## Configure it
The configuration is on the **Tooltip** tab of the field templates page: see [Field templates - Planning board tooltip](administration/settings/fieldtemplates.md#planning-board-tooltip).
---
## Appointment visualization
An appointment on the planning board is a small rectangle carrying a surprising amount of information. Its fill color, the bar across it, the icons on top of it, and the marks at either end are each driven by a different property, and each is configured somewhere else in the application.
This page is the map: what the planner sees, what drives it, and where you go to change it.
## The visual language of an appointment
| What the planner sees | What drives it | Where you set it up |
| ------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------- |
| The **fill color** of the bar | The category | [Indicators → Categories](/administration/settings/indicators#categories) |
| A **colored horizontal bar** on the appointment | The time marker | [Indicators → Time markers](/administration/settings/indicators#time-markers) |
| **Small icons** along the top | Tags | [Indicators → Tags](/administration/settings/indicators#tags) |
| An **exclamation mark or arrow** at the start | Importance | Driven by the back office; the scale is fixed |
| A **colored vertical bar** | Notifications, colored by severity | Driven by the back office |
| The **text** in the bar | The subject and body template | [Appointment subject & body](/user/setup/appointment-content) |
| The **panel that pops up on hover** | The tooltip field template | [Tooltip](/user/setup/tooltip) |
| The **color of a marker on the map** | The pin on the task | [Indicators → Pins](/administration/settings/indicators#pins) |
Only the first three and the last are things you choose a color for. The rest are either fixed scales or text templates.
For what each mark looks like in practice, with screenshots, see [Visual indicators](/user/components/planningboard/appointments#visual-indicators).
## Choosing colors that survive a real board
Most back-office systems seed the indicators themselves but not their colors, because a color is a visualisation choice rather than master data. So you usually set the colors once inside Dime.Scheduler, after the indicators arrive. New rows start out on a default blue, which means a catalog that was imported and never styled looks uniformly blue until someone goes through it.
A few things are worth getting right the first time, because they are hard to unpick once planners have learned them.
**Start with the category palette.** The category fills the bar, so it is by far the largest area of color on the board and it dominates the visual read. Every other indicator has to remain legible on top of it. Saturated dark fills look decisive in the color picker and turn the appointment into an unreadable block on a real board, where the subject text and the tag icons both have to show through.
**Keep the accents distinguishable from the fills.** The time marker bar and the notification bar are thin, so a color that reads clearly as a large fill can disappear entirely as a 3-pixel line. Reserve a few high-contrast colors for the accents rather than drawing both from the same palette.
**Let tags differ by shape, not just hue.** Tags render as icons, several of them side by side, which is precisely what lets an appointment carry four of them without turning into mush. Choose icons with different silhouettes. Two tags that differ only in color are two tags a planner will misread on a busy day.
**Do not encode the same thing twice.** It is tempting to make "urgent" a red category *and* a red tag *and* a high importance. Each indicator should answer a different question, otherwise you have spent three visual channels on one fact and have none left for the next one.
## Configure it
Colors, icons, and the indicator catalog all live on one page: see [Indicators](/administration/settings/indicators) for the full walkthrough.
## Read more
- [Visual indicators on the planning board](user/components/planningboard/appointments.md#visual-indicators)
- [Indicators setup](administration/settings/indicators.md)
- [Tags](user/features/tags.md)
- [Indicators panel](user/components/indicators.md)
---
## Export to PDF and Excel
When you need a static copy of the planning - to print it, attach it to an email, or hand it to someone without app access - export it to a file. Dime.Scheduler supports two formats: **PDF** for the visual planning surfaces (planning board and Gantt chart) and **Excel** for the tabular grids.
## Export to PDF
The [planning board](/user/components/planningboard/toolbars#export) and the [Gantt chart](/user/components/gantt#export) both export to PDF. Click the PDF export button and Dime.Scheduler takes a snapshot of the component and renders it as a PDF.
A settings window lets you tweak the output:

- **Schedule range**: the complete schedule, a date range, or just the visible schedule.
- **Row range**: everything, or just the visible range.
- **Resource fields**: add or remove the resource fields shown in the export.
- **Print settings**: page format, orientation, pagination control, and whether to add a header to the page.
The export runs on an export server. Once the document is ready, a modal window prompts you to save the file. The output closely matches what you see on screen.

Because the export is tightly integrated with the component it captures, we recommend creating a dedicated planning board or Gantt chart layout optimized for printing.
## Export to Excel
The [grids](/user/features/grid#export) export to Excel. Click the icon and the file downloads right away. The visible columns and their order are carried over to the file, so the export reflects exactly what you have on screen.
The file name follows the format `Dime.Scheduler [Grid type] [User Name] [Date].xlsx`.

---
## Share planning over MCP
The Model Context Protocol (MCP) is an open standard that lets AI assistants and agents talk to external systems in a structured way. Dime.Scheduler ships an MCP server, so you can share the planning with tools like Claude, Copilot, and other MCP-aware assistants - and let them read and act on the schedule on your behalf.
Instead of exporting a file or opening the app, you ask the assistant. It queries the planning through the MCP server and answers in natural language, or carries out scheduling actions for you.
## What you can do with it
Once an assistant is connected to the Dime.Scheduler MCP server, it can work with the planning directly - for example:
- Look up the schedule for a resource, job, or date range.
- Create and update tasks and appointments.
- Run optimization and send messages.
The exact capabilities depend on the tools the MCP server exposes.
## Set it up
The MCP server is configured and connected in the integrations section. For prerequisites, configuration, supported clients, and the full list of tools, see the [MCP integration guide](/integrations/mcp/intro).
---
## Other ways to share
Beyond the agenda, planning digest, exports, and [MCP](/user/sharing/mcp), the planning can reach people and systems through several other channels. Each lives in its own part of the documentation; this page is a jumping-off point.
## For people
- **[Outlook sync](/integrations/exchange/intro)** - the Exchange connector keeps appointments in your resources' Outlook calendars in sync with their planning, in real time and in both directions.
- **[Mobile app](/user/mobile)** - the iOS and Android app that lets resources on the road consult and update their own schedule.
## For other systems
- **[API](/develop/api)** - read and write planning data directly from your own applications and integrations.
- **[Power Platform connector](/integrations/powerplatform)** - surface and act on the planning from Power Automate, Power Apps, and the wider Microsoft Power Platform.
- **[Business Central](/integrations/bc/intro)** - two-way integration that keeps the planning in step with Microsoft Dynamics 365 Business Central, so the schedule is visible and actionable inside your ERP.
---
## Planning digest(Sharing)
:::info[Status: Preview]
The planning digest is currently in preview. Functionality and behavior may change between releases. Not recommended for production-critical workflows yet.
:::
The planning digest emails a PDF of the schedule for a selected group of resources on a recurring basis. It is the hands-off way to keep customers and team members informed: instead of asking people to open the app or check the [agenda](/user/agenda), the schedule lands in their inbox on a fixed cadence.
## What recipients get
Each digest is a PDF snapshot of the planning for the configured resources and date range, delivered by email. Recipients do not need a Dime.Scheduler account or a license - they only need to receive the email, which makes the digest a good fit for sharing schedules with customers and external stakeholders.
## Configuration
A planning digest is configured by an administrator. The available settings are:
- **Frequency**: how often emails are sent - daily, weekly, or monthly.
- **Date range**: the range of planning included in the PDF.
- **Recipients**: who receives the email.
- **Resources**: which resources are included in the PDF.
The system generates and sends the PDF automatically according to the schedule.
For the administrative setup, see [Planning digest](/administration/settings/planning-digest) in the administration guide.
---
## Troubleshooting(Troubleshooting)
import AreaTroubleshootingLanding from '@site/src/components/AreaTroubleshootingLanding';
Search for what you're seeing, or browse the reference below. Each symptom links straight to the page that explains the cause and the fix.
## Reference
The sections below are the same answers, listed in full. Use them when you'd rather skim than search.
### Authentication
#### How can I reset my password?
Follow the [password reset guide](user/guides/reset-password.md). It applies to Forms users only. For Windows and Microsoft Entra ID accounts, contact your network administrator instead.
### Map
#### My job or resource doesn't show on the map
If a record is missing from the [map component](user/components/map.md), it almost always means Dime.Scheduler could not turn its address into coordinates. The map silently skips anything without a latitude and longitude.
The usual suspects, in order:
1. **The address is too vague to resolve.** "Main Street" with no city, "HQ" as the entire address, or a single building name in a country that has hundreds of them gives the geocoder nothing to lock onto. Add the street number, zip code, city, and a country code.
2. **The address sits in the wrong column.** The structured columns (street, street number, zip, city, country) are much easier to resolve than the free-text full-address field. If the back-office system dumps everything into one line, fix it upstream where possible.
3. **The country is in a localized name, not an ISO code.** "Belgique" or "Belgien" instead of "BE" trips up the geocoder. Ask your administrator to add the missing entry in the [localization view](administration/settings/localization.md).
4. **The address is on the blacklist.** Dime.Scheduler remembers addresses it has already failed to resolve and skips them on subsequent runs. If you have since corrected the data, ask your administrator to look at the [geocoding documentation](administration/settings/geocoding.md#the-blacklist) and, if needed, contact support to clear the stale entry.
To force a fresh attempt for a single record, an administrator can use the per-row geocode button on the [jobs](administration/settings/jobs.md) or [resources](administration/settings/resources.md) page. After it succeeds, refresh the planning view.
### Browser
#### Share your location with Dime.Scheduler
By default, Dime.Scheduler centers the map on the company's address, which you set in the application setup. When no address is available, it falls back to the current user's location. Both behaviors are configurable.
##### Chrome
- Open the browser
- At the top right, click More and then Settings.
- Under "Privacy and security," click Site settings.
- Click Location.
- Turn Ask before accessing on or off.
##### Edge
- Open the browser
- At the top right, click More and then Settings.
- Under "Site Permissions," click Location.
- Turn Ask before accessing on or off.
##### Firefox
- Click the menu button Menu and select Options.
- Use the Options search box to search for "location".
- In the search results under Permissions, click the Settings button to the right of Location
- The Settings - Location Permissions dialog box will open.
- Review or change the list of sites with allowed or denied location permission.
#### How do I get Chrome to prompt "Always open these types of links" when opening NAV/BC URIs?
This is a [known issue](https://bugs.chromium.org/p/chromium/issues/detail?id=982341) in Chromium, the open-source project behind Google Chrome and others. Until it is fixed, use this workaround:
- Close all Chrome browsers
- Edit the following file: `C:\Users\\AppData\Local\Google\Chrome\User Data\Default\Preferences`
- Insert the following snippet at the bottom, just inside the last closing bracket: `,"protocol_handler":{"excluded_schemes":{"dynamicsnav":false}}`
#### Opening file URLs in Chromium browsers
When you open a URL like `file://\server\directory\file.docx` in Google Chrome, the console logs this error:
> Not allowed to load local resource file://server/directory/file.docx
This is a security feature that prevents navigation across security boundaries. Chrome extensions such as [Local Explorer](https://chrome.google.com/webstore/detail/local-explorer-file-manag/eokekhgpaakbkfkmjjcbffibkencdfkl) can work around it. Download and use them at your own risk.