planif: assistant renfort — nom robuste (jamais « undefined »)

jdAddAssistant : dérive le nom du tech chargé OU du libellé de l'option choisie OU l'id (jamais undefined),
coerce tech_id en String → la réservation (bloc hachuré) se crée correctement. Puce : repli techNameById/id/—.
(DB vérifiée : 0 ligne assistant corrompue ; le « undefined » était transitoire à l'ajout.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-07 13:36:43 -04:00
parent 969d5af9f9
commit 8a655f9933

View File

@ -1484,7 +1484,7 @@
<div v-if="jobDetail.canTeam" class="jd-team"> <div v-if="jobDetail.canTeam" class="jd-team">
<div class="text-subtitle2 row items-center q-mb-xs"><q-icon name="group" size="18px" class="q-mr-xs" /> Équipe / renfort<q-space /><q-spinner v-if="jobDetail.teamLoading" size="16px" color="deep-purple" /></div> <div class="text-subtitle2 row items-center q-mb-xs"><q-icon name="group" size="18px" class="q-mr-xs" /> Équipe / renfort<q-space /><q-spinner v-if="jobDetail.teamLoading" size="16px" color="deep-purple" /></div>
<div v-if="jobDetail.team && jobDetail.team.length" class="row items-center q-gutter-xs q-mb-xs"> <div v-if="jobDetail.team && jobDetail.team.length" class="row items-center q-gutter-xs q-mb-xs">
<q-chip v-for="a in jobDetail.team" :key="a.tech_id" dense removable @remove="jdRemoveAssistant(a.tech_id)" color="deep-purple-1" text-color="deep-purple-9" icon="group">{{ a.tech_name || a.tech_id }}</q-chip> <q-chip v-for="a in jobDetail.team" :key="a.tech_id" dense removable @remove="jdRemoveAssistant(a.tech_id)" color="deep-purple-1" text-color="deep-purple-9" icon="group">{{ a.tech_name || techNameById(a.tech_id) || a.tech_id || '' }}</q-chip>
</div> </div>
<div v-else class="text-caption text-grey-6 q-mb-xs">Aucun assistant. Ajoutez un renfort un bloc <b>hachuré</b> sera réservé dans son horaire (anti doublebooking).</div> <div v-else class="text-caption text-grey-6 q-mb-xs">Aucun assistant. Ajoutez un renfort un bloc <b>hachuré</b> sera réservé dans son horaire (anti doublebooking).</div>
<div class="row items-center no-wrap q-gutter-xs"> <div class="row items-center no-wrap q-gutter-xs">
@ -2383,6 +2383,7 @@ const geoTimeline = computed(() => {
}) })
// Options du sélecteur d'assistant (TechSelect, recherche par nom OU compétence) : tous les techs sauf le lead + l'équipe déjà là. // Options du sélecteur d'assistant (TechSelect, recherche par nom OU compétence) : tous les techs sauf le lead + l'équipe déjà là.
// Label = nom · compétences (searchable) ; CAPABLES d'abord (possèdent la compétence du job) ; value = id (scalaire). // Label = nom · compétences (searchable) ; CAPABLES d'abord (possèdent la compétence du job) ; value = id (scalaire).
function techNameById (id) { const t = (techs.value || []).find(x => x.id === id); return t ? t.name : '' } // repli nom d'un assistant depuis l'id
const jdTeamOptions = computed(() => { const jdTeamOptions = computed(() => {
const taken = new Set([jobDetail.techId, ...(jobDetail.team || []).map(a => a.tech_id)]) const taken = new Set([jobDetail.techId, ...(jobDetail.team || []).map(a => a.tech_id)])
const req = jobDetail.required_skill || jobDetail.skill || '' const req = jobDetail.required_skill || jobDetail.skill || ''
@ -2395,13 +2396,16 @@ const jdTeamOptions = computed(() => {
const jdMessages = computed(() => { const m = (jobDetail.thread && jobDetail.thread.messages) || []; return m.slice().reverse() }) const jdMessages = computed(() => { const m = (jobDetail.thread && jobDetail.thread.messages) || []; return m.slice().reverse() })
async function jdAddAssistant () { async function jdAddAssistant () {
const id = jobDetail.teamAdd; if (!id || !jobDetail.name) return const id = jobDetail.teamAdd; if (!id || !jobDetail.name) return
const tech = (techs.value || []).find(t => t.id === id); if (!tech) return const tech = (techs.value || []).find(t => t.id === id)
// Nom robuste : tech chargé, sinon libellé de l'option choisie (« Nom · compétences »), sinon l'id jamais « undefined ».
const opt = (jdTeamOptions.value || []).find(o => o.value === id)
const name = (tech && tech.name) || (opt && String(opt.label || '').split(' · ')[0].trim()) || String(id)
try { try {
await roster.addAssistant(jobDetail.name, { tech_id: tech.id, tech_name: tech.name, duration_h: 0, pinned: 1 }) await roster.addAssistant(jobDetail.name, { tech_id: String(id), tech_name: name, duration_h: 0, pinned: 1 })
jobDetail.teamAdd = null jobDetail.teamAdd = null
const r = await roster.getJobTeam(jobDetail.name); jobDetail.team = r.assistants || [] const r = await roster.getJobTeam(jobDetail.name); jobDetail.team = r.assistants || []
await reloadOccupancy() await reloadOccupancy()
$q.notify({ type: 'positive', icon: 'group_add', message: tech.name + ' ajouté en renfort · bloc hachuré réservé dans son horaire', timeout: 2600 }) $q.notify({ type: 'positive', icon: 'group_add', message: name + ' ajouté en renfort · bloc hachuré réservé dans son horaire', timeout: 2600 })
} catch (e) { err(e) } } catch (e) { err(e) }
} }
async function jdRemoveAssistant (techId) { async function jdRemoveAssistant (techId) {