feat: reassign on-call days and weeks to another person
Release / test (push) Failing after 4s
Release / build (amd64, darwin) (push) Has been skipped
Release / build (amd64, linux) (push) Has been skipped
Release / build (arm64, darwin) (push) Has been skipped
Release / build (arm64, linux) (push) Has been skipped
Release / release (push) Has been skipped

Assigning over a day somebody else held did nothing but flash a 409 for
three seconds. The server holds one person per date and refused any that
was taken, all-or-nothing, so pressing W on a week where a single day was
already assigned placed none of the other six either. The only way
through was d on each day first — seven delete-and-confirm cycles to move
one week.

The clash is already on screen, so it is found before the request rather
than read back out of an error: the picker hands off to a confirmation
naming who loses the days and how many there are, and accepting sends the
whole selection with replace, which terdut-server v0.8.0 added. One
question to move a week, and nobody's shift moves without somebody being
asked. A day nobody holds still assigns with no prompt at all.

Reassigning somebody to a day they already hold raises no prompt, since
it takes nothing from anyone, but it does send replace: the server
rejects any date that exists, so without it a harmless no-op would fail.
This commit is contained in:
Niklas Ye
2026-08-07 14:04:37 +02:00
parent 85ad2d65ee
commit e336aeea97
8 changed files with 366 additions and 7 deletions
+16 -2
View File
@@ -54,6 +54,7 @@ const (
confirmResolveIncident
confirmDeleteScheduleEntry
confirmDeleteUser
confirmReassignSchedule
)
// pickerTarget says what the user picker is choosing a person for.
@@ -143,6 +144,18 @@ type scheduleDay struct {
entry *api.ScheduleEntry
}
// pendingAssign is an on-call assignment held back by the reassignment
// confirmation, because some of its dates belong to somebody else.
type pendingAssign struct {
userID int64
username string
dates []string
// taken are the dates currently held by other people, and holders the
// distinct names holding them — both only for wording the prompt.
taken []string
holders []string
}
type Model struct {
client *api.Client
serverURL string
@@ -194,6 +207,7 @@ type Model struct {
confirmTarget confirmTarget
pendingDeleteID int64 // note event ID
pendingDeleteEntry *api.ScheduleEntry
pendingAssign *pendingAssign
// Stats
topAlerts []api.TopAlert
@@ -866,9 +880,9 @@ func fetchScheduleCmd(client *api.Client, from, to time.Time) tea.Cmd {
}
}
func assignScheduleCmd(client *api.Client, userID int64, dates []string, from, to time.Time) tea.Cmd {
func assignScheduleCmd(client *api.Client, userID int64, dates []string, replace bool, from, to time.Time) tea.Cmd {
return func() tea.Msg {
if _, err := client.AssignSchedule(userID, dates); err != nil {
if _, err := client.AssignSchedule(userID, dates, replace); err != nil {
return scheduleActionErrMsg{err}
}
entries, err := client.GetSchedule(from.Format("2006-01-02"), to.Format("2006-01-02"))