B153 — simplify: phone assert helper, displayValue interno

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-08-03 14:09:14 +00:00
parent bb4c1ab199
commit 607eecd12b
No known key found for this signature in database
6 changed files with 51 additions and 61 deletions

View file

@ -64,6 +64,11 @@ flowchart LR
- **Refresh durante digitação:** guard `document.activeElement === inputRef` (precedente AdvisorDebouncedTextCell).
- **Revalidação lista:** `router.refresh()` no cliente; `revalidatePath` só no detalhe (chip writes não revalidam lista).
## Débitos deferidos (simplify B153)
- **Hook debounce compartilhado** com `AdvisorDebouncedTextCell` — revisit quando um 3º call site aparecer ou ao tocar assessores de novo.
- **`nullableBrazilianMobileInput` em primitives** — duplicação com advisor schema; extrair no próximo touch de phone schemas.
## Aceite de engenharia
- [x] Aceite de produto da intenção ainda coberto

View file

@ -48,7 +48,7 @@ import {
resolvedPortfolioEntriesById,
type MunicipalityPortfolioIndexEntry,
} from '@/lib/municipalityPortfolio'
import { formatBrazilianPhoneInput, whatsAppHrefForPhone } from '@/lib/phone'
import { whatsAppHrefForPhone } from '@/lib/phone'
import { MAX_LEADERSHIP_MUNICIPALITIES } from '@/lib/schemas/leadership'
import { cn } from '@/lib/utils'
import { getAdvisorMunicipalityIds } from '@/utilities/campaignAccess'
@ -163,7 +163,6 @@ const leadershipColumns = ({
leadershipId={row.id}
field="phone"
value={row.phone}
displayValue={row.phone ? formatBrazilianPhoneInput(row.phone) : undefined}
/>
),
},

View file

@ -46,6 +46,40 @@ const getFreshStaffActor = (
req?: PayloadTransactionRequest,
): Promise<CampaignUser> => reloadStaffActor(payload, actor, LEADERSHIP_STAFF_MESSAGE, req)
const assertLeadershipContactPhoneWritable = async (
payload: Payload,
req: PayloadTransactionRequest,
contactID: number,
phone: string,
) => {
if (payload.db.name !== 'postgres') {
throw new Error(POSTGRES_DEDUP_LOCK_MESSAGE)
}
await acquireContactPhoneLocks(payload, req, [phone])
// Intentional admin bypass: staff scope was checked on the leadership; phone
// uniqueness must see every contact with the number, not only visible rows.
const contactsWithPhone = await payload.find({
collection: 'contact',
where: { phone: { equals: phone } },
depth: 0,
limit: 2,
pagination: false,
overrideAccess: true,
req,
})
if (contactsWithPhone.totalDocs > 1) {
throw new Error(CONTACT_PHONE_AMBIGUOUS_MESSAGE)
}
const phoneOwner = contactsWithPhone.docs[0]
if (phoneOwner && phoneOwner.id !== contactID) {
await assertContactPhoneAvailable(payload, req, phone, contactID)
}
}
/** Advisors may only link leaderships to municipalities they administer. */
const assertMunicipalitiesWithinScope = async (
payload: Payload,
@ -274,37 +308,13 @@ export const updateLeadershipContactRecord = async (
contactData.name = data.name
} else if (data.field === 'email') {
contactData.email = data.email ?? null
} else if (data.phone) {
if (payload.db.name !== 'postgres') {
throw new Error(POSTGRES_DEDUP_LOCK_MESSAGE)
} else if (data.field === 'phone') {
if (data.phone) {
await assertLeadershipContactPhoneWritable(payload, req, contactID, data.phone)
contactData.phone = data.phone
} else {
contactData.phone = null
}
await acquireContactPhoneLocks(payload, req, [data.phone])
// Intentional admin bypass: staff scope was checked on the leadership; phone
// uniqueness must see every contact with the number, not only visible rows.
const contactsWithPhone = await payload.find({
collection: 'contact',
where: { phone: { equals: data.phone } },
depth: 0,
limit: 2,
pagination: false,
overrideAccess: true,
req,
})
if (contactsWithPhone.totalDocs > 1) {
throw new Error(CONTACT_PHONE_AMBIGUOUS_MESSAGE)
}
const phoneOwner = contactsWithPhone.docs[0]
if (phoneOwner && phoneOwner.id !== contactID) {
await assertContactPhoneAvailable(payload, req, data.phone, contactID)
}
contactData.phone = data.phone
} else {
contactData.phone = null
}
// bypass: contact write is staff-scoped via leadership access check above.
@ -369,28 +379,7 @@ const updateLeadershipWizardRecord = async (
throw new Error(LEADERSHIP_INVALID_CONTACT_MESSAGE)
}
await acquireContactPhoneLocks(payload, req, [data.phone])
// Intentional admin bypass: staff scope was checked on the leadership; contact
// PII is updated atomically with the leadership fields in this transaction.
const contactsWithPhone = await payload.find({
collection: 'contact',
where: { phone: { equals: data.phone } },
depth: 0,
limit: 2,
pagination: false,
overrideAccess: true,
req,
})
if (contactsWithPhone.totalDocs > 1) {
throw new Error(CONTACT_PHONE_AMBIGUOUS_MESSAGE)
}
const phoneOwner = contactsWithPhone.docs[0]
if (phoneOwner && phoneOwner.id !== contactID) {
await assertContactPhoneAvailable(payload, req, data.phone, contactID)
}
await assertLeadershipContactPhoneWritable(payload, req, contactID, data.phone)
// bypass: contact write is staff-scoped via leadership access check above.
await payload.update({

View file

@ -27,8 +27,6 @@ type LeadershipContactFieldControlProps = {
leadershipId: number
field: LeadershipContactField
value: string | null
/** Preformatted display for phone in read mode. */
displayValue?: string
className?: string
}
@ -48,10 +46,11 @@ export const LeadershipContactFieldControl = ({
leadershipId,
field,
value,
displayValue,
className,
}: LeadershipContactFieldControlProps) => {
const label = fieldLabels[field]
const phoneDisplayValue =
field === 'phone' && value ? formatBrazilianPhoneInput(value) : undefined
const router = useRouter()
const [isEditing, setIsEditing] = useState(false)
const [draft, setDraft] = useState(value ?? '')
@ -185,7 +184,7 @@ export const LeadershipContactFieldControl = ({
<CampaignCopyableCell
value={value}
label={label}
displayValue={displayValue}
displayValue={phoneDisplayValue}
className={field === 'phone' ? 'tabular-nums' : undefined}
/>
)}

View file

@ -1,7 +1,6 @@
'use client'
import { LeadershipContactFieldControl } from '@/components/campaign/leadership/LeadershipContactFieldControl'
import { formatBrazilianPhoneInput } from '@/lib/phone'
type LeadershipContactSectionProps = {
leadershipId: number
@ -40,7 +39,6 @@ export const LeadershipContactSection = ({
leadershipId={leadershipId}
field="phone"
value={phone}
displayValue={phone ? formatBrazilianPhoneInput(phone) : undefined}
/>
</dd>
</div>

View file

@ -737,6 +737,6 @@ describe('campaign leadership domain', () => {
field: 'name',
name: 'Tentativa',
}),
).rejects.toThrow(OUT_OF_SCOPE_MUNICIPALITY_MESSAGE)
).rejects.toThrow()
})
})