Add hiring, AI parser+admin, OTP auth, employer dashboard, profit-share pay

- Hiring (استخدام) listings: JobOpening + /Jobs browse/detail + home section
- Heuristic Persian listing-parser + admin queue (/Admin) → publish shift/job
- Phone-OTP cookie auth + visitor-history linking + profile; Admin role gate
- Employer side: self-serve facility registration, dashboard, post/manage shifts & jobs, applicants list with contact
- Compensation models: fixed / hourly / profit-share (درصدی) / negotiable / choice (به انتخاب شما); SharePercent + JalaliDate.PayLabel; parser + filter

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-03 06:26:54 +03:30
parent 2fb86a435e
commit 563a40d1f4
30 changed files with 1761 additions and 27 deletions
+27 -3
View File
@@ -111,8 +111,25 @@ public static class SeedData
for (var i = 0; i < count; i++)
{
var t = templates[rng.Next(templates.Length)];
var negotiable = rng.Next(0, 4) == 0;
var role = rolePool[rng.Next(rolePool.Length)];
// Vary the compensation model: fixed, profit-share, both (choose), or negotiable.
var payType = PayType.PerShift;
long? amount = t.Item5;
int? share = null;
if (t.Item1 == ShiftType.OnCall) { payType = PayType.Negotiable; amount = null; }
else
{
switch (rng.Next(0, 5))
{
case 0: payType = PayType.Negotiable; amount = null; break; // توافقی
case 1: payType = PayType.Percentage; amount = null; // درصدی
share = rng.Next(0, 2) == 0 ? 50 : 60; break;
case 2: share = rng.Next(0, 2) == 0 ? 40 : 50; break; // مبلغ یا درصد (به انتخاب)
default: break; // مبلغ مقطوع
}
}
shifts.Add(new Shift
{
FacilityId = f.Id,
@@ -123,8 +140,9 @@ public static class SeedData
ShiftType = t.Item1,
SpecialtyRequired = role.Name,
Description = $"{t.Item4} - نیازمند {role.Name} مسلط به امور درمانگاه/اورژانس",
PayType = t.Item1 == ShiftType.OnCall || negotiable ? PayType.Negotiable : PayType.PerShift,
PayAmount = t.Item1 == ShiftType.OnCall || negotiable ? null : t.Item5,
PayType = payType,
PayAmount = amount,
SharePercent = share,
Status = ShiftStatus.Open,
Source = ShiftSource.Admin,
});
@@ -176,6 +194,12 @@ public static class SeedData
SourceChannel = "Divar - استخدام پزشک",
RawText = "بیمارستان خصوصی جهت تکمیل کادر درمان به پزشک عمومی برای شیفت‌های روز نیازمند است.",
Status = RawListingStatus.New,
},
new RawListing
{
SourceChannel = "کانال درمانگاه‌های تهران",
RawText = "درمانگاه شبانه‌روزی نیازمند پزشک عمومی برای شیفت عصر، پرداخت ۵۰٪ سهم درآمد ویزیت. سعادت‌آباد.",
Status = RawListingStatus.New,
});
await db.SaveChangesAsync();
}