feat: structured salary fields (min/max/currency/period)
Adds SalaryMin/SalaryMax/SalaryCurrency/SalaryPeriod alongside the existing free-text Salary field (kept for back-compat and display). - JobApplication model + idempotent column bridging for SQLite and MySQL - Create/Update DTOs with NormalizeSalary (clamps negatives, swaps inverted min/max, uppercases currency, whitelists period) - JobApplicationDto exposes the fields; CSV export gains 4 columns - UI: add/edit dialogs get min/max/currency/period inputs; job table renders a formatted range via shared salary.ts formatter (falls back to free-text when structured values are absent) - EN/NB translations; backend + full frontend suites green Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,10 @@ namespace JobTrackerApi.Controllers
|
||||
"DateApplied",
|
||||
"Location",
|
||||
"Salary",
|
||||
"SalaryMin",
|
||||
"SalaryMax",
|
||||
"SalaryCurrency",
|
||||
"SalaryPeriod",
|
||||
"NextAction",
|
||||
"FollowUpAt",
|
||||
"JobUrl",
|
||||
@@ -76,6 +80,10 @@ namespace JobTrackerApi.Controllers
|
||||
Esc(j.DateApplied.ToString("o")),
|
||||
Esc(j.Location),
|
||||
Esc(j.Salary),
|
||||
Esc(j.SalaryMin?.ToString(System.Globalization.CultureInfo.InvariantCulture)),
|
||||
Esc(j.SalaryMax?.ToString(System.Globalization.CultureInfo.InvariantCulture)),
|
||||
Esc(j.SalaryCurrency),
|
||||
Esc(j.SalaryPeriod),
|
||||
Esc(j.NextAction),
|
||||
Esc(j.FollowUpAt?.ToString("o")),
|
||||
Esc(j.JobUrl),
|
||||
|
||||
@@ -749,6 +749,10 @@ Canonical profile:
|
||||
Deadline: job.Deadline,
|
||||
Location: job.Location,
|
||||
Salary: job.Salary,
|
||||
SalaryMin: job.SalaryMin,
|
||||
SalaryMax: job.SalaryMax,
|
||||
SalaryCurrency: job.SalaryCurrency,
|
||||
SalaryPeriod: job.SalaryPeriod,
|
||||
NextAction: job.NextAction,
|
||||
FollowUpAt: job.FollowUpAt,
|
||||
FeedbackRequestedAt: job.FeedbackRequestedAt,
|
||||
@@ -1081,6 +1085,10 @@ Canonical profile:
|
||||
DateTime? Deadline,
|
||||
string? Location,
|
||||
string? Salary,
|
||||
decimal? SalaryMin,
|
||||
decimal? SalaryMax,
|
||||
string? SalaryCurrency,
|
||||
string? SalaryPeriod,
|
||||
string? NextAction,
|
||||
DateTime? FollowUpAt,
|
||||
DateTime? FeedbackRequestedAt,
|
||||
@@ -1349,6 +1357,10 @@ Canonical profile:
|
||||
string? Status,
|
||||
string? Location,
|
||||
string? Salary,
|
||||
decimal? SalaryMin,
|
||||
decimal? SalaryMax,
|
||||
string? SalaryCurrency,
|
||||
string? SalaryPeriod,
|
||||
string? NextAction,
|
||||
DateTime? FollowUpAt,
|
||||
string? Notes,
|
||||
@@ -1367,6 +1379,22 @@ Canonical profile:
|
||||
bool? HasOtherAttachment
|
||||
);
|
||||
|
||||
private static (decimal? Min, decimal? Max, string? Currency, string? Period) NormalizeSalary(
|
||||
decimal? min, decimal? max, string? currency, string? period)
|
||||
{
|
||||
if (min is < 0) min = null;
|
||||
if (max is < 0) max = null;
|
||||
if (min.HasValue && max.HasValue && min > max) (min, max) = (max, min);
|
||||
|
||||
var cur = (currency ?? "").Trim().ToUpperInvariant();
|
||||
if (cur.Length > 8) cur = cur[..8];
|
||||
|
||||
var per = (period ?? "").Trim().ToLowerInvariant();
|
||||
if (per is not ("year" or "month" or "hour")) per = "";
|
||||
|
||||
return (min, max, cur.Length == 0 ? null : cur, per.Length == 0 ? null : per);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<JobApplication>> Create([FromBody] CreateJobApplicationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1409,6 +1437,9 @@ Canonical profile:
|
||||
ResponseDate = null,
|
||||
};
|
||||
|
||||
(job.SalaryMin, job.SalaryMax, job.SalaryCurrency, job.SalaryPeriod) =
|
||||
NormalizeSalary(request.SalaryMin, request.SalaryMax, request.SalaryCurrency, request.SalaryPeriod);
|
||||
|
||||
// Generate and persist a short summary at creation time to avoid repeated model calls.
|
||||
try
|
||||
{
|
||||
@@ -1447,6 +1478,10 @@ Canonical profile:
|
||||
DateTime? ResponseDate,
|
||||
string? Location,
|
||||
string? Salary,
|
||||
decimal? SalaryMin,
|
||||
decimal? SalaryMax,
|
||||
string? SalaryCurrency,
|
||||
string? SalaryPeriod,
|
||||
string? NextAction,
|
||||
DateTime? FollowUpAt,
|
||||
bool? HasResume,
|
||||
@@ -1487,6 +1522,8 @@ Canonical profile:
|
||||
job.ResponseDate = request.ResponseDate;
|
||||
job.Location = string.IsNullOrWhiteSpace(request.Location) ? null : request.Location.Trim();
|
||||
job.Salary = string.IsNullOrWhiteSpace(request.Salary) ? null : request.Salary.Trim();
|
||||
(job.SalaryMin, job.SalaryMax, job.SalaryCurrency, job.SalaryPeriod) =
|
||||
NormalizeSalary(request.SalaryMin, request.SalaryMax, request.SalaryCurrency, request.SalaryPeriod);
|
||||
job.NextAction = string.IsNullOrWhiteSpace(request.NextAction) ? null : request.NextAction.Trim();
|
||||
job.FollowUpAt = request.FollowUpAt;
|
||||
job.FeedbackRequestedAt = request.FeedbackRequestedAt;
|
||||
|
||||
Reference in New Issue
Block a user