using JobsMedical.Web.Data; using JobsMedical.Web.Models; using Microsoft.EntityFrameworkCore; namespace JobsMedical.Web.Services.Scraping; /// /// Centralizes "what counts as a live listing". Jobs have no end date, so they go stale after a /// window; shifts expire once their date has passed. Public screens hide anything outside these /// rules, and flips stale Open rows to Expired so dashboards/DB agree. /// public static class ListingPolicy { /// A job opening older than this (since posting) is treated as stale and hidden. public const int JobFreshnessDays = 30; /// «آماده به کار» goes stale faster — the person is usually hired within a few weeks. public const int TalentFreshnessDays = 21; public static DateTime JobCutoffUtc => DateTime.UtcNow.AddDays(-JobFreshnessDays); public static DateTime TalentCutoffUtc => DateTime.UtcNow.AddDays(-TalentFreshnessDays); } /// Sweeps stale listings into the Expired state (archive). Idempotent and cheap. public class ListingArchiver { private readonly AppDbContext _db; private readonly ILogger _log; public ListingArchiver(AppDbContext db, ILogger log) { _db = db; _log = log; } public async Task ArchiveStaleAsync(CancellationToken ct = default) { var today = DateOnly.FromDateTime(DateTime.UtcNow); var jobCutoff = ListingPolicy.JobCutoffUtc; var expiredShifts = await _db.Shifts .Where(s => s.Status == ShiftStatus.Open && s.Date < today) .ExecuteUpdateAsync(u => u.SetProperty(s => s.Status, ShiftStatus.Expired), ct); var expiredJobs = await _db.JobOpenings .Where(j => j.Status == ShiftStatus.Open && j.CreatedAt < jobCutoff) .ExecuteUpdateAsync(u => u.SetProperty(j => j.Status, ShiftStatus.Expired), ct); var talentCutoff = ListingPolicy.TalentCutoffUtc; var expiredTalent = await _db.TalentListings .Where(t => t.Status == ShiftStatus.Open && t.CreatedAt < talentCutoff) .ExecuteUpdateAsync(u => u.SetProperty(t => t.Status, ShiftStatus.Expired), ct); if (expiredShifts + expiredJobs + expiredTalent > 0) _log.LogInformation("Archived {Shifts} shifts + {Jobs} jobs + {Talent} talent as expired", expiredShifts, expiredJobs, expiredTalent); return expiredShifts + expiredJobs + expiredTalent; } }