docs(session)+rules: 2026-05-27 — Quantum M365 onboarding, IX autodiscover fix, Syncro emergency/labor/attribution rules

Session logs: root (Michael #32329 hosting offer + IX simplehost.email autodiscover DNS fix + Cascades #32332 emergency correction) + Quantum client log (M365 tenant 2fd0092b onboarding, break-glass GA, CA report-only).

Syncro rule overhaul:
- Emergency billing: prepaid -> 26184 @ hours x1.5 (was 26118); non-prepaid -> 26184 with channel rate (onsite $262.50 / remote+inshop $225)
- Never make up labor items (existing product + real name; QuickBooks sync)
- Corrections preserve original tech's user_id (commission); adding notes/labor never changes ticket owner

/remediation-tool: Conditional Access may be managed programmatically (report-only first + exclude break-glass + confirm before enforce); fabb3421 deprecated for customer tenants; Quantum tenant onboarded (gotchas table).

Memory: 4 new (no-madeup-labor, corrections-preserve-tech, ca-programmatic, quantum-godaddy-tenant) + updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 14:57:53 -07:00
parent 5d9c739823
commit ba90915da5
12 changed files with 249 additions and 54 deletions

View File

@@ -43,7 +43,7 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
**Billing:** Always ask for minutes and labor type before adding any line item. Never assume a default.
**Emergency/after-hours billing — check prepaid first:** Before adding a `26184` (Emergency) line item, `GET /customers/<id>` and read `prepay_hours`. If `prepay_hours > 0`, the customer has a prepaid block — bill `26118` (Onsite) at `quantity × 1.5` instead (prepaid debits by quantity, not by dollars). Never stack `26118` + `26184` for the same hours — the Emergency product rate already has the 1.5× multiplier baked in. Verified 2026-04-23 on ticket #32203 (Desert Auto Tech) after Winter caught the bug.
**Emergency/after-hours billing — check prepaid first:** Before adding a `26184` (Emergency) line item, `GET /customers/<id>` and read `prepay_hours`. Emergency = time-and-a-half (×1.5), applied ONCE — never bill a separate regular + emergency line for the same hours. **No prepaid (`prepay_hours == 0`):** `26184` at qty = actual hours; set `price_retail` by delivery channel — **Onsite $262.50** (175×1.5, 26184's default), **Remote / In-Shop $225** (150×1.5, override price_retail). The rate carries the 1.5×; do NOT also ×1.5 the qty. **Prepaid (`prepay_hours > 0`):** still use `26184`, at qty = actual hours **× 1.5** (premium goes in the quantity since prepaid debits by quantity; invoice nets $0, block debits hours×1.5). e.g. 1.5 emergency hrs prepaid → `26184` @ 2.25. (Rule updated 2026-05-27 by Mike: prepaid emergency uses `26184`, NOT the old `26118`×1.5 — keeps the line labeled emergency + mapping right in QuickBooks. Original ×1.5-not-additive lesson: #32203 Desert Auto Tech 2026-04-23, Winter.)
**Prepaid customers — ALL billing (not just emergency):** `GET /customers/<id>``prepay_hours` before creating ANY invoice for a prepaid customer. When you bill a prepaid customer using a billable labor product (remote / onsite / in-shop / web), Syncro automatically deducts from their prepay block and the invoice total shows $0.00. The line item name is annotated "- Applied X Prepay Hours". This is correct behavior — do NOT treat a $0.00 invoice as an error. Verify the deduction by re-fetching `customer.prepay_hours` after invoicing and confirming it dropped by `quantity`.
@@ -84,6 +84,10 @@ When invoked, use the Syncro REST API via `curl`. All requests include `?api_key
Every Syncro API call is attributed to the **owner of the API key**. Comments, line items, timer entries, and invoices created by the API are logged as the API user — regardless of who is running the command. So the skill MUST use a per-user API key that matches the actual tech running it, or comments will be misattributed.
**Preserve attribution on edits — do NOT reassign (Mike 2026-05-27):**
- **Correcting mis-billed labor** (a debug action) must keep the ORIGINAL tech's `user_id` (their commission). `update_line_item` preserves the existing `user_id`; a remove+add defaults the new line to the API-key owner — so set `user_id` to the original tech on `add_line_item`, or PUT it afterward. Determine the original tech from `.ticket.user_id` and the line's `.user_id`. Don't take a tech's commission just because the math was fixed by someone else.
- **Ticket ownership:** adding notes/labor or changing status does NOT change the ticket owner. Multiple techs routinely work one ticket. Only PUT a ticket's `user_id` (reassign owner) when explicitly asked; status PUTs send only `status`.
| identity.json user | Syncro user | user_id |
|---|---|---|
| `mike` | Michael Swanson | 1735 |
@@ -270,7 +274,7 @@ Whether the draft came from Ollama or Claude wrote it directly:
1. `price_retail` was fetched live from `GET /products/<product_id>``.product.price_retail` and matches what will be shown to the user
2. `quantity` = minutes ÷ 60 — verify the arithmetic (e.g. 45 min = 0.75, not 0.77)
3. Computed total = `price_retail × quantity` — matches what was communicated to user
4. If labor_type is `emergency` and `prepay_hours > 0`: product must be `26118`, qty must be actual_hours × 1.5
4. If labor_type is `emergency` and `prepay_hours > 0`: product must be `26184` (emergency item), qty must be actual_hours × 1.5 (premium in the quantity)
5. `comment_body` uses `<br>`, not `<ul>/<li>`
6. No internal notes or credential data in a customer-visible comment body
@@ -659,7 +663,7 @@ RATE=$(curl -s "${BASE}/products/${PRODUCT_ID}?api_key=${API_KEY}" | jq -r '.pro
| `1190473` | Labor - Remote Business | Remote work |
| `26118` | Labor - Onsite Business | Onsite work |
| `573881` | Labor - In Shop Business | Device brought to ACG shop |
| `26184` | Labor - Emergency or After Hours | Non-prepaid emergency only; 1.5× rate baked in |
| `26184` | Labor - Emergency or After Hours | ALL emergency. Non-prepaid: qty=actual hrs, set price_retail by channel (Onsite $262.50, Remote/In-Shop $225). Prepaid: qty=actual hrs×1.5 (rate moot) |
| `1049360` | Labor - Warranty work | Any warranty / no-charge work |
| `9269124` | Labor - Internal Labor | Internal ACG time, not customer-facing |
| `26117` | Fee - Travel Time | Per travel event |
@@ -671,9 +675,9 @@ RATE=$(curl -s "${BASE}/products/${PRODUCT_ID}?api_key=${API_KEY}" | jq -r '.pro
| prepay_hours | Regular | Emergency |
|---|---|---|
| `0` / null | delivery-channel product, qty = actual_hours | `26184`, qty = actual_hours |
| `> 0` | delivery-channel product, qty = actual_hours | delivery-channel product, qty = actual_hours × **1.5** |
| `> 0` | delivery-channel product, qty = actual_hours | `26184`, qty = actual_hours × **1.5** |
Prepaid blocks debit by quantity not dollars — for emergency prepaid, use the normal delivery-channel product at 1.5× qty, not `26184` (which has 1.5× already in the dollar rate).
Emergency = time-and-a-half, applied once. Non-prepaid: the `26184` rate ($262.50) carries the 1.5× in dollars, so qty = actual hours. Prepaid: invoice is $0 (debits by quantity), so the 1.5× goes in the **quantity** — bill `26184` at actual hours × 1.5 (e.g. 1.5 hrs → 2.25). (Updated 2026-05-27, Mike — previously "delivery-channel product ×1.5"; now `26184` so the line stays labeled emergency.)
#### Invoices