diff --git a/.claude/commands/syncro.md b/.claude/commands/syncro.md index a9b1699..f8b9edb 100644 --- a/.claude/commands/syncro.md +++ b/.claude/commands/syncro.md @@ -16,6 +16,8 @@ Create, update, close, comment on, and bill tickets in Syncro PSA. /syncro customers Search customers /syncro move-appointment Find and reschedule an existing appointment /syncro estimate Create ticket + linked estimate with line items and private purchase notes +/syncro schedules List recurring invoice schedules for a customer +/syncro schedule View a schedule's template and line items ``` ## API Configuration @@ -51,6 +53,8 @@ Create, update, close, comment on, and bill tickets in Syncro PSA. **Always pass `"taxable": false` explicitly on labor line items.** Labor products are configured with `taxable: false` in Syncro, but `add_line_item` via API does not inherit the product's taxable setting — it posts the line item as `taxable: true` regardless. Always include `"taxable": false` in the payload to match the product's configured value. +**`DELETE /schedules/{id}` destroys the recurring invoice template immediately — no confirmation, no undo.** Past generated invoices are unaffected but future billing stops. The schedule must be recreated manually with all line items if deleted accidentally. NEVER run destructive HTTP method probes against a live customer schedule — use ACG internal account (customer_id 15353550) for any testing. Incident: Russo Law Firm schedule 224454 deleted during API research 2026-05-26; recreated as 509659. + **Appointment dates — always verify day-of-week before the preview.** Day-of-week math is easy to get wrong. Before including any appointment date in a preview, run a live check and display the full day name alongside the date (e.g. "Saturday 2026-05-23", never just "2026-05-23"). The user confirms the day name at the preview step — if the name is wrong, the date is wrong. Incident: #32312 booked Sunday May 24 instead of Saturday May 23 (2026-05-21). Reported by Winter. ```bash @@ -455,6 +459,14 @@ Every endpoint's response shape, verified against the live API. Parse exactly as | Add estimate line item | POST `/estimates/{id}/line_items` | `{"estimate": {...}, "line_item": {"id": N, ...}}` | `.line_item.id` | | Get estimate | GET `/estimates/{id}` | `{"estimate": {"line_items": [...]}}` | `.estimate.line_items[].price` | | Delete estimate | DELETE `/estimates/{id}` | `{"message": "N: We deleted # NNNN. "}` | — | +| List schedules | GET `/schedules` | `{"schedules": [...], "meta": {...}}` | `.schedules[].id`, `.meta.total_entries` | +| Get schedule | GET `/schedules/{id}` | `{"schedule": {"lines": [...]}}` | `.schedule.id`, `.schedule.lines[].id` | +| Create schedule | POST `/schedules` | `{"schedule": {...}}` | `.schedule.id` | +| Update schedule | PUT `/schedules/{id}` | `{"schedule": {...}}` | `.schedule.next_run`, `.schedule.paused` | +| Delete schedule | DELETE `/schedules/{id}` | `{"success": "deleted"}` | — **NO CONFIRMATION — immediate** | +| Add schedule line | POST `/schedules/{id}/line_items` | `{"schedule_line_item": {...}}` | `.schedule_line_item.id` | +| Update schedule line | PUT `/schedules/{id}/line_items/{li_id}` | `{"schedule_line_item": {...}}` | `.schedule_line_item.quantity`, `.schedule_line_item.price_retail` | +| Delete schedule line | DELETE `/schedules/{id}/line_items/{li_id}` | `{"success": true}` | — | **Invoice GET line_items field names differ from ticket line_items:** `item` = product name, `name` = description, `price` = unit rate. Do not use `price_retail` when reading invoice line items. @@ -592,6 +604,106 @@ curl -s -X DELETE "${BASE}/invoices/${INV_ID}?api_key=${API_KEY}" POST `/invoices` pulls all current line items from the ticket into the invoice automatically. The POST response includes `.invoice.id` and `.invoice.total` — if either is null, GET `/invoices?customer_id=${CUST_ID}&per_page=5` and find the invoice by `ticket_id` match before taking any other action. +#### Recurring Invoice Schedules + +Recurring invoice templates are at `/schedules` — **not** `/recurring_invoices` (404). Generated invoices carry a `schedule_id` field linking back to the template. The `recurring_invoice_id` field on invoices is always null; ignore it. + +**Hard rule: never run HTTP method probes against a live customer schedule. Always use an ACG internal test schedule (customer_id 15353550) for destructive-method testing. `DELETE /schedules/{id}` has no confirmation and destroys the template immediately — past generated invoices are unaffected but future billing stops.** + +Valid `frequency` values (verified): `Monthly`, `Quarterly`, `Annually`, `Weekly`, `Biweekly`. All other strings return `{"error": ["Frequency must be a valid selection"]}`. + +```bash +# List all schedules — 50/page, filterable +curl -s "${BASE}/schedules?api_key=${API_KEY}" +curl -s "${BASE}/schedules?customer_id=${CUST_ID}&api_key=${API_KEY}" +curl -s "${BASE}/schedules?paused=true&api_key=${API_KEY}" +# meta: {total_pages, total_entries, per_page} + +# Get one schedule (includes full lines array) +curl -s "${BASE}/schedules/${SCHED_ID}?api_key=${API_KEY}" | jq ' +{ + id: .schedule.id, + name: .schedule.name, + customer_id: .schedule.customer_id, + frequency: .schedule.frequency, + next_run: .schedule.next_run, + paused: .schedule.paused, + subtotal: .schedule.subtotal, + email_customer: .schedule.email_customer, + lines: [.schedule.lines[] | {id, name, quantity, price_retail, product_id}] +}' + +# Create schedule — response: {"schedule": {...}} +# Required: customer_id, name, frequency, next_run +SCHED_RESP=$(curl -s -X POST "${BASE}/schedules?api_key=${API_KEY}" \ + -H "Content-Type: application/json" \ + --data-binary @- <<'JSON' +{ + "customer_id": CUST_ID, + "name": "Schedule name", + "frequency": "Monthly", + "next_run": "YYYY-MM-DD", + "email_customer": true, + "paused": false, + "snail_mail": false, + "charge_mop": false, + "invoice_unbilled_ticket_charges": false +} +JSON +) +SCHED_ID=$(echo "$SCHED_RESP" | jq -r '.schedule.id') + +# Update schedule — any writable field, response: {"schedule": {...}} +# Updatable: name, frequency, next_run, paused, email_customer, snail_mail, +# charge_mop, invoice_unbilled_ticket_charges +curl -s -X PUT "${BASE}/schedules/${SCHED_ID}?api_key=${API_KEY}" \ + -H "Content-Type: application/json" \ + --data-binary @- <