An ad can cover several roles (e.g. «پرستار سالمند و کودک و همراه بیمار»).
The role dropdown is now a checkbox multi-select; on publish we fan out and
create one Shift/Job/Talent per selected role (mirrors the auto-ingest
fan-out). Jobs get a per-role title when multiple are chosen; talent
listings each get their own contact rows; all created items notify matches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Tags: parser extracts cert/skill keywords (mmt, ICU/CCU, دیالیز, اتاق عمل,
اورژانس, مسئول فنی, پروانهدار…) + role + city into TalentListing.Tags
(+ migration); shown as chips on cards.
- Deep search on /Talent: «جستجوی عمیق» box does Postgres ILIKE across
tags, description, person, area, role, city (every term must match);
matches are highlighted with <mark> via SearchHighlight.
- Never delete: ShiftStatus.Archived + the admin «بایگانی گروهی» action now
ARCHIVES aggregated posts (hidden from site, kept in DB) and leaves the
raw crawl rows intact — a permanent archive for future analytics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ContactMethod entity (Type + Value + SortOrder) 1→N on TalentListing (+ migration).
- Parser extracts ALL contacts: multiple phones + landlines, email, and
socials (Instagram/Telegram/Bale/WhatsApp/website) via URLs and Persian
keyword cues; primary Phone kept for cards.
- ContactInfo helper: per-type label/icon/clickable href (tel:/mailto:/t.me/…).
- Ingestion attaches contacts to each (fanned-out) talent listing; manual
Review re-parses to attach them + the admin-typed phone.
- Talent details renders the full contact list as buttons; falls back to the
single phone, then the Divar source link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
When publishing a scraped listing we now look for a facility we already
have that is exactly or closely the same, and only create a new one when
there is no match — avoiding duplicates like «بیمارستان میلاد» vs «میلاد».
- ListingParser: extract a facility name (keyword + distinctive words) from
the post and surface it in the parser notes.
- FacilityMatcher: Persian-aware normalization (ي/ك, ZWNJ, punctuation),
type-word stripping for a "core" name, contains + Levenshtein similarity,
and FindBest (same-city exact → any-city exact → same-city fuzzy → fuzzy).
- Review (manual publish): auto-select a matching facility or prefill the
new-facility name; resolve-or-create uses fuzzy match; dropdown preselects.
- IngestionService (auto-publish): reuse FacilityMatcher against a run-wide
facility list (grows as new ones are created) instead of exact-name only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OnPostPublishAsync inserted a Shift/Job with FacilityId=0 when no
facility was selected (e.g. the dropdown is empty because no facilities
exist yet), throwing FK_Shifts_Facilities_FacilityId and surfacing the
production error page.
- Resolve-or-create the facility before insert: use the picked one, else
create an unverified Facility from a typed name (reusing same-named).
- Guard the role too; on missing facility/role redirect back with a
Persian error message instead of 500.
- Review form: add "new facility name" input + "— none —" option +
error alert; add .alert-error style.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Notification entity + NotificationService: on publish, notify users whose saved prefs match the listing (role/city/+shift type); users with no preference aren't spammed
- Wired into PostShift, PostJob, and Admin Review publish
- 🔔 bell with unread count in the header (@inject) + /Me/Notifications page (mark-all-read on open)
- Reliable in-app delivery (works in Iran without FCM); Web Push can ride the same records later
- Verified: employee pref → employer posts matching shift → employee bell=۱ + 'شیفت جدید: پزشک عمومی'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>