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:
2026-05-05 16:47:31 -07:00
parent bc39d75304
commit eb73a55442

View File

@@ -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}" \