Proposal
ConnectIQ currently puts the job at the centre of the model. A job is episodic — it opens, gets worked, and closes. Everything valuable around it is not.
This note proposes a persistent, location-anchored record that jobs act on rather than hang off: the ConnectIQ object.
01 · The principle
Every entity in an operations portal falls into one of two groups, and the split is the whole argument.
Persistent things exist independently of any activity. A customer exists whether or not anyone is working for them. A site exists before the first install and after the last removal. A subscription runs on its own clock — it renews, lapses, and cancels without reference to whether a job is open.
Episodic things start, run, and end. A job is a unit of work with a completion date. A ticket is a reported problem that gets resolved and closed.
Putting an episodic entity at the centre forces every persistent entity to depend on something designed to finish. That is why a job-centred model strains the moment a subscription needs to outlive its install, or a customer reports a fault before any job exists.
Read left to right, the model is also the project pipeline. A lead converts into a customer and a site; a lead that closes becomes a quote; a signed quote becomes the object. Each arrow is a commitment gate, and only the last one brings the operational core into existence — everything to the right of it is what then happens to a real deployment.
The dashed box in Fig 1 is deliberate. The lead pipeline, the customer, and the location form one bounded context — the Accounts domain, the “who and where.” The object and everything hanging off it — inventory, subscriptions, tickets, jobs — are a different context, the Deployment domain, the “what is out there and what happens to it.” They change for different reasons and on different cadences: account details shift for CRM reasons, deployments shift for operational ones.
Drawing the boundary buys a clean ownership line for a small team, but it only holds if the two sides stay loosely coupled. The test is how many references cross the boundary — and in this model there is essentially just one.
Practically, the object stores a location_id and treats the Accounts domain as the owner of that record — it reads location data through the Accounts boundary rather than reaching into its tables. For a two-developer team this is a bounded context inside a modular monolith, not a literal microservice: you get the same separation and ownership clarity without standing up separate services and the network, deploy, and data-consistency overhead that comes with them.
02 · Prior art
The proposal isn’t a bespoke invention. When we checked it against the established field-service and installed-base platforms, the same separation showed up at every tier of the market — a persistent, location-bound record that work acts on, distinct from the work itself. The ConnectIQ object is our name for that record.
The clearest reference to mirror. A first-class Asset object, separate from the Work Order. Work Orders reference an Asset and a Location; entitlements and contracts — their subscription equivalent — attach to the Asset, not the work. Assets also nest parent-to-child, which is exactly our “one object, many hardware components” case.
Customer, Location, Equipment and Job are separate records, not nested inside one another. Jobs get equipment attached to them; the equipment record is the thing that accumulates a full service history over time — the same reason we keep placements against the object rather than the job.
Installed equipment — help.servicetitan.comThe most doctrinaire version: it markets itself as asset-centric field service, built around the full asset lifecycle, and treats accurate installed-base data as the thing the whole service strategy depends on. Overkill for us operationally, but it confirms the direction at the far end of the spectrum.
Installed base management — ptc.comThe useful counterpoint. Jobber deliberately has no strong equipment object — reviewers note it tracks materials used on a job and stops there. It stays simple precisely by not solving the installed-base problem. That’s the honest trade: the object model is worth its weight only because we have durable equipment with subscriptions and recurring service on top.
Product features — getjobber.comSubscriptions and recurring service put us in ServiceTitan / Salesforce territory, not Jobber’s. That’s the tell that the object is worth the extra table rather than a premature abstraction — the businesses that skip it don’t carry an installed base the way we do.
03 · Object vs location
The object is anchored to exactly one location and never moves, which makes it feel like it could simply be the location. It can’t. Two ordinary business states become unrepresentable the moment you merge them.
A third reason matters for the ticket queue. A customer can raise a ticket about a site before anything is installed there — a survey request, a quote chase, an access problem. That ticket needs a location to attach to, and no object exists yet. Keeping the two separate gives it somewhere to live.
The line worth repeating in review: location answers where, the object answers what is deployed there.
04 · Creation
Not when the quote is drafted, and not when the installer finishes. Confirmation is the first moment the deployment is both real and fully specified — someone has committed to it, and the quote already says what goes where.
Quote drafted. Speculative — most quotes never convert.
Quote confirmed. Object exists with zero hardware attached.
Install job complete. Hardware placed, subscription running.
Subscription lapsed or service paused. Hardware still on site.
Removed. Hardware unlinked, record kept for history.
Creating an object at quote drafting fills the installed base with things that will never exist. Quote conversion is well under 100%, and every unconverted quote leaves a ghost object that pollutes counts, dashboards, and subscription reporting. The quote line is already a fine record of a proposed deployment; it doesn’t need a second one.
This is the more important half. If the object isn’t created until the install completes, then the install job has nothing to reference. You would have to let jobs float unattached and retro-link them, or point the job at the quote — which rebuilds exactly the job-centred coupling this model is trying to remove.
Creating at confirmation means the install job is born with a target. It is a job against object CIQ-00418 from the moment it is scheduled, which is what makes the job history on that object complete rather than starting one entry late.
At the moment of creation, the object takes a direct, permanent location_id copied from its quote line. From that instant the object knows its site on its own: one object, one location, set once. No query ever walks through the quote to find where an object lives, so the quote’s later lifecycle — completed, archived, superseded — breaks nothing. quote_line_id stays on the object purely as origin and audit.
This is what keeps the model honest years later. A maintenance quote or a maintenance job doesn’t create or redefine anything — it simply references the existing object, the same as any other work against it. The customer, likewise, is reached through the location (object → location → customer) rather than a second direct link, so there is exactly one answer to “whose site is this” — while the paying party is carried by the active subscription, which traces to whoever signed its quote. Those are usually the same customer, and when a site changes hands they legitimately differ until the new owner signs.
An object in pending_install has no inventory placements at all. That is a valid state, not a data error — validation and UI both need to allow it. An object with no hardware is a commitment that hasn’t been fulfilled yet.
It also needs a terminal state for deals that die after signature. A confirmed quote cancelled before install should move the object to cancelled rather than delete it, so the commercial history survives.
05 · Change of owner
When a location changes hands, the wrong move is creating a new location record. Our objects carry an immutable location_id, so a fresh record would force migrating every object at the site, split the address’s job and ticket history across two rows, and duplicate the address itself. The location is the physical truth; owners come and go on top of it.
This is also the established industry shape. Utilities separate the premise — the permanent physical service point — from the account, the commercial party currently attached to it; a building being sold never changes the meter point’s identity. Salesforce models the same idea as an association between Account and Location carrying active-from and active-to dates. Both are the same answer: the site keeps a dated history of owners.
Which makes this the third appearance of a pattern we’ve already adopted twice: hardware occupies an object for a dated period, and now a customer occupies a location for a dated period. Same table shape, same rules — one open period at a time, close-then-open in a single transaction, history never deleted.
A change of owner touches the model in a strict order. First, close the old ownership period and open the new one, effective-dated, in one transaction. Nothing on the object changes — location_id is immutable and placements are untouched. The old owner’s subscription then runs to its own termination or transfer, and the new owner signs a new quote, which produces a new subscription against the same object. That is the “follows a new quote” flow: the quote carries the new commercial relationship to an object that never moved.
The window in between — site owned by the new party, active subscription still the old one’s — is a legitimate, representable state, and the portal should display it as a deliberate handover rather than a discrepancy. One query nuance follows: object → location → owner now means the current owner, while historical questions (“who owned this site when that job ran”) resolve against the dated rows. Warranty disputes and billing audits will want the second form.
Keep locations.customer_id as a denormalised pointer to the current owner if reads want it, but location_ownerships is the source of truth — the pointer is written in the same transaction as the ownership rows, never on its own. Same rule as placements.
06 · Fan-out
A quotation is a commercial document scoped to a customer. A ConnectIQ object is an operational record scoped to a site. These two things don’t sit at the same grain, and confirmation is where the translation happens.
The unit that carries the mapping is the quote line. Each line names a location and a system; on confirmation, each line that represents a deployable system instantiates one object.
The sentence above assumes one line, one location. The CRM does not work that way. A quote line carries a quantity and is assigned across any number of locations, each with its own quantity — fifty players spread over thirty sites is one line, thirty assignments. So “each line instantiates one object” gives the wrong answer as soon as a rollout is larger than a single site.
The three cases below still hold; what changes is the thing objects are counted from. It is the line-to-location assignment, not the line. How many objects each assignment produces is deferred to S1 — see the delivery plan’s register.
Case C spawns at least three install jobs, because a job is a site visit and someone travels to each address. Case B is the interesting one: a single visit commissions two objects.
That forces a decision. If one visit routinely commissions more than one system, a plain job→object link won’t hold it, and you need either one job per object (grouped under a shared appointment) or job lines that each target an object — the same shape as quote lines. Salesforce Field Service takes the second route: the work order sits at the location, and its line items target individual assets.
Don’t resolve this until we know how often case B actually occurs. If multi-system sites are rare, one job per object is simpler and costs nothing. If they’re common, job lines mirroring quote lines are worth the extra table — and the symmetry makes the model easier to explain, not harder.
The same deferral now covers the object grain itself. Whether an assignment yields one object per site, one per assignment, or one per system per site depends on a concept the CRM does not yet carry — nothing in a quote line says which product anchors a system. Both questions want the same evidence, and both are answered at S1.
07 · Hardware
Because the object is fixed to its site and carries the subscription, hardware underneath it can be swapped freely without disturbing anything commercial. A failed unit is pulled, a replacement goes in, and the subscription never notices.
This is the same pattern aviation and telecom asset systems use — a fixed position holds a swappable serialised part, and each occupies the position for a dated period. The position’s history stays continuous; the part carries its own separate history from site to site.
Replacing a unit closes the outgoing placement by setting unlinked_at, and opens a new one for the incoming unit — both stamped with the same job_id. That job becomes the single answer to why the change happened and who performed it.
Two rules keep this honest. An item may hold only one open placement at a time, so it can never appear installed in two places; and retired units keep their placement rows, because that history is exactly what a warranty claim needs.
Not every device in an object is ours. A subscription can run on hardware the customer provided, so every inventory item carries an owned_by flag — company or customer. The placement mechanics are identical either way, but the flag changes the rules around them: a customer-owned unit is never pulled back to our stock or redeployed to another site, warranty responsibility sits with them, and at decommission it stays behind while our units return to inventory. If a subscription is ever bound to a specific serial — a licence or SIM tied to the hardware — that binding lives on the placement, so a swap-job moves it in the same transaction and the subscription’s anchor to the object never wavers.
08 · Schema
Grouped by domain. Identifiers and foreign keys are shown in mono.
Nothing episodic owns anything persistent. Jobs, tickets, and placements may reference the object, the location, or an item — never the reverse. A persistent record must never carry a foreign key to an episodic one, because that would tie something built to last to something built to end. The Deployment tables also hold at most one reference back across the Accounts boundary.
Keep a denormalised current_placement pointer if reads need it, but treat inventory_placements as the only source of truth. The pointer is written in the same transaction as the placement row, never on its own.