Advanced guides
Demands and shifts
Call-centers and retailers typically does not have fixed shifts. They have demands such as
- (Some)one on duty between 8 AM to 8 PM
- (Some)one on duty between 9 AM and 11 AM
This demand could be covered by two employees or by three employees. In the latter case, two employees would share the first demand by covering two 6-hour shifts.
Our API lets you schedule in a demand-based way easily.
- Turn your demand into continuous
shifts
of one hour. - Use the same
spot
for every one of these shifts and include that spot inallowedCollisionSpots
, see below. - Change the
settings
to allow multiple shifts a day, see below.
It is important that you base your requests of the settings below to avoid scenarios where an employee work for just one hour one a given day!
We use shifts
in this article differently than we have done in the rest of this documentation. The default settings are great for default use-cases but if you do demand-based scheduling you have to change the default settings.
Breaking demands into shifts
Here is an example in Kotlin of how you could turn a daily demand one person being present from 8 AM to 6 PM each day. If you are unsure about the fields here, consult the documentation for shifts.
This code creates a week-long schedule of hourly shifts for two alternating spots with shifts running from 8 AM to 6 PM each day. The main logic is in a nested loop structure.
The outer loop iterates over 7 days (1 until 8).
- For each day, it creates 6 spots.
- For each spot, it creates shifts for every hour from 8 AM to 6 PM (10 hours).
val tz = TimeZone.of("Europe/Copenhagen")
val startDate = LocalDateTime.parse("2024-07-28T08:00:00")
val shifts = (1 until 8).flatMap { day ->
val currentDate = startDate.toInstant(tz).plus(day, DateTimeUnit.DAY, tz)
(1..6).flatMap { spotNumber ->
(8 until 18).map { hour -> // Range from 8 AM to 6 PM (which is maxHoursDay)
val start = currentDate.plus(hour - 8, DateTimeUnit.HOUR, tz)
val end = currentDate.plus(hour - 7, DateTimeUnit.HOUR, tz) // Each shift lasts 1 hour
Shift(
id = (day * 1000 + hour * 10 + spotNumber),
spot = (spotNumber % 2) + 1,
start = start,
end = end,
importance = StaffingMode.MANDATORY,
allowedCollisionSpots = listOf((spotNumber % 2) + 1),
fixed = false,
historic = false,
employee = null,
)
}
}
}
Changing the settings
Changing the settings like so would be a good starting point. To get help, you can always contact us at [email protected] or revisit the section on Settings.
@Serializable
data class Setting(
var firstPriorityWeight: Int,
var secondPriorityWeight: Int,
var thirdPriorityWeight: Int,
var fourthPriorityWeight: Int
)
payload = (
employees = listOf(), // Add employees here
shifts = shifts, // As defined above
spots = listOf(), // Make sure to include spot id=1 and id=2
numberOfAllowedShiftCollisions = 1000,
settings = mapOf(
"distributeShiftsEvenly" to Setting(0, 0, 0, 10),
"minimizeEmployeeUsage" to Setting(0, 0, 100, 0),
"employeeHourlyPatternCompactness" to Setting(0, 20, 0, 0),
"atLeastTwoContinousDaysOffAWeek" to Setting(0, 0, 0, 0),
"maxConsecutiveLateShifts" to Setting(0,0,0,0),
"maxConsecutiveNightShifts" to Setting(0,0,0,0),
"maxConsecutiveWorkingDays" to Setting(0,0,0,0),
"minConsecutiveWorkingDays" to Setting(0,0,0,0),
"noConsecutiveFiveWorkdayWeeks" to Setting(0,0,0,0),
"sameTimeOfDayForConsecutiveShifts" to Setting(0,0,0,0),
"singleShiftPerDay" to Setting(0,0,0,0),
"employeeShiftCompactness" to Setting(0,0,0,0),
)
)
A full example
Here is a full example including Kotlin data classes
@Serializable
data class ConsumerRequestPayload(
val employees: List<ConsumerEmployee>,
val shifts: List<ConsumerShiftRequest>,
val spots: List<ConsumerSpot>,
val numberOfAllowedShiftCollisions: Int,
val settings: Map<String, Setting>,
val callbackUrl: String,
val callbackAuthenticationHeaderName: String,
val callbackAuthenticationHeaderValue: String
)
@Serializable
data class Setting(
var firstPriorityWeight: Int,
var secondPriorityWeight: Int,
var thirdPriorityWeight: Int,
var fourthPriorityWeight: Int
)
@Serializable
data class Spot(
val id: Int,
val requiresRestAfterShiftHere: Boolean = false,
val singleEmployeePerWeekPreferred: Boolean = false,
val teams: List<Int>,
val skills: List<Int>,
val foreignInt: Int? = null
)
@Serializable
data class Shift(
val id: Int,
val spot: Int,
@Serializable(with = InstantIso8601Serializer::class)
val start: Instant,
@Serializable(with = InstantIso8601Serializer::class)
val end: Instant,
val importance: StaffingMode,
val suggestedEmployees: List<Int> = listOf(),
val allowedCollisionSpots: List<Int> = listOf(),
val fixed: Boolean,
val historic: Boolean,
var employee: Int?,
val foreignInt: Int? = null
)
@Serializable
data class Employee(
val id: Int,
val teams: List<Team>,
val skills: List<Skill>,
val contract: Contract,
val wishes: List<Wish>
)
@Serializable
data class Team(
val id: Int,
@Serializable(with = InstantIso8601Serializer::class)
val start: Instant? = null,
@Serializable(with = InstantIso8601Serializer::class)
val end: Instant? = null,
)
@Serializable
data class Skill(
val id: Int,
@Serializable(with = InstantIso8601Serializer::class)
val start: Instant? = null,
@Serializable(with = InstantIso8601Serializer::class)
val end: Instant? = null,
)
@Serializable
data class Contract(
val hoursBetweenShift: Int,
val maxHoursDay: Int,
val maxHoursWeek: Int,
val maxHoursMonth: Int,
val maxWorkDaysMonth: Int,
val maxConsecutiveDays: Int,
val minConsecutiveDays: Int,
val weekendWorkFrequency: Int,
val maxNhours: Int,
val InKWeeks: Int,
val maxConsecutiveLateShifts: Int,
val maxConsecutiveNightShifts: Int
)
@Serializable
data class Wish(
val type: WishTypeEnum,
@Serializable(with = InstantIso8601Serializer::class)
val start: Instant,
@Serializable(with = InstantIso8601Serializer::class)
val end: Instant,
)
val tz = TimeZone.of("Europe/Copenhagen")
val startDate = LocalDateTime.parse("2024-07-28T08:00:00")
val defaultContract = Contract(
hoursBetweenShift = 11,
maxHoursDay = 10,
maxHoursWeek = 40,
maxHoursMonth = 40 * 4,
maxWorkDaysMonth = 30,
maxConsecutiveDays = 30,
minConsecutiveDays = 1,
weekendWorkFrequency = 1,
maxNhours = 40,
InKWeeks = 1,
maxConsecutiveLateShifts = 100,
maxConsecutiveNightShifts = 100,
)
private val payload = Payload(
employees = (1 until 10).map { index ->
Employee(
id = index,
teams = listOf(Team((index % 2) + 1)),
skills = listOf(Skill((index % 2) + 1)),
contract = defaultContract,
wishes = listOf(
Wish(
// Every one has a full day in week 1
type = "UNAVAILABLE",
start = startDate.toInstant(tz).minus(8, DateTimeUnit.HOUR, tz)
.plus(index % 5, DateTimeUnit.DAY, tz),
end = startDate.toInstant(tz).plus(16, DateTimeUnit.HOUR, tz)
.plus(index % 5, DateTimeUnit.DAY, tz),
)
),
)
},
spots = listOf(
Spot(id = 1, singleEmployeePerWeekPreferred = true, teams = listOf(1), skills = listOf(1)),
Spot(id = 2, singleEmployeePerWeekPreferred = true, teams = listOf(2), skills = listOf(2)),
),
shifts = (1 until 8).flatMap { day ->
val currentDate = startDate.toInstant(tz).plus(day, DateTimeUnit.DAY, tz)
(1..6).flatMap { spotNumber ->
(8 until 18).map { hour -> // Range from 8 AM to 6 PM (which is maxHoursDay)
val start = currentDate.plus(hour - 8, DateTimeUnit.HOUR, tz)
val end = currentDate.plus(hour - 7, DateTimeUnit.HOUR, tz) // Each shift lasts 1 hour
Shift(
id = (day * 1000 + hour * 10 + spotNumber),
spot = (spotNumber % 2) + 1,
start = start,
end = end,
importance = StaffingMode.MANDATORY,
allowedCollisionSpots = listOf((spotNumber % 2) + 1),
fixed = false,
historic = false,
employee = null,
)
}
}
},
numberOfAllowedShiftCollisions = 1000,
settings = mapOf(
"distributeShiftsEvenly" to Setting(0, 0, 0, 10),
"minimizeEmployeeUsage" to Setting(0, 0, 100, 0),
"employeeHourlyPatternCompactness" to Setting(0, 20, 0, 0),
"atLeastTwoContinousDaysOffAWeek" to Setting(0, 0, 0, 0),
"maxConsecutiveLateShifts" to Setting(0,0,0,0),
"maxConsecutiveNightShifts" to Setting(0,0,0,0),
"maxConsecutiveWorkingDays" to Setting(0,0,0,0),
"minConsecutiveWorkingDays" to Setting(0,0,0,0),
"noConsecutiveFiveWorkdayWeeks" to Setting(0,0,0,0),
"sameTimeOfDayForConsecutiveShifts" to Setting(0,0,0,0),
"singleShiftPerDay" to Setting(0,0,0,0),
"employeeShiftCompactness" to Setting(0,0,0,0),
)
callbackUrl = "https://www.yourdomain.app/custom_callback_url",
callbackAuthenticationHeaderName = "X-Api-Key",
callbackAuthenticationHeaderValue = "SECRET"
)