3a906b881e
Evolve the existing readiness workflow into one persisted, user-controlled checklist rather than adding a second tracker. ApplicationChecklistItem records only completion state and user intent. Each default system item carries a stable SystemKey and an AutoSignal — the same signal /readiness already computed — and re-syncs on every read: a satisfied signal auto-completes the item, a reverted signal reopens it, and a manual tick always wins. Users can add, reorder, dismiss and delete. Readiness is refactored into a projection of the checklist (score = completion percentage, completed/missing = live items by status). Its DTO shape and the workflowSignal/reminders health view are unchanged, so no API contract breaks. The workspace's next recommended action now comes from the first pending checklist item in category priority order (preparation, submission, follow-up, interview, custom), replacing the parallel ruleset — so the overview can never recommend something already ticked off, and a user's own task can be next. The table follows the established MariaDB-safe path: the scaffolded migration is a no-op and the idempotent reconciler owns the DDL for both providers. Verified on MariaDB 11 — auto_increment PK, varchar/datetime(6)/tinyint(1) columns, both indexes inside the key limit, cascade delete, unique system key per application, and NULL system keys not colliding for custom items. 329 backend tests, 94 frontend tests, type check, production build and both Docker builds pass locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
3.4 KiB
C#
81 lines
3.4 KiB
C#
namespace JobTrackerApi.Models;
|
|
|
|
// Phase 5 Milestone 2 — the application checklist.
|
|
//
|
|
// A workflow GUIDANCE layer, not a new store of truth. It duplicates nothing: the CV lives in
|
|
// CvVariant, documents in Attachment, history in JobEvent, follow-up in JobApplication.FollowUpAt.
|
|
// A checklist item only records "is this step done, and does the user still want it".
|
|
//
|
|
// System items carry a stable SystemKey and (usually) an AutoSignal: the same readiness signal the
|
|
// /readiness endpoint already computes. When the signal is satisfied the item auto-completes, so the
|
|
// checklist and readiness can never disagree — readiness became the calculation, this is the surface.
|
|
// docs/architecture/application-workspace.md.
|
|
public sealed class ApplicationChecklistItem
|
|
{
|
|
public int Id { get; set; }
|
|
public string OwnerUserId { get; set; } = string.Empty;
|
|
public int JobApplicationId { get; set; }
|
|
public JobApplication? JobApplication { get; set; }
|
|
|
|
// Stable identifier for system items ("prepare-cv", ...). Null for user-created items, so re-seeding
|
|
// is idempotent and a system item can be recognised across renames.
|
|
public string? SystemKey { get; set; }
|
|
|
|
// Which readiness signal completes this item automatically. Null = manual only.
|
|
public string? AutoSignal { get; set; }
|
|
|
|
public string Title { get; set; } = string.Empty;
|
|
public string? Description { get; set; }
|
|
|
|
// preparation | submission | follow-up | interview | custom. Drives grouping and next-action priority.
|
|
public string Category { get; set; } = ChecklistCategories.Custom;
|
|
|
|
// pending | done | dismissed. "dismissed" is the user opting out — not every item fits every role.
|
|
public string Status { get; set; } = ChecklistStatuses.Pending;
|
|
|
|
// Workspace section this step is done in, so the checklist can link straight to the work.
|
|
public string? Section { get; set; }
|
|
|
|
public int SortOrder { get; set; }
|
|
public bool IsSystemGenerated { get; set; }
|
|
|
|
// Set when the auto-signal completed the item, so the sync may reopen it if the signal reverts.
|
|
// A manual tick clears this and therefore sticks.
|
|
public bool IsAutoCompleted { get; set; }
|
|
|
|
public DateTimeOffset? CompletedAt { get; set; }
|
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
public static class ChecklistStatuses
|
|
{
|
|
public const string Pending = "pending";
|
|
public const string Done = "done";
|
|
public const string Dismissed = "dismissed";
|
|
|
|
public static bool IsValid(string? value) =>
|
|
value is Pending or Done or Dismissed;
|
|
}
|
|
|
|
public static class ChecklistCategories
|
|
{
|
|
public const string Preparation = "preparation";
|
|
public const string Submission = "submission";
|
|
public const string FollowUp = "follow-up";
|
|
public const string Interview = "interview";
|
|
public const string Custom = "custom";
|
|
|
|
// Next-action priority: critical preparation, then submission, then chasing, then interview prep,
|
|
// then whatever the user added themselves.
|
|
public static readonly string[] Order = { Preparation, Submission, FollowUp, Interview, Custom };
|
|
|
|
public static int Rank(string? category)
|
|
{
|
|
var index = Array.IndexOf(Order, category ?? Custom);
|
|
return index < 0 ? Order.Length : index;
|
|
}
|
|
|
|
public static bool IsValid(string? value) => Array.IndexOf(Order, value ?? string.Empty) >= 0;
|
|
}
|