--- 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/` → `.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/` → `.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/` 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.