Objective
- Allow service modifiers to optionally run only when that service's charge line is the primary service on the job
- Prevent modifier stacking when the same modifier type (e.g. Requested Inspector) is configured on multiple services on one inspection
- Give companies a first-class setting instead of workarounds like attaching a modifier only to the home inspection service
Background
- Modifiers are evaluated per service/charge line during scheduling, quote pricing, workorder recalculation, and payroll admin/flat-rate adjustments
- The Requested Inspector modifier uses a job-level flag (
_inspectorRequestedIds/ inspector-request star) but applies independently to every service on the job that has the modifier configured — so a $75 requested fee on Home Inspection and a $25 fee on Radon both fire on the same job isPrimaryis already computed per charge line in pricing (calculateServicePrices,recalculateChargesForRequiredInfo) and passed intomodifierPriceAdjFn, but individual modifiers do not currently gate on it- A similar primary only pattern already exists elsewhere in service settings (required reports and agreement templates use
primaryOnlywith tooltip copy: "Only triggers if the service is a primary service")
Product Decisions
Locked
- Semantics —
primaryOnlymeans: skip the modifier when this service's charge line is not primary on the job. It is not a "once per job" deduplication flag — if Radon is booked standalone and is primary, a Radon modifier withprimaryOnlystill applies - Default — Existing modifiers behave as today (
primaryOnlyfalse / unset). No automatic migration of live modifier configs - Immediate use case — Requested Inspector fee should be configurable to apply only on the primary service line, avoiding duplicate upcharges when add-ons share the job
- Modifier type scope —
primaryOnlyis available on all modifier types (increment, bracket, compare, enum, zipcodes, requested, conditional) - UI placement — Engineering chooses shared vs per-form placement, as long as the modifier settings UI stays consistent and the new toggle is accommodated cleanly; tooltip copy should match existing
primaryOnlylanguage on required reports ("Only triggers if the service is a primary service")
Open
None — all product decisions resolved.
Scope
Backend (attik-backend)
src/models/modifierSchema.ts— add optionalprimaryOnlyonmodiferData; CRUD viasrc/routes/modifier.tssrc/util/functions/schedule/modifierPriceCalc.ts—calculateModifierPricemust acceptisPrimary(or equivalent) and skip modifiers whenprimaryOnlyis true and the line is not primary; apply gating for all modifier types; mirror existingaddType: 'skip'pattern for missing required-info fieldssrc/util/functions/payroll/payrollContext.ts—applyModifiersToInspectionsBatchrecalculates modifiers per charge but does not passisPrimarytoday; wirecharge.isPrimaryso payroll respects the flagsrc/routes/aiEndpoints.ts— any scheduler/AI pricing paths callingcalculateModifierPriceshould pass primary context consistently
Frontend (attik-frontend)
src/util/types/serverTypeCollection/services.ts— extendModifierSchema/modiferDatawithprimaryOnlysrc/util/functions/data/modifierPriceAdjFn.ts— gate all modifier types usingservice.isPrimary(already on the function signature)src/util/functions/schedulingHelpers/calculateServicePrices.tsandrecalculateChargesForRequiredInfo.ts— already passisPrimary; verify no regressions once gating is added- Service settings UI under
src/app/tools/settings/services/[id]/Modifier/— addprimaryOnlytoggle on all modifier forms (shared viaModifierHeader.tsxor equivalent consistent pattern); followServiceRequiredReportsModal.tsxfor tooltip copy
Out of scope (unless expanded)
- Job-level "apply this modifier once total" deduplication across different services
- Changing how primary service is assigned (
canBePrimary, first eligible service inusePriceCalculation)
References
- Modifier pricing (frontend):
src/util/functions/data/modifierPriceAdjFn.ts,src/util/functions/schedulingHelpers/calculateServicePrices.ts - Modifier pricing (backend):
src/util/functions/schedule/modifierPriceCalc.ts - Requested inspector UI:
src/app/tools/settings/services/[id]/Modifier/Requested.tsx - Existing
primaryOnlyprecedent:src/app/tools/settings/services/[id]/components/ServiceRequiredReportsModal.tsx