feat(jobs): complete workspace draft parity
This commit is contained in:
@@ -218,7 +218,11 @@ namespace JobTrackerApi.Controllers
|
||||
TailoredCvRenderOptions? RenderOptions,
|
||||
string? Status);
|
||||
public sealed record GenerateApplicationPackageDto(string TailoredCvText, string? CoverLetterDraft, string? ApplicationAnswerDraft, string? RecruiterMessageDraft, List<string> KeyPoints, List<string> AttachmentSignals, List<string> AttachmentFilesUsed, List<string> CoverLetterVariants, List<string> RecruiterMessageVariants);
|
||||
public sealed record SaveApplicationDraftsRequest(string? CoverLetterText, string? Notes, string? RecruiterMessageDraft);
|
||||
public sealed record SaveApplicationDraftsRequest(
|
||||
string? CoverLetterText,
|
||||
string? Notes,
|
||||
string? RecruiterMessageDraft,
|
||||
string? ApplicationAnswerDraft = null);
|
||||
public sealed record SavedPackageMaterial(string? TailoredCvText, string? CoverLetterText, string? RecruiterMessageDraft, string? Notes);
|
||||
public sealed record InterviewPrepDto(string Summary, List<string> TalkingPoints, List<string> LikelyQuestions, List<string> WeakSpots);
|
||||
public sealed record ReadinessDto(int Score, string Level, List<string> Completed, List<string> Missing, List<string> Reminders, WorkflowSignalDto WorkflowSignal);
|
||||
|
||||
@@ -837,7 +837,11 @@ Canonical profile:
|
||||
job.FeedbackRequestedAt = request.FeedbackRequestedAt;
|
||||
// HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment are derived from
|
||||
// Attachment rows, not settable here -- see AttachmentsController.RecomputeAttachmentFlagsAsync.
|
||||
job.Notes = request.Notes;
|
||||
// Application answers use the legacy Notes column for storage compatibility, but the
|
||||
// general editor owns only human notes. Preserve the separate answer when those notes
|
||||
// are edited; the application-package endpoint is the only place that clears it.
|
||||
var savedApplicationAnswer = ExtractSavedApplicationAnswerDraft(job.Notes);
|
||||
job.Notes = UpsertSavedApplicationAnswerDraft(request.Notes, savedApplicationAnswer);
|
||||
job.Description = request.Description;
|
||||
job.TranslatedDescription = request.TranslatedDescription;
|
||||
job.DescriptionLanguage = request.DescriptionLanguage;
|
||||
@@ -1826,19 +1830,26 @@ Candidate CV/profile:
|
||||
var job = await _db.JobApplications.FirstOrDefaultAsync(j => j.Id == id, cancellationToken);
|
||||
if (job is null) return NotFound();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.CoverLetterText))
|
||||
if (request.CoverLetterText is not null)
|
||||
{
|
||||
job.CoverLetterText = request.CoverLetterText.Trim();
|
||||
job.CoverLetterText = string.IsNullOrWhiteSpace(request.CoverLetterText) ? null : request.CoverLetterText.Trim();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Notes))
|
||||
if (request.Notes is not null)
|
||||
{
|
||||
job.Notes = request.Notes.Trim();
|
||||
job.Notes = string.IsNullOrWhiteSpace(request.Notes) ? null : request.Notes.Trim();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.RecruiterMessageDraft))
|
||||
if (request.ApplicationAnswerDraft is not null)
|
||||
{
|
||||
job.RecruiterMessageDraft = request.RecruiterMessageDraft.Trim();
|
||||
job.Notes = UpsertSavedApplicationAnswerDraft(job.Notes, request.ApplicationAnswerDraft);
|
||||
}
|
||||
|
||||
if (request.RecruiterMessageDraft is not null)
|
||||
{
|
||||
job.RecruiterMessageDraft = string.IsNullOrWhiteSpace(request.RecruiterMessageDraft)
|
||||
? null
|
||||
: request.RecruiterMessageDraft.Trim();
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
@@ -34,6 +34,8 @@ public sealed record WorkspaceOverviewDto(
|
||||
string? DescriptionLanguage,
|
||||
IReadOnlyList<string> Tags,
|
||||
string? Notes,
|
||||
string? ApplicationAnswerDraft,
|
||||
string? RecruiterMessageDraft,
|
||||
string? Source,
|
||||
string? CountryCode,
|
||||
bool HasJobDescription,
|
||||
@@ -131,7 +133,9 @@ public sealed class ApplicationWorkspaceService : IApplicationWorkspaceService
|
||||
job.TranslatedDescription,
|
||||
job.DescriptionLanguage,
|
||||
JobApplicationHelpers.SplitTags(job.Tags).Distinct(StringComparer.OrdinalIgnoreCase).ToList(),
|
||||
job.Notes,
|
||||
JobApplicationHelpers.RemoveSavedApplicationAnswerDraft(job.Notes),
|
||||
JobApplicationHelpers.ExtractSavedApplicationAnswerDraft(job.Notes),
|
||||
job.RecruiterMessageDraft,
|
||||
job.Job?.Source,
|
||||
job.Job?.CountryCode,
|
||||
!string.IsNullOrWhiteSpace(job.Description) || !string.IsNullOrWhiteSpace(job.TranslatedDescription),
|
||||
|
||||
@@ -281,6 +281,21 @@ namespace JobTrackerApi.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? UpsertSavedApplicationAnswerDraft(string? notes, string? draft)
|
||||
{
|
||||
var humanNotes = RemoveSavedApplicationAnswerDraft(notes);
|
||||
var answer = (draft ?? string.Empty).Trim();
|
||||
if (string.IsNullOrWhiteSpace(answer))
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(humanNotes) ? null : humanNotes;
|
||||
}
|
||||
|
||||
var answerBlock = $"{ApplicationAnswerDraftStart}\n{answer}\n{ApplicationAnswerDraftEnd}";
|
||||
return string.IsNullOrWhiteSpace(humanNotes)
|
||||
? answerBlock
|
||||
: $"{humanNotes}\n\n{answerBlock}";
|
||||
}
|
||||
|
||||
public static string BuildFollowUpSubject(JobApplication job, Correspondence? lastMessage)
|
||||
{
|
||||
var subject = (lastMessage?.Subject ?? string.Empty).Trim();
|
||||
@@ -467,13 +482,14 @@ namespace JobTrackerApi.Services
|
||||
var value = notes ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(value)) return string.Empty;
|
||||
|
||||
var startIndex = value.IndexOf(ApplicationAnswerDraftStart, StringComparison.Ordinal);
|
||||
var endIndex = value.IndexOf(ApplicationAnswerDraftEnd, StringComparison.Ordinal);
|
||||
if (startIndex >= 0 && endIndex > startIndex)
|
||||
while (true)
|
||||
{
|
||||
var startIndex = value.IndexOf(ApplicationAnswerDraftStart, StringComparison.Ordinal);
|
||||
var endIndex = value.IndexOf(ApplicationAnswerDraftEnd, StringComparison.Ordinal);
|
||||
if (startIndex < 0 || endIndex <= startIndex) break;
|
||||
var before = value[..startIndex].Trim();
|
||||
var after = value[(endIndex + ApplicationAnswerDraftEnd.Length)..].Trim();
|
||||
return string.Join("\n\n", new[] { before, after }.Where(part => !string.IsNullOrWhiteSpace(part))).Trim();
|
||||
value = string.Join("\n\n", new[] { before, after }.Where(part => !string.IsNullOrWhiteSpace(part))).Trim();
|
||||
}
|
||||
|
||||
const string legacyPrefix = "Application answer draft:";
|
||||
|
||||
Reference in New Issue
Block a user