Landscapt (CRM)

Damage Cases

Tracking property damage and warranty claims tied to a job — what a case captures, how cost rolls up, and a real current limitation in how it connects to a client record.

What a damage case is

A Damage Case is a standalone record for tracking either property damage caused during a job (e.g. a mower striking a client's sprinkler head or fence) or a warranty claim (e.g. a plant that died within its warranty period and needs replacement). Both share the same record shape — a caseType field of "damage" or "warranty" — so the feature is really one case-tracking model with two flavors, not two separate features.

It is not attached to a vehicle-accident or fleet-incident workflow — there is no field for at-fault party, insurance carrier, or claim number anywhere in the schema. What it captures is closer to "an incident happened at a customer's property, here's what it cost to make right."

Where cases live

Damage Cases is a Landscapt tool, not a CMMS feature — it's listed in the crew-hidden Tools sidebar as "Track property damage & warranty". It lives at a single route, /tools/damage-cases; the older /dashboard/damage-cases and /dashboards/damage-cases copies now redirect there.

The page itself has two tabs: Cases (a searchable list — by customer, case #, description, or property address) and Reporting (a chart of year-to-date damage vs. warranty cost by month, with YTD summary tiles).

Opening a case

Cases are opened standalone, from the "Open Case" button on the list page — there is no "log damage" action hanging off a Job, Visit, or Ticket record that pre-fills a case. A crew member or office staffer fills out a plain form:

FieldRequired?Notes
Case TypeRequired“Damage” or “Warranty” — a two-value select, defaults to Damage.
Date of IncidentRequiredDate picker.
Find ClientOptionalSearch-and-select that fills the name (and address, when blank) below — the case still stores the name as text, see the limitation below.
WarrantyOptionalWarranty cases only — links the case to one of your active warranties, fills in the customer, and warns when the incident date is outside its coverage. See the Warranties guide.
Customer / Property NameRequiredFree text, so a customer who isn’t a client can be typed directly.
Property AddressOptionalFree-text.
DescriptionRequiredTextarea. Placeholder copy changes with the selected Case Type.
PhotosOptionalNew cases only. Take Photo opens the device camera; Choose Photos picks from the gallery (HEIC photos are converted automatically). They upload to the case right after it is created.
Resolution NotesEdit onlyOnly shown once a case exists — not on initial creation.

A case number is assigned server-side via the next_damage_case_number() RPC. Creation retries up to three times on a unique-constraint collision (two concurrent submissions computing the same number), rather than surfacing a raw DB error.

Worked example. A mower clips a client's sprinkler head while mowing a back yard. The crew lead (or office, once told) opens a new case: Case Type Damage, Date of Incident set to today, Customer / Property Name typed as "Sterling Storage", Description "Mower struck and cracked a sprinkler head on the west lawn near the fence line." The crew lead snaps a photo of the cracked head on the form, and it lands on the case's Files tab (see below). The case is now visible in the list with status Open and $0.00 total cost until an expense is logged.

Reporting from the field

Crew and anyone with Job Photos access can also open a case without going near the Tools menu: Job Photos → Field → Damage Report is a phone-friendly form with the same fields (report type, date, customer / property, address, what happened). Submitting it opens a new case with the same case number, ticket and automation behavior as the form above. The field form also has the Find Client search and a Take Photo / Choose Photos control; if a photo fails to upload, the case is still created and the form says how many photos need to be added by the office. Employee injuries are tracked separately — see Injury & Near-Miss Cases.

Status

Status is a plain four-value field, changed from a dropdown on the case's status badge — any status can be selected from any other at any time; there is no enforced progression or guard rail in the UI or the mutation.

StatusMeaning
OpenDefault status on every new case.
In ProgressBeing worked — no system-enforced meaning beyond the label.
ResolvedHas a dedicated color in the shared StatusBadge palette (teal).
ClosedEnd state.

Expenses & cost tracking

A case's Total Cost is not a field you set directly — it's derived by summing every non-deleted row in damage_case_expenses for that case, both in the list view and the detail panel.

Continuing the sprinkler-head example: the case's Expenses tab gets an entry — Date, an optional Vendor (picked from the shared Vendors table, or typed free-text if the vendor isn't in the system yet), a Description ("Replacement sprinkler head + labor"), and an Amount in dollars, stored as cents. The case detail panel's Total Cost updates immediately to match.

Each expense row also carries a nullable purchaseOrderId, but the Add Expense form always submits it as null — there is no UI control to set it per-expense today. The only PO link that's actually wired up in the UI is the one at the case level, described next.

Beyond expenses, each case has generic Files, Comments, and Audit Trail tabs shared with other record types across the app (AttachmentsSection, CommentsSection, AuditTrailTab). Files accepts images, PDFs, and common office documents. Photos added when the case was opened, or uploaded later, show as a thumbnail Photos strip at the top of the Files tab; click one to open it full size.

Tickets

Every new case automatically opens a ticket for the office to follow up on, whether it was created from the Open Case dialog or the crew's field form. The ticket is Open, normal priority, in the Damage / Warranty category, with the case number, customer, address, incident date and description in its body, and it is linked back to the case. If the customer name matches exactly one client, the ticket is attached to that client and appears on their activity timeline; otherwise it is created without a client.

The case's Tickets tab lists every ticket linked to it (number, subject, status); click one to open it. You can link more tickets yourself: on the ticket, use Linked Records → Add Link, choose Damage Case, and pick the case.

Automations & Zapier

Creating a case fires a damage_case_created event, usable two ways:

  • Landscapt's internal Automations — "Damage case was created" is a selectable trigger, filterable by Case Type.
  • Zapier — listed as the "New Damage Case" trigger, delivered instantly like the rest of Landscapt's triggers. See the Zapier guide for connection details.
The automation only fires if the customer name matches an existing client. Because customerName is free text (see below), case creation does an exact-match lookup against clients.display_name (trimmed, lowercased) purely to decide whether to fire the trigger — nothing is written back onto the case either way. If the typed name doesn't match a client exactly, the automation and Zapier trigger silently never fire for that case. A typo, an abbreviation, or a property name instead of the client's name on file is enough to break this.

The client-linking limitation

damage_cases.customer_name is a plain text column — there is no foreign key to clients.id. This is confirmed still true directly in the code, not just in CLAUDE.md's general note about informal client-name strings: the type definition has a customerName: string field and no clientId, the Find Client picker on the New Case form only copies the client's display name (and address) into the text fields, and case creation has to run an exact-match text lookup against the clients table after the fact just to fire automations — a workaround that only exists because there's no real relationship to query.

Current impact. Damage Cases cannot be filtered or reported on by client the way Jobs, Estimates, or Invoices can — there's no client ID to join on. Two cases for the same client with slightly different typed names ("Sterling Storage" vs. "Sterling Storage LLC") show up as unrelated. And any automation or Zap keyed off a damage case reaching a client's activity timeline depends entirely on the typed name matching the client's display_name exactly. Per CLAUDE.md, migrating this to a real client_id FK is deferred until after the Landscapt dev/prod split — until then, treat the customer name field as a label, not a relationship.

More in Landscapt (CRM)

Clients, Properties & Leads
Client accounts, commercial hierarchies, service properties, and the activity timeline that ties it all together.
Estimates & the Budget Engine
How an estimate is built, how its numbers are actually calculated, and how it becomes a job.
Jobs & Packages
The six job types, how a job's status differs from a visit's status, and how a Package template turns into a billed job.
Services & Pricing
The service catalog, bulk catalog price changes, and Price Adjustment runs — which prices seed new work and which ones actually bill.
The Dispatch Board
The daily scheduling screen crews and dispatchers live in — visits, crews, status, and how actual hours get calculated.
The Crew App
What a crew sees on their phone — the day's stops, clocking on and off, breaks, photos, and sending work back to the office.
Sales Meetings
Booking appointments per sales rep, the double-booking warning, and the split between a rep's direct reminder and the client-facing automation trigger.
The Waiting List
Jobs with a date range instead of a date, held for opportunistic scheduling — and how to actually get them dispatched.
Snow Jobs & Storm Dispatch
Storm events, priority dispatch, and the invoicing flow built specifically for snow.
Invoicing & Payments
How an invoice is born, how its status moves, and where the client's own PO number actually goes.
Contracts
Ongoing billing agreements — monthly amounts, seasonal overrides, sub-properties, and how signing and invoicing actually work.
Communication Automations
How sequences, triggers, and events work — and how Automations differ from Sales Campaigns.
Forms & Lead Capture
Building, publishing, and sharing public forms — and what happens to a submission once it lands.
Document & Email Templates
Building the reusable emails and text messages that go out with invoices, estimates, campaigns and automations — blocks, merge tags, buttons, previews and test sends.
Tickets
Support and service tickets — where they come from, how they're worked, and how they connect to the rest of a client's record.
Warranties
Tracking the warranties you give on walls, walkways, patios and renovations — when each starts and ends, how claims tie back to it, and expiry reminders.
Commissions
Paying sales reps a percentage of what they sell — plans and rules by service and sale type, how earnings are calculated, and locking a quarter into a statement.
Injury & Near-Miss Cases
Reporting injuries, illnesses and near misses from the field, following them up in the office, tracking self-paid costs, supervisor alerts, and the Accident Free Workdays KPI.
Job Photos
Field photo documentation, annotation, and before/after comparisons — attached to a job site, not a person.
Online Payments & Stripe
How a client actually pays an invoice online, how Stripe Connect keeps every org's money in their own account, and how the platform takes zero cut.
Browse all guides
© 2026 Landscapt. All rights reserved.