'integer', 'start_date' => 'date', 'due_date' => 'date', 'completed_at' => 'date', 'sort_order' => 'integer', 'imported_at' => 'datetime', ]; public function attachments(): HasMany { return $this->hasMany(SystemizationTodoAttachment::class, 'systemization_todo_id') ->orderBy('id'); } public function isCompleted(): bool { return $this->status === self::STATUS_COMPLETED; } public function statusLabel(): string { return $this->isCompleted() ? self::STATUS_COMPLETED : self::STATUS_INCOMPLETE; } public static function mergeNotesIntoProgress(?string $progress, ?string $notes): ?string { $progress = trim(str_replace(["\r\n", "\r"], "\n", (string) $progress)); $notes = trim(str_replace(["\r\n", "\r"], "\n", (string) $notes)); if ($notes === '') { return $progress !== '' ? $progress : null; } if ($progress === '' || str_contains($progress, $notes)) { return $progress !== '' ? $progress : $notes; } return $progress . "\n" . $notes; } public function displayProgress(): string { $progress = trim((string) self::mergeNotesIntoProgress($this->progress, $this->notes)); if ($progress === '') { return ''; } $normalized = (string) preg_replace('/[\s。.]+$/u', '', $progress); return $this->isCompleted() && $normalized === self::STATUS_COMPLETED ? '' : $progress; } public function reminder(): array { if ($this->isCompleted()) { return ['label' => '—', 'tone' => 'muted']; } if ($this->due_date === null) { return ['label' => '时间待定', 'tone' => 'muted']; } $today = today()->startOfDay(); $dueDate = $this->due_date->copy()->startOfDay(); if ($dueDate->lt($today)) { return [ 'label' => '逾期 ' . $dueDate->diffInDays($today) . ' 天', 'tone' => 'danger', ]; } if ($dueDate->equalTo($today)) { return ['label' => '今天到期', 'tone' => 'warning']; } return [ 'label' => '剩余 ' . $today->diffInDays($dueDate) . ' 天', 'tone' => 'info', ]; } }