Compressed memory store 104 -> 71 files via four passes: - Syncro: 19 scattered feedback_syncro_* files merged into 3 rule files (api/billing/workflow) + an on-demand feedback_syncro_history.md for incident detail, quotes, and tech/product ID tables. - Four near-duplicate merges: Howard paste-safety, Pluto build server, Howard backend deferral, IX server access (ssh+tailscale). - Per-cluster rule/state/history split applied to GuruConnect (2->1), Dataforth (3->2), Cascades (7->3), GuruRMM (13->3). - New reference_resource_map.md: single auto-loaded cheatsheet for "do I have access to X and how do I connect from this machine?" - MEMORY.md rewritten to match the new layout. Health: broken backlinks 8->7, overlap clusters 12->5, orphans 17->0.
105 lines
6.1 KiB
Markdown
105 lines
6.1 KiB
Markdown
---
|
||
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.
|