47d05ba946
Phase F2 of the Career Workspace roadmap: CvVariant, CvVersion, and TailoredApplication -- the reference seam. Per the product boundary, a job application REFERENCES a tailored output; it does not own it. CvVariant is not job-owned: it survives job deletion (SetNull on its optional CareerProfile link, not cascaded), can be reused across applications, and carries its own append-only CvVersion history. TailoredApplication is the join that links a variant to a job (cascades with either side, since the link is meaningless without both). Rather than shipping empty tables with no consumer, this dual-writes from both existing TailoredCvDraft save paths (SaveTailoredCvDraft, UpsertGeneratedTailoredCvDraftAsync via GenerateTailoredCvDraft) -- same pattern as CareerProfile in Phase F1. TailoredCvDraft remains authoritative for every existing read path; the sync is additive and never blocks or fails a draft save. 2 new tests: variant/version/link created on first save, same variant reused (not duplicated) with version incrementing on subsequent saves. Verified against the real dev DB -- FK dependency ordering (CareerProfiles -> CvVariants -> CvVersions/TailoredApplications) holds in both SQLite and MySQL reconciler dialects.
53 lines
2.5 KiB
C#
53 lines
2.5 KiB
C#
namespace JobTrackerApi.Models;
|
|
|
|
// Career Workspace foundation, Phase F2 (career-workspace-implementation-roadmap.md). A CvVariant
|
|
// is a durable, named lens on the career profile -- unlike TailoredCvDraft, it is not owned by a
|
|
// job; TailoredApplication is the reference that links a variant to a job, so the same variant can
|
|
// be reused across applications and a job can be deleted without losing the variant.
|
|
//
|
|
// This phase dual-writes from the existing TailoredCvDraft save paths (same pattern as
|
|
// CareerProfile in Phase F1): every draft save also upserts the job's CvVariant, appends a
|
|
// CvVersion snapshot, and ensures a TailoredApplication link. TailoredCvDraft remains the
|
|
// authoritative row every existing read path uses.
|
|
public sealed class CvVariant
|
|
{
|
|
public int Id { get; set; }
|
|
public string OwnerUserId { get; set; } = string.Empty;
|
|
public int? CareerProfileId { get; set; }
|
|
public CareerProfile? CareerProfile { get; set; }
|
|
public string Name { get; set; } = string.Empty;
|
|
// Serialized TailoredCvDocument -- reuses the existing document shape rather than inventing a
|
|
// new one, so this phase carries zero data-shape risk.
|
|
public string ContentJson { get; set; } = string.Empty;
|
|
public string ThemeId { get; set; } = "ats-minimal";
|
|
public int Version { get; set; }
|
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
// Append-only history: one row per CvVariant save.
|
|
public sealed class CvVersion
|
|
{
|
|
public int Id { get; set; }
|
|
public string OwnerUserId { get; set; } = string.Empty;
|
|
public int CvVariantId { get; set; }
|
|
public CvVariant? CvVariant { get; set; }
|
|
public int Version { get; set; }
|
|
public string ContentJson { get; set; } = string.Empty;
|
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
// The integration seam per the product boundary: a job application REFERENCES a tailored output,
|
|
// it does not own it. Deleting a job does not delete the variant; the variant can outlive the job
|
|
// or be reused by a future one.
|
|
public sealed class TailoredApplication
|
|
{
|
|
public int Id { get; set; }
|
|
public string OwnerUserId { get; set; } = string.Empty;
|
|
public int CvVariantId { get; set; }
|
|
public CvVariant? CvVariant { get; set; }
|
|
public int JobApplicationId { get; set; }
|
|
public JobApplication? JobApplication { get; set; }
|
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
|
}
|