38031cb189
Phone fix: shifts/jobs showed Facility.Phone, but unnamed ads all share one placeholder facility, so every such listing displayed the same stale number while the ad's real phone sat unused in the description. ContactMethod is now attachable to a Shift/JobOpening (not just talent); ingestion stores the ad's own number(s) on each listing and the detail pages render them (new _ContactList partial), falling back to the facility phone only when the ad had none. Migration ShiftJobContacts (nullable owner FKs) — auto-applies on deploy. Stale applicants: skip «آماده به کار» posts older than 7 days at ingest, by the source's real timestamp (Telegram <time>, Bale date) or a Persian time-ago phrase in the text (Divar «۲ هفته پیش»). Recorded as Discarded; shifts/jobs are not aged out. Admin: Review page now shows a «مشاهده آگهی در منبع» link (RawListing.SourceUrl) so the source post can be checked before publishing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
74 lines
2.3 KiB
C#
74 lines
2.3 KiB
C#
using JobsMedical.Web.Data;
|
|
using JobsMedical.Web.Models;
|
|
using JobsMedical.Web.Services;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace JobsMedical.Web.Pages.Jobs;
|
|
|
|
public class DetailsModel : PageModel
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly InterestService _interest;
|
|
private readonly JobsMedical.Web.Services.Scraping.SettingsService _settings;
|
|
|
|
public DetailsModel(AppDbContext db, InterestService interest,
|
|
JobsMedical.Web.Services.Scraping.SettingsService settings)
|
|
{
|
|
_db = db;
|
|
_interest = interest;
|
|
_settings = settings;
|
|
}
|
|
|
|
public JobOpening? Job { get; private set; }
|
|
public string? MapKey { get; private set; }
|
|
public bool ShowContact { get; private set; }
|
|
public bool Saved { get; private set; }
|
|
public bool Reported { get; private set; }
|
|
|
|
public async Task<IActionResult> OnGetAsync(int id)
|
|
{
|
|
await LoadAsync(id);
|
|
if (Job is null) return NotFound();
|
|
MapKey = (await _settings.GetAsync()).NeshanMapKey;
|
|
Reported = Request.Query["reported"] == "1";
|
|
await _interest.LogJobAsync(InterestEventType.View, id);
|
|
return Page();
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostInterestAsync(int id)
|
|
{
|
|
await LoadAsync(id);
|
|
if (Job is null) return NotFound();
|
|
await _interest.LogJobAsync(InterestEventType.Apply, id);
|
|
ShowContact = true;
|
|
return Page();
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostSaveAsync(int id)
|
|
{
|
|
await LoadAsync(id);
|
|
if (Job is null) return NotFound();
|
|
await _interest.LogJobAsync(InterestEventType.Save, id);
|
|
Saved = true;
|
|
return Page();
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostDismissAsync(int id)
|
|
{
|
|
await _interest.LogJobAsync(InterestEventType.Dismiss, id);
|
|
return RedirectToPage("/Jobs/Index");
|
|
}
|
|
|
|
private async Task LoadAsync(int id)
|
|
{
|
|
Job = await _db.JobOpenings
|
|
.Include(j => j.Facility).ThenInclude(f => f.City)
|
|
.Include(j => j.Facility).ThenInclude(f => f.District)
|
|
.Include(j => j.Role)
|
|
.Include(j => j.Contacts)
|
|
.FirstOrDefaultAsync(j => j.Id == id);
|
|
}
|
|
}
|