Files
hamkadr/src/JobsMedical.Web/Models/JobOpening.cs
T
soroush.asadi 38031cb189
CI/CD / CI · dotnet build (push) Successful in 1m3s
CI/CD / Deploy · hamkadr (push) Successful in 1m18s
Per-ad contacts for shifts/jobs, stale-applicant filter, review source link
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>
2026-06-10 21:28:12 +03:30

52 lines
1.9 KiB
C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace JobsMedical.Web.Models;
/// <summary>
/// A permanent / ongoing hiring position (استخدام) — the hiring side of the marketplace,
/// alongside one-off <see cref="Shift"/>s. No date/time; instead an employment type and a
/// monthly salary range. Reuses <see cref="ShiftStatus"/> for lifecycle (Open/Filled/…).
/// </summary>
public class JobOpening
{
public int Id { get; set; }
public int FacilityId { get; set; }
public Facility Facility { get; set; } = null!;
public int RoleId { get; set; }
public Role Role { get; set; } = null!;
[Required, MaxLength(200)]
public string Title { get; set; } = ""; // عنوان موقعیت
public EmploymentType EmploymentType { get; set; } = EmploymentType.FullTime;
public long? SalaryMin { get; set; } // حقوق ماهانه (تومان)؛ null = توافقی
public long? SalaryMax { get; set; }
[MaxLength(2000)]
public string? Description { get; set; }
[MaxLength(1000)]
public string? Requirements { get; set; } // شرایط احراز
public Gender GenderRequirement { get; set; } = Gender.Any; // جنسیت مورد نیاز
public ShiftStatus Status { get; set; } = ShiftStatus.Open;
public ShiftSource Source { get; set; } = ShiftSource.Admin;
[MaxLength(500)]
public string? SourceUrl { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>Contact channels harvested from the source ad (aggregated openings). When empty, the
/// detail page falls back to the facility's phone.</summary>
public ICollection<ContactMethod> Contacts { get; set; } = new List<ContactMethod>();
// Transient: distance (km) when "near me" is active. Not persisted.
[NotMapped] public double? DistanceKm { get; set; }
}