The builder does not change when the house sells

Whether a garage door call is warranty or billable depends on who bought the door, not who owns the house. One extra lookup on Location is what makes that answerable — and one custom warranty date field that had to be deleted the next day.

A technician arrives at a house with a garage door that will not close. Before anything else, dispatch needs to know one thing: is this a warranty call or a billable one?

At Doorvana that question has a non-obvious answer, and it drove a data model decision I want to write down because I think it generalises to every home services company that touches new construction.

The situation the model has to survive

A builder puts up a house and buys the garage door. Eighteen months later the house sells. The homeowner calls about the door.

Who is the customer? The homeowner — they own the house, they made the call, they are who the technician meets. Who bought the door? The builder. Whose warranty applies? The builder’s purchase.

If the Location carries a single Account lookup, the moment the house sells, one of those two facts gets overwritten. Point it at the homeowner and the warranty trail is gone. Leave it on the builder and you cannot bill anybody.

Two lookups, not one

force-app/main/default/objects/Location/fields/Builder_Account__c.field-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Builder_Account__c</fullName>
    <label>Builder Account</label>
    <description>The builder that originally bought work at this property.
        Deliberately separate from the property's current owner — the builder
        does not change when the house sells, and that separation is what makes
        a warranty-versus-billable decision possible for a homeowner living in a
        builder-supplied home.</description>
    <type>Lookup</type>
    <referenceTo>Account</referenceTo>
    <relationshipLabel>Builder Properties</relationshipLabel>
    <relationshipName>Builder_Locations</relationshipName>
    <deleteConstraint>SetNull</deleteConstraint>
</CustomField>

One extra lookup. That is the whole fix, and it is worth the field because the alternative is a question nobody can answer at the moment they need it.

Warranty dates are not a formula, and they are not a field either

The tempting next move is Warranty_End_Date__c = InstallDate + 365. New construction warranties are a year, the data is already there, and a formula field costs nothing to maintain.

It is wrong the moment you look at what is actually installed.

A new construction warranty might be a year. The door manufacturer’s warranty on panels and springs runs on its own terms. The operator manufacturer warrants the motor on different terms again, often longer. Parts carry their own. A formula returns a confident, precise, wrong answer for every item that is not the default case — and a wrong warranty date does not fail loudly. It gets quoted to a customer.

So I made Asset.Warranty_End_Date__c a plain Date rather than a formula. Someone or something sets it. More work at data entry, honest about the fact that the answer varies. I wrote that down in the build note, including the phrase “no shadowing”, and moved on.

It was shadowing. Not a field — an entire object model I had not described.

WarrantyTerm and AssetWarranty already exist

The next day’s work was a diagnosis checklist for a home-warranty network, which wanted parts and labor guarantee durations on every diagnosis. Before writing any fields I described WorkOrderLineItem, WorkOrder, Asset, ProductConsumed, AssetWarranty and WarrantyTerm against the org. Two of those I had never opened.

WarrantyTerm defines a coverage term. Parts, labor and expenses each get their own duration and their own unit of time — a term is not one number. AssetWarranty applies a term to an individual Asset and stamps the resulting end dates separately:

the standard warranty chain, queried

SELECT Id, AssetId, WarrantyTermId,
       PartsCoveredEndDate, LaborCoveredEndDate, ExpensesCoveredEndDate,
       WarrantyTerm.WarrantyDuration, WarrantyTerm.WarrantyUnitOfTime
FROM AssetWarranty
WHERE AssetId = :assetId

And one Asset can carry several AssetWarranty records. Which is precisely the problem I had written two paragraphs arguing was real — manufacturer and part warranties differ by brand and by component — and then answered with a single date field that cannot express any of it.

Warranty_End_Date__c did not shadow a field on Asset. Nothing on Asset is called that. It shadowed AssetWarranty, an object, reachable from WorkOrderLineItem through the standard AssetWarrantyId lookup that Field Service already reads.

The field was removed the same day it was found: a destructiveChangesPre.xml deployment, validated first with RunLocalTests (10 of 10 passing), then executed against the sandbox. It had never reached production, so nothing was lost except the build note that claimed it was clean. Removing a custom field is cheap exactly once — before real data exists in it. That window is measured in days.

The door and the opener are different products

Door_Brand__c and Operator_Brand__c are two restricted picklists, not one.

This sounds like a detail until you picture the opening: a door from one manufacturer, an opener from another, bolted to the same header. That is not an edge case, it is routine. A single Brand field cannot describe the installation, so parts lookup goes wrong, warranty tracking goes wrong, and the technician ends up phoning the office.

Restricted, both of them — an unrestricted brand picklist accumulates “LiftMaster”, “Lift Master” and “liftmaster” within a quarter, and then no report on brand means anything.

Door_Material__c is a third, for the same reason: steel, wood, composite and glass behave differently enough that a technician wants it on the record before arriving.

Check the standard fields first

