---
name: Syncro billing rules — products, rates, taxes, attribution, emergency, warranty
description: How to bill a Syncro ticket correctly — fetch live rates, use real product names, pick the right labor type for the delivery channel, set taxable=false on labor (AZ), warranty product 1049360, emergency ×1.5 (branch by prepay_hours), preserve original tech's user_id on corrections, estimate hardware uses product 32252.
metadata:
  type: feedback
---

Rules only. Incident detail, verbatim Mike quotes, ticket numbers, dates, the tech user_id table, and the labor-product table all live in [[feedback_syncro_history]] — read on-demand when judging an edge case. API mechanics: [[feedback_syncro_api]]. Workflow: [[feedback_syncro_workflow]].

`.claude/commands/syncro.md` is the authoritative live product table.

---

## 1. Bill with `add_line_item` directly — never the timer workflow

`POST /tickets/{id}/add_line_item` is the billing path for ALL work (labor, warranty, internal, hardware). The timer workflow (`timer_entry → charge_timer_entry`) is **not used**.

**Payload:** `product_id`, `quantity` (decimal hours), `price_retail` (fetched live), `name` (the product's REAL name — see §3), `description` (free-text work narrative), `taxable: false` for labor.

---

## 2. ALWAYS fetch the rate live

Fetch `price_retail` from `GET /products/<id>` → `.product.price_retail` before billing. The product-ID table in `.claude/commands/syncro.md` is valid for IDs but **not** dollar amounts — rates vary by contract and change.

```bash
RATE=$(curl -s "$BASE/products/$PRODUCT_ID?api_key=$API_KEY" | jq -r '.product.price_retail')
```

Use `$RATE` for drafts, the user preview, and the `price_retail` field.

---

## 3. NEVER invent or rename labor line items

Every labor line MUST be an existing Syncro product, billed under its **REAL name** (from `GET /products/<id>` → `.product.name`, verbatim). Work-specific narrative goes in `description`, never the `name`.

**Why:** invented names break the Syncro → QuickBooks sync. QB maps each labor line to an existing item; a fabricated name has no QB match and messes up the accounting. If no existing product fits, **STOP and ask Mike** — never invent one.

Product table lives in [[feedback_syncro_history]] (and `.claude/commands/syncro.md`).

---

## 4. Labor type must match delivery channel — never "Prepaid project labor"

Pick the labor product matching how work was delivered: **remote** (most common), **onsite**, **in-shop**, or **web**. Resolve `product_id` via `GET /products?search=remote+labor` etc.

**Never default to "Prepaid project labor"** — it is **exempt** and does NOT consume hours from a customer's prepaid block. Block accounting silently drifts.

**Verify:** after billing a prepay-block customer, confirm the block balance dropped by the expected hours. If it didn't, the labor type was wrong.

---

## 5. Labor is NEVER taxable in Arizona

Pass `"taxable": false` explicitly on every labor line. The product config has `taxable: false`, but `add_line_item` does **not** inherit it — posts as `taxable: true` regardless. Applies to remote, onsite, in-shop, emergency, warranty, prepaid.

---

## 6. Warranty / no-charge → product `1049360`, never patch the price

Warranty work uses `product_id: 1049360` ("Labor- Warranty work", $0/hr, non-taxable). The line generates at $0 from `price_retail × quantity` — no need to flag or patch anything.

**Do NOT** pick a regular labor product and try to neutralize it with `billable: false` or by patching `price_retail` to `0`. **Prices are set by selecting the correct product.** If you reach for `update_line_item` to drop a price, that's the signal to back up and pick a different `product_id`.

The only legitimate `update_line_item price_retail` use is the Syncro auto-gen-zero recovery case (auto-line came in at $0 instead of the product's rate).

---

## 7. Emergency / after-hours — ×1.5 applied ONCE; branch by `prepay_hours`

`GET /customers/<id>` and read `prepay_hours` BEFORE adding any emergency line. Emergency = **time-and-a-half, applied ONCE**. Never bill a separate regular line + emergency line for the same hours.

**No prepaid block (`prepay_hours == 0`):**
- product `26184`, quantity = **actual hours** (do NOT also ×1.5 the quantity)
- `price_retail` by delivery channel (the 1.5× lives in the dollars):
  - Onsite emergency = `$262.50` (175 × 1.5; 26184's default).
  - Remote / In-Shop emergency = `$225` (150 × 1.5) → override `price_retail` to `225`.

**Prepaid block (`prepay_hours > 0`):**
- product `26184`, quantity = **actual hours × 1.5** (premium goes in the QUANTITY)
- Delivery channel / dollar rate is irrelevant; prepaid blocks debit by quantity. Invoice nets to $0; block debits hrs×1.5.
- e.g. 1.5 emergency hrs → `26184` @ `2.25`.

Always set `price_retail` explicitly — the rate doesn't auto-populate and the line posts $0 if omitted. Verify after: `.invoice.total` (non-prepaid) or block decrement (prepaid).

---

## 8. Corrections preserve the ORIGINAL tech's attribution; ticket ownership is sticky

**Labor lines:** when fixing a wrong line, preserve the **original tech's** `user_id` so their commission isn't lost.
- **Prefer `update_line_item` in place** — it preserves `user_id`.
- **If you must remove + re-add:** the new line defaults to the **API-key owner's** `user_id`. Explicitly set `user_id` to the original tech on `add_line_item`, or PUT-fix it afterward.
- Determine the original tech from `.ticket.user_id` and the line's `.user_id` BEFORE correcting; verify after.

**Ticket ownership:** adding notes or labor does **NOT** change `.ticket.user_id`. Multiple techs routinely work the same ticket. Only change ticket ownership when explicitly asked. Status PUTs send only `status`; line edits use `update_line_item`; neither touches `user_id`.

Tech user_id table → [[feedback_syncro_history]].

---

## 9. Estimate hardware → product `32252`

All hardware on estimates uses one generic product: `product_id: 32252` ("Hardware", `price_retail: 0.0`). Differentiate via `name` ("Dell OptiPlex 7010") and `price_retail` (actual cost). Hardware is typically `taxable: true`. Never look up individual hardware product IDs — there's only one.
