sync: auto-sync from HOWARD-HOME at 2026-05-05 16:47:31
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-05-05 16:47:31
This commit is contained in:
@@ -486,12 +486,43 @@ This is the documented billing path. It records hours into Syncro's time-trackin
|
||||
| Charge timer (creates line item) | POST | `/tickets/<id>/charge_timer_entry` |
|
||||
| Update timer | PUT | `/tickets/<id>/update_timer_entry` |
|
||||
| Delete timer | POST | `/tickets/<id>/delete_timer_entry` |
|
||||
| List timers | GET | `/ticket_timers?ticket_id=<id>` |
|
||||
| List timers (on a ticket) | GET | `/tickets/<id>` → `.ticket.ticket_timers` |
|
||||
|
||||
**CRITICAL — response shapes are FLAT:** Both `POST /timer_entry` and `POST /charge_timer_entry` return a flat object — `{"id": N, "ticket_id": ..., "product_id": ..., ...}` — NOT wrapped in `{"timer": {...}}` or `{"timer_entry": {...}}`. Parse as `.id` directly. The wrapped pattern silently returns `null`, breaks `charge_timer_entry` ("Not found"), and triggers a duplicate-timer retry. Hit on ticket #32253 on 2026-05-05; recovery via `delete_timer_entry`. Verified shape:
|
||||
|
||||
```json
|
||||
// POST /tickets/{id}/timer_entry response
|
||||
{"id": 39031258, "ticket_id": 109895882, "user_id": 1750, "start_time": "...", "end_time": "...",
|
||||
"recorded": false, "billable": true, "notes": "...", "product_id": 26118,
|
||||
"comment_id": null, "ticket_line_item_id": null, "active_duration": 1800, ...}
|
||||
|
||||
// POST /tickets/{id}/charge_timer_entry response (also flat)
|
||||
{"id": 39031258, "recorded": true, "ticket_line_item_id": 42313052, ...}
|
||||
```
|
||||
|
||||
**CRITICAL — duplicate prevention:** Syncro has no idempotency on `/timer_entry`. **Never retry the POST without first GET `/tickets/{id}` and inspecting `.ticket.ticket_timers[]`.** The standalone `GET /ticket_timers?ticket_id=N` query parameter does NOT filter — it returns the entire global timer history. Use the ticket object instead.
|
||||
|
||||
```bash
|
||||
# Verification pattern after ambiguous timer_entry response
|
||||
curl -s "${BASE}/tickets/${ID}?api_key=${API_KEY}" | \
|
||||
jq '.ticket.ticket_timers[] | select(.recorded == false) | {id, start_time, end_time, product_id, notes}'
|
||||
```
|
||||
|
||||
If duplicates exist, delete the older one(s) before charging:
|
||||
```bash
|
||||
curl -s -X POST "${BASE}/tickets/${ID}/delete_timer_entry?api_key=${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @- <<'JSON'
|
||||
{"timer_entry_id": <older_duplicate_id>}
|
||||
JSON
|
||||
# Returns: {"success": true}
|
||||
```
|
||||
|
||||
```bash
|
||||
# 1. Create timer entry — records hours in Syncro's time-tracking system.
|
||||
# For warranty / no-charge work, set "billable": false (time still records).
|
||||
curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \
|
||||
# Capture .id directly — response is FLAT (see above).
|
||||
TIMER_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @- <<'JSON'
|
||||
{
|
||||
@@ -502,13 +533,15 @@ curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \
|
||||
"product_id": 1190473
|
||||
}
|
||||
JSON
|
||||
)
|
||||
TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.id')
|
||||
|
||||
# 2. Charge the timer — sets recorded:true and auto-generates a linked line
|
||||
# item with the timer's product_id and computed quantity (hours).
|
||||
curl -s -X POST "${BASE}/tickets/${ID}/charge_timer_entry?api_key=${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @- <<'JSON'
|
||||
{"timer_entry_id": N}
|
||||
--data-binary @- <<JSON
|
||||
{"timer_entry_id": ${TIMER_ID}}
|
||||
JSON
|
||||
|
||||
# 3. Verify the auto-generated line item picked up the rate. Syncro sometimes
|
||||
@@ -622,7 +655,9 @@ Winter caught this on #32203 (Desert Auto Tech) 2026-04-23 after a stack of 1hr
|
||||
| Charge timer → line item | POST | `/tickets/<id>/charge_timer_entry` |
|
||||
| Update timer | PUT | `/tickets/<id>/update_timer_entry` |
|
||||
| Delete timer | POST | `/tickets/<id>/delete_timer_entry` |
|
||||
| List timers | GET | `/ticket_timers?ticket_id=<id>` |
|
||||
| List timers (on a ticket) | GET | `/tickets/<id>` → `.ticket.ticket_timers` |
|
||||
|
||||
Both `POST /timer_entry` and `POST /charge_timer_entry` return FLAT objects — parse `.id` directly. See "Billable Line Items → Default" above for the full response-shape note and duplicate-prevention pattern.
|
||||
|
||||
#### Invoices
|
||||
|
||||
@@ -716,7 +751,7 @@ TIMER_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KE
|
||||
}
|
||||
JSON
|
||||
)
|
||||
TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.timer.id // .timer_entry.id')
|
||||
TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.id') # response is FLAT — see "response shapes" note above
|
||||
|
||||
# Step 3: Charge the timer — creates the linked line item automatically.
|
||||
curl -s -X POST "${BASE}/tickets/${ID}/charge_timer_entry?api_key=${API_KEY}" \
|
||||
|
||||
Reference in New Issue
Block a user