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` |
|
| Charge timer (creates line item) | POST | `/tickets/<id>/charge_timer_entry` |
|
||||||
| Update timer | PUT | `/tickets/<id>/update_timer_entry` |
|
| Update timer | PUT | `/tickets/<id>/update_timer_entry` |
|
||||||
| Delete timer | POST | `/tickets/<id>/delete_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
|
```bash
|
||||||
# 1. Create timer entry — records hours in Syncro's time-tracking system.
|
# 1. Create timer entry — records hours in Syncro's time-tracking system.
|
||||||
# For warranty / no-charge work, set "billable": false (time still records).
|
# 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" \
|
-H "Content-Type: application/json" \
|
||||||
--data-binary @- <<'JSON'
|
--data-binary @- <<'JSON'
|
||||||
{
|
{
|
||||||
@@ -502,13 +533,15 @@ curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KEY}" \
|
|||||||
"product_id": 1190473
|
"product_id": 1190473
|
||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
|
)
|
||||||
|
TIMER_ID=$(echo "$TIMER_RESP" | jq -r '.id')
|
||||||
|
|
||||||
# 2. Charge the timer — sets recorded:true and auto-generates a linked line
|
# 2. Charge the timer — sets recorded:true and auto-generates a linked line
|
||||||
# item with the timer's product_id and computed quantity (hours).
|
# 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}" \
|
curl -s -X POST "${BASE}/tickets/${ID}/charge_timer_entry?api_key=${API_KEY}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
--data-binary @- <<'JSON'
|
--data-binary @- <<JSON
|
||||||
{"timer_entry_id": N}
|
{"timer_entry_id": ${TIMER_ID}}
|
||||||
JSON
|
JSON
|
||||||
|
|
||||||
# 3. Verify the auto-generated line item picked up the rate. Syncro sometimes
|
# 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` |
|
| Charge timer → line item | POST | `/tickets/<id>/charge_timer_entry` |
|
||||||
| Update timer | PUT | `/tickets/<id>/update_timer_entry` |
|
| Update timer | PUT | `/tickets/<id>/update_timer_entry` |
|
||||||
| Delete timer | POST | `/tickets/<id>/delete_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
|
#### Invoices
|
||||||
|
|
||||||
@@ -716,7 +751,7 @@ TIMER_RESP=$(curl -s -X POST "${BASE}/tickets/${ID}/timer_entry?api_key=${API_KE
|
|||||||
}
|
}
|
||||||
JSON
|
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.
|
# 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}" \
|
curl -s -X POST "${BASE}/tickets/${ID}/charge_timer_entry?api_key=${API_KEY}" \
|
||||||
|
|||||||
Reference in New Issue
Block a user