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.
Customer / Property NameRequiredFree-text field, not a client picker — see the limitation below.
Property AddressOptionalFree-text.
DescriptionRequiredTextarea. Placeholder copy changes with the selected Case Type.
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." A photo of the cracked head can be attached afterward from 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.

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, so incident photos go here — there is no dedicated photo field on the case itself.

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 New/Edit Case form is a plain text Input with placeholder "e.g. Sterling Storage", not a client picker, 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.
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.
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.