75 lines
8.6 KiB
Markdown
75 lines
8.6 KiB
Markdown
# Ticket Scheduling Runbook (Schedule Entries)
|
||
|
||
End-to-end flow for "schedule my tickets on the calendar" requests, validated end-to-end on the VistaNet instance. All timestamps UTC.
|
||
|
||
## 1. Resolve the member
|
||
`/system/myMember` 404s for API members. Use `GET /system/members?conditions=identifier='SBrown'` (URL-encode quotes) → id. Reuse the id for every `owner/id` and `member/id` (SBrown = 147).
|
||
|
||
## 2. Pull open tickets
|
||
`GET /service/tickets?conditions=owner/id=<memberId> AND closedFlag=false&pageSize=1000`; filter client-side for truly-open (status starting `>`, name `Closed`, or `dateResolved` present ⇒ exclude). Handle both list and `{"tickets": [...]}` shapes.
|
||
|
||
## 3. Check SLA reality before doing SLA math
|
||
`GET /service/SLAs/{id}` tells you the plan exists. If `/service/SLAs/{id}/plans` returns EMPTY and `/service/slaPlans` returns 404, the SLA carries NO hour plans — Standard SLA (id 2) here has none, so every Priority 3–Medium ticket is SLA-identical and SLA contributes nothing to urgency. Do not invent response-time tiers from the SLA name. `/service/boards/{id}/slaVersions` and its `/detail` subpath also 404 on this instance — treat both as dead ends, not as a reason to stop ranking.
|
||
|
||
## 4. Rank by what changes a deadline
|
||
When priorities are uniform (all P3-Medium), rank from signals in the ticket data itself, in order:
|
||
1. Hard expiration dates in the summary (cert renewals: "Expiring on YYYY-MM-DD") — expired or expiring this week first.
|
||
2. Active security incidents and outages (summary words: intrusion, confirmed, outage, unreachable, failing) with status In Progress.
|
||
3. Client-flagged urgency ("Urgent per <name>").
|
||
4. Security hygiene (patching, key setup, directory sync, mail filtering).
|
||
5. User-facing breakage (Tier 3 Assigned).
|
||
6. Small tasks (new user setup) swept into slack blocks.
|
||
|
||
Do NOT cram project/coordination tickets (status `Must Schedule`) into gaps — they need client scheduling in long blocks; propose them for next week instead.
|
||
|
||
Unschedulable (list, don't book): On Hold, Waiting Client Response, Waiting parts/repair.
|
||
|
||
Status nuance: ticket status `Scheduled` only means SOME entry exists — often an all-day 0.0h placeholder months out. Check the entry's date against the summary expiry before trusting it.
|
||
|
||
## 5. Check existing commitments first
|
||
Fetch the week UNFILTERED — `dateStart > [Monday-local ISO] AND dateStart < [NextMonday-local ISO]`, pageSize 500 — and filter by `member.id`/`member.identifier` client-side. Member-scoped conditions on this endpoint intermittently drop or mutate entries. Retry until two consecutive fetches agree (a stability gate), never gate on an absolute entry count — entries legitimately vanish mid-session when other people edit the calendar. Compose around firm entries; anchor back-to-back rather than overlapping (overlaps ⇒ `400 InvalidObject` SystemConflict on dateStart). Note objectId-0 blocks (internal placeholders with no ticket behind them) and re-check commitments immediately before POSTing — the calendar can move between your fetch and your write.
|
||
|
||
## 6. Slot composition and dispositions
|
||
- Local (PDT = UTC-7) to UTC conversion: +7h (2:30p PDT → 21:30Z).
|
||
- Same-day urgent work goes TODAY in the first free block — an expiring cert waits for no one; start from the next half hour.
|
||
- Batch small tasks (≤1h each) contiguously on the lightest afternoon.
|
||
- Tickets whose start is gated externally (client confirmation pending, vendor scheduling unknown, still in evaluation) get NO entry — list them as dispositions with the blocking reason so nothing silently drops. Book when the gate clears. An ALREADY-BOOKED ticket whose gate moves later keeps its entry: reschedule via PATCH (§9), never delete+repost. A scheduling gate is recorded as an INTERNAL TICKET NOTE (§9), never in the entry `name` — a ticket-linked entry's display name auto-regenerates from `Company/Summary` and silently discards custom label text.
|
||
- Recurring unavailability: team/tech meeting every Friday 8:00–9:30 AM PDT — never schedule SBrown in that window, and never let a Friday block start before 9:30.
|
||
- After-hours work (evening server/RDS upgrades needing users logged off) gets a full evening block (e.g. 6–9p), not an afternoon gap fill, and needs a coordination runway — book the earliest evening the client could plausibly agree to, not tonight.
|
||
- Booking requests are literal id lists: book exactly the named tickets; a duplicated id means one booking plus a note, not two entries.
|
||
|
||
## 7. POST each entry
|
||
`POST /schedule/entries` with a plain JSON object (NOT JSON Patch):
|
||
```json
|
||
{
|
||
"name": "Company / Ticket# 12345 - short label",
|
||
"member": {"id": 147},
|
||
"type": {"id": 4},
|
||
"span": {"id": 1},
|
||
"status": {"id": 2},
|
||
"objectId": 12345,
|
||
"ticketType": "S",
|
||
"where": {"id": 4},
|
||
"dateStart": "2026-10-06T16:00:00Z",
|
||
"dateEnd": "2026-10-06T18:00:00Z",
|
||
"hours": 2.0,
|
||
"doneFlag": false,
|
||
"meetingFlag": false
|
||
}
|
||
```
|
||
Observed ids: span 1 = `N`, status 2 = `Firm`, type 4 = `S`, where 4 = `Remote`. `name` is the free-form calendar label.
|
||
|
||
## 8. Verify, then audit coverage
|
||
Re-GET the unfiltered week window and confirm each new entry by objectId AND intended start time; a 200 on the POST is not proof the calendar shows it. If a freshly posted entry is missing from the refetch, direct-GET the entry id: 404 ⇒ deleted/rolled back; present ⇒ transient list unreliability. Then audit the whole picture in the same pass: every open ticket with a summary expiration date vs its calendar coverage, every Must Schedule ticket vs entries, my timed entries pair-wise for overlaps, and next week's window for duplicate bookings of the same ticket. A vanished block usually means the ticket closed (`>Completed`), was reassigned (owner changed), or a duplicate was cleaned up — check ticket status/owner before treating absence as a scheduling failure.
|
||
|
||
Present results with local times, then a disposition list — booked / blocked-status (Waiting Client, On Hold, Waiting parts) / pending-external-gate / intentionally unbooked — plus flags: expired-but-waiting-on-client warnings, objectId-0 blocks with no ticket, and Must Schedule candidates for next week.
|
||
|
||
## 9. Modifying existing entries and recording gates
|
||
- **PATCH `/schedule/entries/{id}` requires a JSON Patch ARRAY** (same as tickets): `[{'op':'replace','path':'/dateStart','value':'...Z'},{'op':'replace','path':'/dateEnd','value':'...Z'},{'op':'replace','path':'/hours','value':3.0}]`. A plain JSON object 400s. A 200 does NOT prove every field took — re-GET and check each patched field.
|
||
- **Do not patch `/name` on a ticket-linked entry**: the write is accepted but the display name regenerates from `Company/Summary`, so any PENDING/DO-NOT-START marker in the label silently disappears. Skip it up front.
|
||
- **Scheduling gates live as internal ticket notes**: `POST /service/tickets/{id}/notes` with a PLAIN JSON object (notes are not JSON Patch): `{'text': '...', 'internalAnalysisFlag': true, 'processNotifications': false}`. State the window, the entry id, who must confirm, and DO-NOT-START until then. `customerFlag` is not a valid ServiceNote member on this version (400 if included).
|
||
- **Owner reassignment** (ticket leaves your queue): `[{'op':'replace','path':'/owner/id','value':<memberId>}]` on `/service/tickets/{id}`, then fresh-GET to verify owner identifier. Resolve the new member's id first with `GET /system/members?conditions=identifier='X'`. Remove the ticket from your scheduling plans the same turn — don't book/keep entries for tickets you no longer own.
|
||
- **Ownership hygiene check (standing rule)**: for a tech's open tickets whose most recent note is from another tech (a likely transfer), vet before PATCH: automation members (the API/service-account members) NEVER receive ownership — their notes are integration/alert noise; time entries count as "me being there" (older intake note beats nothing, loses to a newer TE by the current owner); read the note's intent — intake/directive notes ("please order and drop ship", "get you guys involved") keep the ticket with the current owner; when the current owner has NO note and NO TE and the other tech's note is newer, that's a legitimate transfer. Verify each PATCH with a fresh GET of owner.identifier; drop transferred tickets from any staged plan same turn.
|
||
|
||
## Next-week candidate review
|
||
When asked what could be scheduled next week: fetch next + far-week windows (stability-gated), subtract every open ticket that has a booking in them, list the remainder grouped as schedulable (with concrete proposed slots from actual gaps), conditional-on-gate, and parked — naming the gate per ticket. Next-week Mon–Wed is typically wide open; propose day+time anchors, not vague availability. |