Files
hamkadr/src/JobsMedical.Web/Models/TalentListing.cs
T
soroush.asadi 4ab6ce29c9
CI/CD / CI · dotnet build (push) Successful in 1m59s
CI/CD / Deploy · hamkadr (push) Successful in 1m49s
Approximate-location map on aggregated listings (Divar coords)
We captured Divar's privacy-fuzzed coords on RawListing but discarded them for
the listings that need them: unnamed-facility shifts/jobs dropped them (to avoid
piling on the shared placeholder) and applicants had no coordinate field at all.

- Add Lat/Lng to Shift, JobOpening, TalentListing (migration ListingApproxCoords).
- Publish stores the source ad's approx coords on each aggregated listing.
- Detail pages render the map from the listing's own coords (fallback: facility),
  and aggregated coords show as a shaded «محدودهٔ تقریبی» circle (not a precise
  pin) via _NeshanMap data-approx, with a disclaimer. Applicants get a map card
  (they had none) + the page now loads the Neshan key.

Only Divar provides coords; the map needs NeshanMapKey set in admin settings.
Existing rows get coords once reprocessed (RawListing already has them).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 15:10:05 +03:30

72 lines
3.1 KiB
C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace JobsMedical.Web.Models;
/// <summary>
/// «آماده به کار» — a healthcare worker advertising *themselves* as available for work
/// (the supply side), as opposed to a <see cref="Shift"/>/<see cref="JobOpening"/> posted by a
/// facility (the demand side). Very common in Iranian medical channels ("پرستار آماده همکاری…").
/// There is no facility; the valuable field is the contact <see cref="Phone"/>.
/// </summary>
public class TalentListing
{
public int Id { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; } = null!;
[MaxLength(150)]
public string? PersonName { get; set; } // «دکتر سپیده علیزاده» (best-effort)
public int? YearsExperience { get; set; } // سابقه (سال)
public bool IsLicensed { get; set; } // پروانه‌دار / دارای پروانه
public int CityId { get; set; }
public City City { get; set; } = null!;
public int? DistrictId { get; set; }
public District? District { get; set; }
[MaxLength(150)]
public string? AreaNote { get; set; } // «فقط منطقه ۱» وقتی محله دقیق نگاشت نشد
/// <summary>Searchable keyword tags (space-separated): certs/skills (mmt, icu…), پروانه‌دار,
/// role, city. Drives deep search + tag chips.</summary>
[MaxLength(500)]
public string? Tags { get; set; }
public EmploymentType? Availability { get; set; } // تمام‌وقت/پاره‌وقت/قراردادی...
public Gender Gender { get; set; } = Gender.Any; // جنسیت فرد
// Expected compensation — reuses the shift/job comp model.
public PayType PayType { get; set; } = PayType.Negotiable;
public long? PayAmount { get; set; } // مبلغ مدنظر (تومان)
public int? SharePercent { get; set; } // درصد/سهم درآمد مدنظر («۵۰٪ تسویه»)
[MaxLength(30)]
public string? Phone { get; set; } // primary phone (kept for cards/back-compat)
/// <summary>All contact channels (phones, email, Instagram, Telegram, Bale, website…).</summary>
public ICollection<ContactMethod> Contacts { get; set; } = new List<ContactMethod>();
[MaxLength(2000)]
public string? Description { get; set; }
public ShiftStatus Status { get; set; } = ShiftStatus.Open;
public ShiftSource Source { get; set; } = ShiftSource.Admin;
[MaxLength(500)]
public string? SourceUrl { get; set; }
// APPROXIMATE coords from the source ad (Divar) — an applicant has no facility, so this is the
// only location we have. Shown as a «محدودهٔ تقریبی» circle (the area they're available in).
public double? Lat { get; set; }
public double? Lng { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Transient: distance (km) when "near me" is active. Not persisted.
[NotMapped] public double? DistanceKm { get; set; }
}