Files
claudetools/.claude/memory/feedback_syncro_billing.md
Howard Enos 405832d049 sync: auto-sync from GURU-BEAST-ROG at 2026-06-23 15:56:27
Author: Mike Swanson
Machine: GURU-BEAST-ROG
Timestamp: 2026-06-23 15:56:27
2026-06-23 15:56:46 -07:00

7.1 KiB
Raw Blame History

name, description, metadata
name description metadata
Syncro billing rules — products, rates, taxes, attribution, emergency, warranty 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.
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.

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. API key follows the BILLING TECH — always

Attribution is determined by which API key you use. Every add_line_item / remove_line_item call is logged as the owner of that key. user_id in the payload does NOT override this.

Common-sense defaults (confirmed by Howard 2026-06-23):

  • Howard asks for billing → use Howard's key (he's billing himself)
  • Mike asks for billing → use Mike's key
  • Told "put X hours in for [tech]" → use that tech's key, regardless of who is asking
  • Split ticket ("2 hrs for Mike, 1 hr for Howard") → two separate add_line_item calls, each with the correct tech's key

Vault paths:

  • Howard → msp-tools/syncro-howard.sops.yamlcredentials.credential
  • Mike → msp-tools/syncro.sops.yamlcredentials.credential
HOWARD_KEY=$(bash .claude/scripts/vault.sh get-field msp-tools/syncro-howard credentials.credential)
MIKE_KEY=$(bash .claude/scripts/vault.sh get-field msp-tools/syncro credentials.credential)

# Each line item call uses the BILLING TECH's key as a query param:
curl -s -X POST "https://computerguru.syncromsp.com/api/v1/tickets/{id}/add_line_item?api_key=${HOWARD_KEY}" ...
curl -s -X POST "https://computerguru.syncromsp.com/api/v1/tickets/{id}/add_line_item?api_key=${MIKE_KEY}" ...

Auth note: ?api_key= is the attribution mechanism. The Authorization: <key> header works for reads but does NOT control line-item attribution — always use ?api_key= for billing writes.

Corrections: wrong key used → remove_line_item with any key (doesn't matter), then re-add_line_item with the correct tech's key. update_line_item does NOT fix user_id.

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.

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.