3f240e5277
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>
35 lines
1.0 KiB
PHP
35 lines
1.0 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class IssueTask extends Model
|
|
{
|
|
use SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'issue_id', 'title', 'is_done', 'done_at', 'done_by',
|
|
'assigned_to', 'due_date', 'order',
|
|
'uuid', 'client_updated_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_done' => 'boolean',
|
|
'done_at' => 'datetime',
|
|
'due_date' => 'date',
|
|
];
|
|
|
|
public function issue() { return $this->belongsTo(Issue::class); }
|
|
public function assignee() { return $this->belongsTo(User::class, 'assigned_to'); }
|
|
public function completer() { return $this->belongsTo(User::class, 'done_by'); }
|
|
public function media() { return $this->morphMany(Media::class, 'mediable'); }
|
|
|
|
/** Overdue = has a due date in the past and not yet done. */
|
|
public function getIsOverdueAttribute(): bool
|
|
{
|
|
return ! $this->is_done && $this->due_date && $this->due_date->isPast();
|
|
}
|
|
}
|