Add «آماده به کار» (talent) listing type — workers offering themselves
Adds a third listing kind alongside Shift/Job for healthcare staff who advertise their own availability (very common in Iranian medical channels, e.g. "دندانپزشک آماده همکاری… ۵۰٪ تسویه"). These have no facility; the contact phone is the key field. - Model: TalentListing (role, person name, years, licensed, city/district, area note, availability, gender, comp, phone) + ListingKind.Talent + RawListing.LinkedTalentId + DbSet/relations/indexes + EF migration. - Parser: detect آمادهبهکار/جویای کار → Kind=Talent; extract person name, years of experience, licensed flag, area («منطقه ۱»), phone. Facility name extraction now skipped for talent. - Validator: talent path scores role + phone + medical (no facility/pay required). - Ingestion auto-publish: creates a TalentListing for talent kind. - Review (manual publish): Talent option + talent fields; publishes a TalentListing without a facility. Shift/Job facility now falls back to a shared «نامشخص / ثبت نشده» record when the ad names none — publishing never fails on a missing facility. - Browse /Talent (indexable, filters: city/district/role/gender), details /Talent/Details (noindex — personal contact, tel: call button), _TalentCard, badge-talent, nav link, home section. - Sitemap includes /Talent; robots disallows /Talent/Details. Archiver expires stale talent listings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,11 +69,13 @@ public enum EmploymentType
|
||||
Plan = 3 // طرح
|
||||
}
|
||||
|
||||
/// <summary>What an aggregated/raw listing turned out to be — a shift or a hiring opening.</summary>
|
||||
/// <summary>What an aggregated/raw listing turned out to be — a shift, a hiring opening, or a
|
||||
/// worker advertising themselves as available («آماده به کار»).</summary>
|
||||
public enum ListingKind
|
||||
{
|
||||
Shift = 0,
|
||||
Job = 1
|
||||
Job = 1,
|
||||
Talent = 2
|
||||
}
|
||||
|
||||
/// <summary>Which listing types a job alert watches.</summary>
|
||||
|
||||
@@ -24,6 +24,8 @@ public class RawListing
|
||||
public int? LinkedShiftId { get; set; } // شیفت ساختهشده از این آگهی
|
||||
public Shift? LinkedShift { get; set; }
|
||||
|
||||
public int? LinkedTalentId { get; set; } // آگهی «آماده به کار» ساختهشده از این متن
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? SourceUrl { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
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; } // «فقط منطقه ۱» وقتی محله دقیق نگاشت نشد
|
||||
|
||||
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; } // شماره تماس — مهمترین فیلد
|
||||
|
||||
[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; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Transient: distance (km) when "near me" is active. Not persisted.
|
||||
[NotMapped] public double? DistanceKm { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user