Fix: mapeo employee_id→user_id + status_name de KAPPA + filtro asignado solo desarrolladores asignados
- KappaUserStory: status_name, status/priority aceptan number - parseAssignedUser: separa employee_id (asignado_a) de user_id (assigned_to) employee_id se guarda negativo en Turso para distinguirlo - enrichHU: usa status_name de KAPPA directo, fallback a resolveStatusName - DashboardView: resolveEmployeeToUser() busca employee→user via employees store - assignedName: muestra nombre real desde employee→user, fallback asignado_a_names - Filtros: placeholder Estado/Prioridad/Asignado en vez de Todos - assignedUsers: filtro solo muestra desarrolladores asignados a HUs del proyecto
This commit is contained in:
@@ -17,8 +17,11 @@ const STATUSES: StatusDef[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const STATUS_MAP: Record<string, number> = {
|
const STATUS_MAP: Record<string, number> = {
|
||||||
|
'1': 1,
|
||||||
'todo': 1,
|
'todo': 1,
|
||||||
'por hacer': 1,
|
'por hacer': 1,
|
||||||
|
'not started': 1,
|
||||||
|
'2': 2,
|
||||||
'in_progress': 2,
|
'in_progress': 2,
|
||||||
'doing': 2,
|
'doing': 2,
|
||||||
'wip': 2,
|
'wip': 2,
|
||||||
@@ -26,16 +29,25 @@ const STATUS_MAP: Record<string, number> = {
|
|||||||
'in progress': 2,
|
'in progress': 2,
|
||||||
'en progreso': 2,
|
'en progreso': 2,
|
||||||
'true': 2,
|
'true': 2,
|
||||||
|
'3': 3,
|
||||||
'review': 3,
|
'review': 3,
|
||||||
'revisión': 3,
|
'revisión': 3,
|
||||||
|
'qa-ready': 3,
|
||||||
|
'4': 4,
|
||||||
'testing': 4,
|
'testing': 4,
|
||||||
'pruebas': 4,
|
'pruebas': 4,
|
||||||
|
'qa-testing': 4,
|
||||||
|
'5': 5,
|
||||||
'done': 5,
|
'done': 5,
|
||||||
'completed': 5,
|
'completed': 5,
|
||||||
'closed': 5,
|
'closed': 5,
|
||||||
'finalizado': 5,
|
'finalizado': 5,
|
||||||
|
'6': 6,
|
||||||
'blocked': 6,
|
'blocked': 6,
|
||||||
'bloqueado': 6,
|
'bloqueado': 6,
|
||||||
|
'ready to deploy': 6,
|
||||||
|
'qa-client': 6,
|
||||||
|
'7': 7,
|
||||||
'cancelled': 7,
|
'cancelled': 7,
|
||||||
'cancelado': 7,
|
'cancelado': 7,
|
||||||
'false': 1,
|
'false': 1,
|
||||||
|
|||||||
+25
-14
@@ -18,6 +18,7 @@ export interface EnrichedUserStory extends KappaUserStory {
|
|||||||
_assignedUserId: number | null
|
_assignedUserId: number | null
|
||||||
_assignedName: string
|
_assignedName: string
|
||||||
_statusName: string
|
_statusName: string
|
||||||
|
_assignedEmployeeId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnrichedEpic extends KappaEpicDevelopment {
|
export interface EnrichedEpic extends KappaEpicDevelopment {
|
||||||
@@ -63,29 +64,36 @@ export const useWorkItemsStore = defineStore('workitems', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAssignedUser(hu: any): { id: number | null; name: string } {
|
function parseAssignedUser(hu: any): { id: number | null; name: string; employeeId: number | null } {
|
||||||
// Intenta múltiples formatos que KAPPA puede devolver
|
// 1. asignado_a → EMPLOYEE IDs (ej: [1135]). NO son user_ids.
|
||||||
if (hu.assigned_to != null && hu.assigned_to !== '') {
|
|
||||||
const id = Number(hu.assigned_to)
|
|
||||||
if (!isNaN(id)) return { id, name: hu.assigned_name || '' }
|
|
||||||
}
|
|
||||||
if (hu.asignado_a != null) {
|
if (hu.asignado_a != null) {
|
||||||
if (Array.isArray(hu.asignado_a)) {
|
if (Array.isArray(hu.asignado_a)) {
|
||||||
const first = hu.asignado_a[0]
|
const first = hu.asignado_a[0]
|
||||||
if (first != null) {
|
if (first != null) {
|
||||||
const id = typeof first === 'object' ? Number(first.id) : Number(first)
|
const employeeId = typeof first === 'object' ? Number(first.id) : Number(first)
|
||||||
if (!isNaN(id)) return { id, name: typeof first === 'object' ? first.name || first.full_name || '' : '' }
|
if (!isNaN(employeeId)) {
|
||||||
|
const name = Array.isArray(hu.asignado_a_names) ? (hu.asignado_a_names[0] ?? '') : (hu.asignado_a_names ?? '')
|
||||||
|
return { id: null, name, employeeId }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { id: null, name: '' }
|
// 2. assigned_to con valor negativo → employee_id (convención Turso)
|
||||||
|
if (hu.assigned_to != null && hu.assigned_to !== '') {
|
||||||
|
const raw = Number(hu.assigned_to)
|
||||||
|
if (!isNaN(raw)) {
|
||||||
|
if (raw < 0) return { id: null, name: '', employeeId: Math.abs(raw) }
|
||||||
|
return { id: raw, name: hu.assigned_name || '', employeeId: null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { id: null, name: '', employeeId: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
function enrichHU(hu: { title?: string; id?: number; status?: string | null; acceptance_criteria?: string | null; criterios_aceptacion?: string | null; assigned_to?: number | null; asignado_a?: number[] | string[] | null; assigned_name?: string }, initiativeId: number): EnrichedUserStory {
|
function enrichHU(hu: { title?: string; id?: number; status?: string | number | null; status_name?: string | null; acceptance_criteria?: string | null; criterios_aceptacion?: string | null; assigned_to?: number | null; asignado_a?: number[] | string[] | null; asignado_a_names?: string[] | string | null; assigned_name?: string }, initiativeId: number): EnrichedUserStory {
|
||||||
const h = parseHierarchy(hu.title || '')
|
const h = parseHierarchy(hu.title || '')
|
||||||
const rawCriteria = hu.acceptance_criteria || hu.criterios_aceptacion || ''
|
const rawCriteria = hu.acceptance_criteria || hu.criterios_aceptacion || ''
|
||||||
const criteriaList = rawCriteria ? parseQuillList(rawCriteria) : []
|
const criteriaList = rawCriteria ? parseQuillList(rawCriteria) : []
|
||||||
const { id: assignedUserId, name: assignedName } = parseAssignedUser(hu)
|
const { id: assignedUserId, name: assignedName, employeeId } = parseAssignedUser(hu)
|
||||||
return {
|
return {
|
||||||
id: hu.id ?? 0,
|
id: hu.id ?? 0,
|
||||||
title: hu.title || '',
|
title: hu.title || '',
|
||||||
@@ -99,7 +107,8 @@ export const useWorkItemsStore = defineStore('workitems', () => {
|
|||||||
has_impairment: false,
|
has_impairment: false,
|
||||||
_assignedUserId: assignedUserId,
|
_assignedUserId: assignedUserId,
|
||||||
_assignedName: assignedName,
|
_assignedName: assignedName,
|
||||||
_statusName: resolveStatusName(hu.status),
|
_statusName: hu.status_name || resolveStatusName(hu.status),
|
||||||
|
_assignedEmployeeId: employeeId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +219,9 @@ export const useWorkItemsStore = defineStore('workitems', () => {
|
|||||||
hasImpairment = impairments.some(p => !p.status)
|
hasImpairment = impairments.some(p => !p.status)
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
const { id: assignedUserId } = parseAssignedUser(hu)
|
const { id: assignedUserId, employeeId } = parseAssignedUser(hu)
|
||||||
|
// Convención: employee_id se guarda como negativo para distinguirlo de user_id
|
||||||
|
const assignedTo = employeeId != null ? -employeeId : assignedUserId
|
||||||
|
|
||||||
await tauriDb.saveUserStory({
|
await tauriDb.saveUserStory({
|
||||||
id: huId,
|
id: huId,
|
||||||
@@ -225,7 +236,7 @@ export const useWorkItemsStore = defineStore('workitems', () => {
|
|||||||
story_points: hu.story_points ?? null,
|
story_points: hu.story_points ?? null,
|
||||||
estimated_hours: null,
|
estimated_hours: null,
|
||||||
actual_hours: null,
|
actual_hours: null,
|
||||||
assigned_to: assignedUserId,
|
assigned_to: assignedTo,
|
||||||
sprint: safeStr(hu.sprint),
|
sprint: safeStr(hu.sprint),
|
||||||
has_impairment: hasImpairment,
|
has_impairment: hasImpairment,
|
||||||
item_type: null,
|
item_type: null,
|
||||||
|
|||||||
+3
-2
@@ -43,8 +43,9 @@ export interface KappaUserStory {
|
|||||||
description?: string
|
description?: string
|
||||||
acceptance_criteria?: string
|
acceptance_criteria?: string
|
||||||
criterios_aceptacion?: string
|
criterios_aceptacion?: string
|
||||||
status?: string
|
status?: string | number
|
||||||
priority?: string
|
status_name?: string
|
||||||
|
priority?: string | number
|
||||||
initiative: number | string
|
initiative: number | string
|
||||||
story_points?: number
|
story_points?: number
|
||||||
sprint?: string
|
sprint?: string
|
||||||
|
|||||||
+57
-29
@@ -51,21 +51,19 @@ const projectTeam = computed(() => {
|
|||||||
.map(u => ({ id: u!.id, name: u!.full_name || u!.email }))
|
.map(u => ({ id: u!.id, name: u!.full_name || u!.email }))
|
||||||
})
|
})
|
||||||
|
|
||||||
const allAssignableUsers = computed(() => {
|
const assignedUsers = computed(() => {
|
||||||
// Todos los usuarios activos + cualquier usuario asignado por KAPPA
|
|
||||||
const seen = new Set<number>()
|
const seen = new Set<number>()
|
||||||
const list: { id: number; name: string }[] = []
|
const list: { id: number; name: string }[] = []
|
||||||
for (const u of usersStore.activeUsers) {
|
|
||||||
seen.add(u.id)
|
|
||||||
list.push({ id: u.id, name: u.full_name || u.email })
|
|
||||||
}
|
|
||||||
// Incluir usuarios asignados por KAPPA aunque no estén en activeUsers
|
|
||||||
for (const hu of workItems.userStories) {
|
for (const hu of workItems.userStories) {
|
||||||
const uid = hu._assignedUserId
|
const employeeId = hu._assignedEmployeeId
|
||||||
if (uid != null && !seen.has(uid)) {
|
if (employeeId == null || employeeId <= 0 || seen.has(employeeId)) continue
|
||||||
seen.add(uid)
|
seen.add(employeeId)
|
||||||
list.push({ id: uid, name: hu._assignedName || `Usuario #${uid}` })
|
// Resolver employee → user para mostrar el nombre real
|
||||||
}
|
const uid = resolveEmployeeToUser(employeeId)
|
||||||
|
const name = uid != null
|
||||||
|
? (usersStore.users.find(u => u.id === uid)?.full_name || usersStore.users.find(u => u.id === uid)?.email || '')
|
||||||
|
: ''
|
||||||
|
list.push({ id: employeeId, name: name || hu._assignedName || `Usuario #${employeeId}` })
|
||||||
}
|
}
|
||||||
return list.sort((a, b) => a.name.localeCompare(b.name))
|
return list.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
})
|
})
|
||||||
@@ -87,10 +85,21 @@ function saveAssignment(huId: number, userId: number | null) {
|
|||||||
storage.setJSON(STORAGE_KEY, all)
|
storage.setJSON(STORAGE_KEY, all)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAssignedUserId(hu: { id?: number; _assignedUserId?: number | null }): number | null {
|
function resolveEmployeeToUser(employeeId: number | null): number | null {
|
||||||
// 1. KAPPA asigna directo
|
if (employeeId == null) return null
|
||||||
|
const emp = usersStore.employees.find(e => e.id === employeeId)
|
||||||
|
return emp?.user ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAssignedUserId(hu: { _assignedEmployeeId?: number | null; _assignedUserId?: number | null; id?: number }): number | null {
|
||||||
|
// 1. KAPPA asigna por employee_id (asignado_a: [1135]) → resolver a user_id
|
||||||
|
if (hu._assignedEmployeeId != null && hu._assignedEmployeeId > 0) {
|
||||||
|
const userId = resolveEmployeeToUser(hu._assignedEmployeeId)
|
||||||
|
if (userId != null) return userId
|
||||||
|
}
|
||||||
|
// 2. KAPPA asigna directo por user_id (assigned_to)
|
||||||
if (hu._assignedUserId != null && hu._assignedUserId > 0) return hu._assignedUserId
|
if (hu._assignedUserId != null && hu._assignedUserId > 0) return hu._assignedUserId
|
||||||
// 2. Fallback: asignación manual en localStorage
|
// 3. Fallback: asignación manual en localStorage
|
||||||
if (hu.id != null) {
|
if (hu.id != null) {
|
||||||
const all = loadAssignments()
|
const all = loadAssignments()
|
||||||
return all[hu.id] ?? null
|
return all[hu.id] ?? null
|
||||||
@@ -98,11 +107,30 @@ function getAssignedUserId(hu: { id?: number; _assignedUserId?: number | null })
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignedName(hu: { id?: number; _assignedUserId?: number | null; _assignedName?: string }): string {
|
function assignedName(hu: { _assignedName?: string; _assignedEmployeeId?: number | null; _assignedUserId?: number | null; id?: number }): string {
|
||||||
|
// 1. Intentar resolver employee_id → user_id → nombre real
|
||||||
|
if (hu._assignedEmployeeId != null && hu._assignedEmployeeId > 0) {
|
||||||
|
const userId = resolveEmployeeToUser(hu._assignedEmployeeId)
|
||||||
|
if (userId != null) {
|
||||||
|
const u = usersStore.users.find(u => u.id === userId)
|
||||||
|
if (u) return u.full_name || u.email
|
||||||
|
}
|
||||||
|
// Si el employee no se ha cargado aún, mostrar el nombre de KAPPA (asignado_a_names)
|
||||||
|
if (hu._assignedName) return hu._assignedName
|
||||||
|
}
|
||||||
|
// 2. user_id directo
|
||||||
|
if (hu._assignedUserId != null && hu._assignedUserId > 0) {
|
||||||
|
const u = usersStore.users.find(u => u.id === hu._assignedUserId)
|
||||||
|
if (u) return u.full_name || u.email
|
||||||
|
if (hu._assignedName) return hu._assignedName
|
||||||
|
}
|
||||||
|
// 3. Fallback localStorage
|
||||||
const uid = getAssignedUserId(hu)
|
const uid = getAssignedUserId(hu)
|
||||||
if (!uid) return ''
|
if (uid) {
|
||||||
const u = usersStore.users.find(u => u.id === uid)
|
const u = usersStore.users.find(u => u.id === uid)
|
||||||
return u?.full_name || u?.email || hu._assignedName || ''
|
if (u) return u.full_name || u.email
|
||||||
|
}
|
||||||
|
return hu._assignedName || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Priority helpers ─────────────────────────────────
|
// ─── Priority helpers ─────────────────────────────────
|
||||||
@@ -132,30 +160,30 @@ function priorityLabel(p: unknown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Filters ─────────────────────────────────────────
|
// ─── Filters ─────────────────────────────────────────
|
||||||
const filterStatus = ref('__all')
|
const filterStatus = ref('')
|
||||||
const filterPriority = ref('__all')
|
const filterPriority = ref('')
|
||||||
const filterAssigned = ref('__all')
|
const filterAssigned = ref('')
|
||||||
|
|
||||||
const filteredHUs = computed(() => {
|
const filteredHUs = computed(() => {
|
||||||
let list = workItems.userStories
|
let list = workItems.userStories
|
||||||
if (filterStatus.value !== '__all') {
|
if (filterStatus.value && filterStatus.value !== '__all') {
|
||||||
list = list.filter(h => {
|
list = list.filter(h => {
|
||||||
const s = String(h.status ?? '').toLowerCase()
|
const s = String(h.status ?? '').toLowerCase()
|
||||||
return s === filterStatus.value
|
return s === filterStatus.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filterPriority.value !== '__all') {
|
if (filterPriority.value && filterPriority.value !== '__all') {
|
||||||
list = list.filter(h => {
|
list = list.filter(h => {
|
||||||
const p = String(h.priority ?? '').toLowerCase()
|
const p = String(h.priority ?? '').toLowerCase()
|
||||||
return p === filterPriority.value
|
return p === filterPriority.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (filterAssigned.value !== '__all') {
|
if (filterAssigned.value && filterAssigned.value !== '__all') {
|
||||||
if (filterAssigned.value === '__none') {
|
if (filterAssigned.value === '__none') {
|
||||||
list = list.filter(h => !getAssignedUserId(h))
|
list = list.filter(h => !getAssignedUserId(h) && !h._assignedEmployeeId)
|
||||||
} else {
|
} else {
|
||||||
const uid = Number(filterAssigned.value)
|
const targetId = Number(filterAssigned.value)
|
||||||
list = list.filter(h => getAssignedUserId(h) === uid)
|
list = list.filter(h => h._assignedEmployeeId === targetId || getAssignedUserId(h) === targetId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
@@ -674,7 +702,7 @@ const statusLabel = (status: unknown) => {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="__all" class="text-xs">{{ t('dashboard.filterAll') }}</SelectItem>
|
<SelectItem value="__all" class="text-xs">{{ t('dashboard.filterAll') }}</SelectItem>
|
||||||
<SelectItem value="__none" class="text-xs">{{ t('dashboard.unassigned') }}</SelectItem>
|
<SelectItem value="__none" class="text-xs">{{ t('dashboard.unassigned') }}</SelectItem>
|
||||||
<SelectItem v-for="m in allAssignableUsers" :key="m.id" :value="String(m.id)" class="text-xs">{{ m.name }}</SelectItem>
|
<SelectItem v-for="m in assignedUsers" :key="m.id" :value="String(m.id)" class="text-xs">{{ m.name }}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user