feat(issues): incidencias enriquecidas (tareas/comentarios/fotos/verificación) + tabla Rappasoft + logo
Web: - IssueTask + IssueComment (modelos, migraciones, soft-deletes, campos de sync). Issue gana tasks()/comments() y accessor de % de avance derivado de tareas. - IssueDetail (página): checklist con asignado/fecha límite/progreso, hilo de comentarios con foto por comentario, galería de fotos de la incidencia y flujo de verificación open→in_review→resolved/closed (+reabrir) con notas. - Creación/edición en páginas propias (IssueForm), sin modal; al guardar redirige al detalle. Rutas projects.issues.create/edit/show. - Listado con tabla Rappasoft (IssueTable): filtros por estado/prioridad, búsqueda, barra de progreso y acciones por fila gateadas por permisos; IssueManager queda como contenedor (cabecera + stats) que embebe la tabla. - Seguridad: pertenencia al proyecto + permisos por acción (view/create/edit/delete issues, upload/delete media) en todos los componentes. API móvil (offline): - /sync: issue_task.create/update y issue_comment.create (idempotente, LWW). - /media: parent_entity issue_task / issue_comment. - bundle + tombstones incluyen issue_tasks / issue_comments. - openapi.yaml + MOBILE_SYNC_PROTOCOL.md actualizados. Tests: MobileApiTest 23 passing (+5); IssuesTablePageTest (3) smoke de la tabla. Branding: logo RTE International — MAI Group (public/images/logo-rte.png) en login y navegación; application-logo pasa de SVG por defecto a <img>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -110,3 +110,17 @@ Respuesta por operación:
|
||||
- **Fase C — PUSH:** `/sync` idempotente con validación/autorización/conflictos (recoge y endurece la lógica actual).
|
||||
- **Fase D — Media:** subida multipart + descarga.
|
||||
- **Fase E — Endurecimiento + Docs:** rate-limit, `sync_logs`, OpenAPI/Swagger como contrato para el equipo móvil.
|
||||
|
||||
---
|
||||
|
||||
## Addendum (2026-06-18): Incidencias enriquecidas — tareas, comentarios y fotos
|
||||
|
||||
El detalle de una incidencia incluye ahora un **checklist de tareas** y un **hilo de comentarios**, ambos con fotos. Todo es sincronizable offline:
|
||||
|
||||
- **Nuevas entidades de PULL** en el `bundle` (y en `deleted`): `issue_tasks`, `issue_comments`.
|
||||
- **Nuevas operaciones de PUSH** en `/sync` (idempotentes por `uuid`):
|
||||
- `issue_task.create` — `data`: `issue_id`, `title`, `assigned_to?`, `due_date?`, `is_done?`. Requiere `edit issues`.
|
||||
- `issue_task.update` — `data`: `id`, y cualquiera de `title`/`assigned_to`/`due_date`/`is_done`. Last-write-wins por `client_updated_at`. Requiere `edit issues`.
|
||||
- `issue_comment.create` — `data`: `issue_id`, `body`. Requiere `view issues`.
|
||||
- **Fotos**: `POST /media` admite `parent_entity` = `issue_task` y `issue_comment` (además de `issue`). Requiere `upload media`.
|
||||
- El **% de avance** de la incidencia se deriva de las tareas completadas (no se almacena ni se sincroniza).
|
||||
|
||||
+6
-2
@@ -130,7 +130,7 @@ paths:
|
||||
required: [uuid, parent_entity, parent_id, file]
|
||||
properties:
|
||||
uuid: { type: string, format: uuid }
|
||||
parent_entity: { type: string, enum: [feature, issue, project, phase, layer] }
|
||||
parent_entity: { type: string, enum: [feature, issue, issue_task, issue_comment, project, phase, layer] }
|
||||
parent_id: { type: integer }
|
||||
file: { type: string, format: binary }
|
||||
category: { type: string, enum: [image, document, other] }
|
||||
@@ -156,7 +156,7 @@ components:
|
||||
type: object
|
||||
required: [entity, op, uuid, data]
|
||||
properties:
|
||||
entity: { type: string, enum: [progress_update, inspection, issue, feature] }
|
||||
entity: { type: string, enum: [progress_update, inspection, issue, issue_task, issue_comment, feature] }
|
||||
op: { type: string, enum: [create, update] }
|
||||
uuid: { type: string, format: uuid, description: client-generated idempotency key }
|
||||
client_updated_at: { type: string, format: date-time }
|
||||
@@ -185,6 +185,8 @@ components:
|
||||
features: { type: array, items: { type: object } }
|
||||
inspections: { type: array, items: { type: object } }
|
||||
issues: { type: array, items: { type: object } }
|
||||
issue_tasks: { type: array, items: { type: object } }
|
||||
issue_comments: { type: array, items: { type: object } }
|
||||
templates: { type: array, items: { type: object } }
|
||||
media: { type: array, items: { type: object } }
|
||||
deleted:
|
||||
@@ -196,3 +198,5 @@ components:
|
||||
features: { type: array, items: { type: integer } }
|
||||
inspections: { type: array, items: { type: integer } }
|
||||
issues: { type: array, items: { type: integer } }
|
||||
issue_tasks: { type: array, items: { type: integer } }
|
||||
issue_comments: { type: array, items: { type: integer } }
|
||||
|
||||
Reference in New Issue
Block a user