sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-22 13:42:56

Author: Mike Swanson
Machine: DESKTOP-0O8A1RL
Timestamp: 2026-05-22 13:42:56
This commit is contained in:
2026-05-22 13:42:59 -07:00
parent ee865426c7
commit 66c65fa9bb
2 changed files with 185 additions and 88 deletions

View File

@@ -15,6 +15,7 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
/syncro search <query> Search tickets by subject/customer
/syncro customers <query> Search customers
/syncro move-appointment <customer> Find and reschedule an existing appointment
/syncro estimate <customer> <subject> Create ticket + linked estimate with line items and private purchase notes
```
## API Configuration
@@ -584,139 +585,129 @@ POST `/invoices` pulls all current line items from the ticket into the invoice a
#### Estimates
Estimates (quotes) always get an associated ticket with a private note containing links. This is a hard workflow requirement — never create an estimate without the ticket and private note.
Estimates (quotes) always require a linked ticket — create the ticket first, then the estimate with `ticket_id` set. This enables private notes (purchase links, sourcing details) on the ticket using the standard hidden comment endpoint. Estimates created without a ticket have no notes surface accessible via API. Verified 2026-05-22 against ACG internal account.
**Required fields for POST /estimates:** `customer_id`, `date` (ISO date string `"YYYY-MM-DD"`)
**Optional:** `name` (estimate title), `ticket_id` (link to ticket), `location_id`
**Statuses:** `Fresh` (default), `Approved`, `Declined`
**MANDATORY estimate workflow (4 steps, always in this order):**
1. Create estimate
2. Add line items (with price fix — see below)
3. Create ticket (`do_not_email: true`, `hidden: true` note) with private note containing estimate link + product/source links
4. Link estimate to ticket via PUT, then post a single bot alert with both links
**Full estimate workflow (ticket → estimate → line items → recalc → private notes):**
```bash
# Step 1 — Create estimate
TODAY=$(date +%Y-%m-%d)
# 1. Create ticket
TICKET_RESP=$(curl -s -X POST "${BASE}/tickets?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"customer_id": ${CUST_ID},
"subject": "Estimate: <subject>",
"status": "New",
"problem_type": "Estimate"
}
JSON
)
TICKET_ID=$(echo "$TICKET_RESP" | jq -r '.ticket.id')
# 2. Create estimate linked to ticket
EST_RESP=$(curl -s -X POST "${BASE}/estimates?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"customer_id": ${CUST_ID},
"name": "Estimate for <subject>",
"date": "$(date +%Y-%m-%d)"
"name": "<subject>",
"date": "${TODAY}",
"ticket_id": ${TICKET_ID}
}
JSON
)
ESTIMATE_ID=$(echo "$EST_RESP" | jq -r '.estimate.id')
ESTIMATE_NUM=$(echo "$EST_RESP" | jq -r '.estimate.number')
# Step 2 — Add line item, then fix price via PUT
# NOTE: POST /line_items ignores price_retail for hardware (product 32252) — price stays $0.
# Always follow up with PUT /line_items/{id} to set the price. Verified 2026-05-22.
LI_RESP=$(curl -s -X POST "${BASE}/estimates/${ESTIMATE_ID}/line_items?api_key=${API_KEY}" \
# 3. Add line items
# Labor — price_retail is respected on POST
LI_LABOR=$(curl -s -X POST "${BASE}/estimates/${ESTIMATE_ID}/line_items?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"product_id": ${PRODUCT_ID},
"name": "<product name>",
"description": "<one-line description>",
"quantity": ${QTY},
"product_id": ${LABOR_PRODUCT_ID},
"name": "<labor product name>",
"description": "<work description>",
"quantity": ${HOURS},
"price_retail": ${RATE},
"taxable": false
}
JSON
)
# Hardware — price_retail is IGNORED on POST (product 32252 always lands at $0); set via PUT below
LI_HW=$(curl -s -X POST "${BASE}/estimates/${ESTIMATE_ID}/line_items?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"product_id": 32252,
"name": "<item name>",
"description": "<brief description>",
"quantity": ${QTY},
"price_retail": ${PRICE},
"taxable": true
}
JSON
)
LI_ID=$(echo "$LI_RESP" | jq -r '.line_item.id')
LI_HW_ID=$(echo "$LI_HW" | jq -r '.line_item.id')
# Fix price via PUT (required — POST does not apply price_retail for hardware)
curl -s -X PUT "${BASE}/estimates/${ESTIMATE_ID}/line_items/${LI_ID}?api_key=${API_KEY}" \
# 4. Set hardware price via PUT (required — POST price is ignored for product 32252)
curl -s -X PUT "${BASE}/estimates/${ESTIMATE_ID}/line_items/${LI_HW_ID}?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{"price": ${RATE}, "price_retail": ${RATE}}
{"price": ${PRICE}, "price_retail": ${PRICE}}
JSON
> /dev/null
# Step 3 — Create ticket + private note
TICKET_RESP=$(curl -s -X POST "${BASE}/tickets?api_key=${API_KEY}" \
# 5. Force recalc — PUT any field on the estimate; response contains live totals
RECALC=$(curl -s -X PUT "${BASE}/estimates/${ESTIMATE_ID}?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"customer_id": ${CUST_ID},
"subject": "<subject matching estimate name>",
"problem_type": "Hardware",
"status": "New",
"priority": "2 Normal",
"user_id": ${TECH_ID},
"do_not_email": true
}
{"date": "${TODAY}"}
JSON
)
TICKET_ID=$(echo "$TICKET_RESP" | jq -r '.ticket.id')
TICKET_NUM=$(echo "$TICKET_RESP" | jq -r '.ticket.number')
echo "Estimate #${ESTIMATE_NUM} — subtotal: $(echo "$RECALC" | jq '.estimate.subtotal') total: $(echo "$RECALC" | jq '.estimate.total')"
# Private note — hidden, with estimate link + product/source links
# 6. Add private notes to linked ticket — one per hardware line item with purchase source/link
# First note: include the estimate link; one additional note per item as needed
curl -s -X POST "${BASE}/tickets/${TICKET_ID}/comment?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"subject": "Estimate Links",
"body": "Estimate #${ESTIMATE_NUM}: https://computerguru.syncromsp.com/estimates/${ESTIMATE_ID}<br><source link description>: <URL><br><cost breakdown>",
"body": "Estimate #${ESTIMATE_NUM}: https://computerguru.syncromsp.com/estimates/${ESTIMATE_ID}<br><br>Purchase: <item name> x${QTY} - <URL> - MSRP \$X.XX ea, quoted at \$${PRICE} (markup)",
"hidden": true,
"do_not_email": true
}
JSON
# Step 4 — Link estimate to ticket + touch to recalculate total
curl -s -X PUT "${BASE}/estimates/${ESTIMATE_ID}?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{"ticket_id": ${TICKET_ID}}
JSON
# GET estimate (verify total recalculated)
curl -s "${BASE}/estimates/${ESTIMATE_ID}?api_key=${API_KEY}" | \
jq '{id: .estimate.id, number: .estimate.number, total: .estimate.total,
lines: [.estimate.line_items[]? | {id, item, name, quantity, price}]}'
# DELETE estimate
curl -s -X DELETE "${BASE}/estimates/${ESTIMATE_ID}?api_key=${API_KEY}"
# Response: {"message": "N: We deleted # NNNN. "}
> /dev/null
```
**Required fields for POST /estimates:** `customer_id`, `date` (ISO date string `"YYYY-MM-DD"`)
**Optional:** `name` (estimate title), `ticket_id` (link to ticket), `location_id`
**Statuses:** `Fresh` (default), `Approved`, `Declined`
**Line item field naming in estimate responses:** `item` = product name, `name` = description, `price` = unit rate. This matches invoice line_items, NOT ticket `add_line_item` (which uses `price_retail`).
**GET /estimates line_items vs POST response:** GET returns `line_items` as an array on `.estimate.line_items[]`. POST `/line_items` returns the line item under `.line_item` (singular, not nested under estimate).
**Estimate total recalculation:** The `total` field on GET /estimates does not update automatically after line item changes. Always do a PUT on the estimate (even a no-op name update) to trigger recalculation, then re-fetch to verify.
**Stale estimate totals after line item PUT — force recalc with a PUT touch:** After using `PUT /estimates/{id}/line_items/{id}` to set hardware prices, the estimate-level `total` and `subtotal` remain stale until the estimate record itself is saved. Fix: `PUT /estimates/{id}` with any innocuous field (e.g. `{"date": "YYYY-MM-DD"}`). The PUT response and all subsequent GETs show correct recalculated totals. No dedicated `/recalculate` endpoint exists (404). Verified 2026-05-22 on test estimate #7184.
**Hardware on estimates:** All hardware line items use a single generic product — `product_id: 32252` ("Hardware", `price_retail: 0.0`). The specific item name and price are set per-line-item via the `name` and `price_retail` fields on each line. Never look up a separate product ID for hardware items on estimates — always use `32252` and vary the description and price per item.
**Private notes on estimates — use the linked ticket:** Estimates have no notes/comments API surface of their own (`POST /estimates/{id}/notes` and `/comments` both 404; `note`/`private_note`/`notes` fields on PUT are silently ignored). The ticket linked via `ticket_id` is the only place to store private notes. Use `POST /tickets/{id}/comment` with `hidden: true, do_not_email: true`. Note the endpoint is `/comment` (singular) — `/comments` (plural) returns 404. Verified 2026-05-22 on test ticket #32315.
**Hardware line item price bug (verified 2026-05-22):** POST `/estimates/{id}/line_items` ignores `price_retail` for product 32252 — the line item is created at $0. Always follow POST with a PUT to `/estimates/{id}/line_items/{li_id}` passing both `price` and `price_retail`. The PUT succeeds and sets the price correctly.
**Hardware on estimates:** All hardware uses `product_id: 32252` ("Hardware", base price $0). Set the actual price per item via `name` and `price_retail` fields — but price_retail is ignored on POST for this product. Always follow with a PUT to set the price. Never look up individual hardware product IDs.
```bash
# Example hardware line item on an estimate (POST + required price fix PUT)
LI_RESP=$(curl -s -X POST "${BASE}/estimates/${ESTIMATE_ID}/line_items?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"product_id": 32252,
"name": "Dell OptiPlex 7010 SFF",
"description": "Refurbished desktop, 16GB RAM, 512GB SSD",
"quantity": 1.0,
"price_retail": 649.00,
"taxable": true
}
JSON
)
LI_ID=$(echo "$LI_RESP" | jq -r '.line_item.id')
# GET estimate (verify line items and totals)
curl -s "${BASE}/estimates/${ESTIMATE_ID}?api_key=${API_KEY}" | \
jq '{id: .estimate.id, number: .estimate.number, subtotal: .estimate.subtotal, total: .estimate.total,
lines: [.estimate.line_items[]? | {id, item, name, quantity, price}]}'
# Required: fix price via PUT
curl -s -X PUT "${BASE}/estimates/${ESTIMATE_ID}/line_items/${LI_ID}?api_key=${API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{"price": 649.00, "price_retail": 649.00}
JSON
# DELETE estimate
curl -s -X DELETE "${BASE}/estimates/${ESTIMATE_ID}?api_key=${API_KEY}"
# Response: {"message": "N: We deleted # NNNN. "}
```
### Display formatting
@@ -868,9 +859,6 @@ echo "$ALERT_OUT"
|---|---|
| Ticket (create / update / close / comment / bill) | `https://computerguru.syncromsp.com/tickets/<ticket.id>` |
| Customer (create) | `https://computerguru.syncromsp.com/customers/<customer.id>` |
| Estimate (create) | `https://computerguru.syncromsp.com/estimates/<estimate.id>` |
**Estimate alert — single post with both links:** When creating an estimate, send ONE alert after all four steps complete (estimate + line items + ticket + link). Include both the ticket link and estimate link in a single message.
**Examples:**
@@ -880,10 +868,6 @@ bash "$CLAUDETOOLS_ROOT/.claude/scripts/post-bot-alert.sh" \
"[SYNCRO] Howard created #32301 (Desert Auto Tech) - Server won't boot -> https://computerguru.syncromsp.com/tickets/110736645"
# Success output: [OK] post-bot-alert: posted to #bot-alerts (message_id=1507055781780918404)
# Estimate created (single alert, both links)
bash "$CLAUDETOOLS_ROOT/.claude/scripts/post-bot-alert.sh" \
"[SYNCRO] Mike created estimate #7188 (Arizona Computer Guru) - ASUS V500 i7 Workstation \$849.99 | ticket #32316 -> https://computerguru.syncromsp.com/tickets/110843061 | https://computerguru.syncromsp.com/estimates/23967407"
# Billed + invoiced
bash "$CLAUDETOOLS_ROOT/.claude/scripts/post-bot-alert.sh" \
"[SYNCRO] Mike billed #32164 (Jerry Burger) - 1.0h remote, \$150.00 -> https://computerguru.syncromsp.com/tickets/110169036"