ConnectWise PSA skills and tools under ops/connectwise (hub-sanitized 2026-11-01: client intel, internal workflow/credential/member/company ids, archived-page apiToken/sessionId stripped to placeholders)
This commit is contained in:
commit
9c96092034
36 files changed
+310875
No files matched your search
@@ -0,0 +1,82 @@
|
||||
# Ticket Time Entries, Notes & Status Changes — Working Payloads
|
||||
|
||||
Verified request/response shapes for documenting work on tickets via `scripts/cw_api.py`.
|
||||
|
||||
## Response shape normalization (do this FIRST in every script)
|
||||
|
||||
`cw_api.cw_request` returns `(data, http_status)` tuples, and list endpoints may wrap the payload one list deeper. Normalize before any `.get()` or loop, or scripts fail with `AttributeError: 'tuple' object has no attribute 'get'`:
|
||||
|
||||
```python
|
||||
def norm(resp):
|
||||
data = resp[0] if isinstance(resp, tuple) and len(resp) == 2 else resp
|
||||
if isinstance(data, list) and data and isinstance(data[0], list):
|
||||
data = data[0]
|
||||
return [x for x in (data if isinstance(data, list) else [data]) if isinstance(x, dict)]
|
||||
```
|
||||
|
||||
## Create a time entry on a ticket
|
||||
|
||||
`POST /time/entries`. Convert local Pacific time to UTC by adding 7 hours (1:30–3:30 PM PDT → 20:30–22:30Z):
|
||||
|
||||
```json
|
||||
{
|
||||
"company": {"id": <companyId>},
|
||||
"member": {"id": 147},
|
||||
"chargeToId": <ticketId>,
|
||||
"chargeToType": "ServiceTicket",
|
||||
"locationId": 11,
|
||||
"businessUnitId": 1,
|
||||
"workType": {"id": 3},
|
||||
"workRole": {"id": 4},
|
||||
"timeStart": "<ISO UTC>",
|
||||
"timeEnd": "<ISO UTC>",
|
||||
"actualHours": 2.0,
|
||||
"billableOption": "Billable",
|
||||
"notes": "<work note text>",
|
||||
"addToDetailDescriptionFlag": true
|
||||
}
|
||||
```
|
||||
|
||||
- `addToDetailDescriptionFlag` does NOT produce a ticket note on this instance — post the note separately if the text must appear on the ticket.
|
||||
- Verify with `GET /time/entries/{id}`: check `member.identifier`, `timeStart`/`timeEnd`, `actualHours`, and `notes` (top level; `_info.notes` only appears on the ticket timeEntries list view).
|
||||
- `billableOption`: "Billable" or "NoCharge".
|
||||
|
||||
## Add a ticket note
|
||||
|
||||
`POST /service/tickets/{id}/notes`:
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "<note text>",
|
||||
"detailDescriptionFlag": true,
|
||||
"internalAnalysisFlag": false,
|
||||
"resolutionFlag": false
|
||||
}
|
||||
```
|
||||
|
||||
- Do NOT include `customerVisibleFlag` (or any field not on ServiceNote) — the API 400s the entire request: "Could not find member 'customerVisibleFlag' on object of type 'ServiceNote'".
|
||||
- Notes are attributed to the API member (TinMan), not an arbitrary author.
|
||||
- Verify with `GET /service/tickets/{id}/notes`.
|
||||
|
||||
## Change ticket status
|
||||
|
||||
1. Resolve the numeric status ID: `GET /service/boards/{boardId}/statuses`, match on `name` (VistaNet Help Desk board = 1; "In Progress" = id 1; names starting with `>` are closed-type statuses).
|
||||
2. PATCH `/service/tickets/{id}` with a JSON Patch ARRAY:
|
||||
|
||||
```json
|
||||
[{"op": "replace", "path": "status/id", "value": 1}]
|
||||
```
|
||||
|
||||
3. Verify: `GET /service/tickets/{id}?fields=id,status`.
|
||||
|
||||
## Documentation audit of a scheduled day
|
||||
|
||||
```python
|
||||
# UTC bounds: Pacific day 00:00–24:00 PDT → add 7 hours
|
||||
params = {"conditions": f"member/id={memberId} AND dateStart >= [{startUtc}] AND dateStart <= [{endUtc}]",
|
||||
"pageSize": 100}
|
||||
entries = norm(cw_api.cw_request("GET", "schedule/entries", params=params))
|
||||
# per entry: objectId = ticket id → GET /service/tickets/{id}/notes AND /service/tickets/{id}/timeEntries
|
||||
# count only notes where note["member"]["identifier"] matches the member — time entries do NOT count as notes
|
||||
# report per ticket: notes present / time-entry-only / nothing
|
||||
```
|
||||
Reference in new issue
Block a user