Before adding any of this I listed what Asset already ships with:

InstallDate · PurchaseDate · UsageEndDate · SerialNumber · Status · ManufactureDate · StockKeepingUnit

All present, all standard, no custom duplicates. This is a five-minute check that prevents a specific, expensive mistake: creating Install_Date__c next to a standard InstallDate splits the data across two fields, and anything reading the standard one — including parts of Field Service — quietly gets nothing.

It is also the check that passed while Warranty_End_Date__c was being added, because Asset has no standard warranty end date for it to sit next to. The field list is worth reading and it is not sufficient on its own.

One field did survive the check honestly. Asset has no standard model number, and ProductCode only exists when the Asset is tied to a Product2 — which a door somebody else installed never is:

force-app/main/default/objects/Asset/fields/Model_Number__c.field-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>Model_Number__c</fullName>
    <label>Model Number</label>
    <description>The manufacturer's model number as read off the door or operator.
        Asset has no standard model field, and ProductCode only exists when the Asset
        is tied to a Product2; most doors serviced here were installed by someone else
        and have no Product2.</description>
    <inlineHelpText>Model number from the door or operator label.</inlineHelpText>
    <type>Text</type>
    <length>80</length>
</CustomField>

The Location type nobody had turned on

One more, from the same pass, and it is a good example of an assumption worth testing.

LocationType on the standard Location object had exactly one active value: Virtual. A physical property could not be typed as a site, because the value did not exist.

Added: Site (default), Warehouse, Plant, Van.

Warehouse is not filler. Doorvana holds no inventory — technicians collect product from vendor locations on the way to a job. Those pickup points are real places that appear in a technician’s day and belong in the system as Location records. A model that only knows about customer sites cannot describe the actual route.

What this buys at the dispatch board

The house has a Location. The Location knows its current owner and, separately, the builder who bought the original work. The door and opener are Assets on that Location with their own brands, and their coverage hangs off AssetWarranty records with PartsCoveredEndDate and LaborCoveredEndDate set from whichever WarrantyTerm applies — one per component, because the door and the operator were never warranted on the same terms.

So when the call comes in, “is this warranty or billable” is a lookup rather than a phone call to whoever remembers the subdivision. That is the return on one extra lookup field, a decision not to write a formula, and a custom field deleted the day after it was built.

Filed under

Disclosure: Doorvana's owner is also a co-owner of Nocona Consulting. This is a real engagement between two separate companies, but not an arm's-length one — and not an independent reference. More on the work page.

FAQ

How should a home services company model warranty coverage in Salesforce?

Keep the party who bought the work separate from the party who owns the property today. On a Location, an owner lookup and a separate builder lookup answer different questions: who to bill, and whose warranty applies. Collapsing both into a single Account lookup means the original purchaser is overwritten the first time the property changes hands, and warranty eligibility becomes unanswerable.

Where should a warranty end date be stored on a Salesforce Asset?

On AssetWarranty, not on a custom field. Salesforce ships a warranty model most orgs never look at: WarrantyTerm defines a coverage term with separate durations for parts, labor and expenses, and AssetWarranty applies a term to an individual Asset and stamps PartsCoveredEndDate, LaborCoveredEndDate and ExpensesCoveredEndDate. One Asset can carry several AssetWarranty records, which is what a door and its operator under different manufacturer terms actually require. A custom Warranty_End_Date__c on Asset duplicates that model with a single date and cannot express it.

Should a warranty end date be a formula on install date?

No. A formula such as install date plus 365 days is right only while every warranty term is identical. Manufacturer and part warranties differ by brand and by component, so a hardcoded formula returns a confident wrong answer for every item that does not match the assumption. The variation is the reason to use WarrantyTerm and AssetWarranty, which model parts and labor coverage separately, rather than to replace the formula with a plain custom date field that flattens the same variation in a quieter way.

Should door brand and opener brand be the same field in Salesforce?

No. A garage door and its operator are routinely different manufacturers on the same opening, so a single Brand field cannot describe the installation. Two restricted picklists — one for the door, one for the operator — keep parts lookup and warranty tracking accurate for each component.

What standard fields does the Salesforce Asset object already have?

Asset ships with InstallDate, PurchaseDate, UsageEndDate, SerialNumber, Status, ManufactureDate and StockKeepingUnit, among others. Check the standard field list before adding custom equivalents — shadowing a standard field with a custom one splits the data across two places and breaks anything that reads the standard one, including parts of Field Service.

Can a custom field shadow something other than a standard field?

Yes, and that case is harder to catch. A field list comparison only detects a custom field sitting next to a standard field of the same meaning on the same object. It cannot detect a custom field that duplicates a standard object — a single date field standing in for a related record with its own term, its own durations and its own separate end dates. Describe the related standard objects, not only the object's own fields, before deciding a custom field is needed.

Working on something like this?

I can tell you what it takes.

If you run a home services business and this looks like a problem you have, book a call and we will talk through it.