[Alerts] Customizable job alerts + Help capabilities showcase
Job alerts (هشدار شغلی): users save what they want — scope (shift/job/both), role, city, shift type, employment type, minimum pay — and get notified when an employer posts a match. New JobAlert model + AlertScope enum + DbContext (user-cascade, role set-null, IsActive index) + migration. /Me/Alerts page to create/pause/delete alerts; entry point added to the کارجو panel. NotificationService.NotifyNewShift/Job now unions preference matches with active-alert matches (deduped) so alert owners are notified on publish. Help page gains an 'امکانات همکادر' capability showcase grid (with a 'ساخت هشدار شغلی' CTA) so the page demonstrates what the app does. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,9 @@ public class NotificationService
|
||||
.FirstOrDefaultAsync(x => x.Id == shiftId);
|
||||
if (s is null) return;
|
||||
|
||||
var users = await MatchingUserIdsAsync(s.RoleId, s.Facility.CityId, s.ShiftType);
|
||||
var prefUsers = await MatchingUserIdsAsync(s.RoleId, s.Facility.CityId, s.ShiftType);
|
||||
var alertUsers = await ShiftAlertUserIdsAsync(s);
|
||||
var users = prefUsers.Union(alertUsers).Distinct().ToList();
|
||||
var title = $"شیفت جدید: {s.Role.Name}";
|
||||
var body = $"{s.Facility.Name} — {JalaliDate.WeekDayName(s.Date)} {JalaliDate.Time(s.StartTime)}";
|
||||
await AddAsync(users, title, body, $"/Shifts/Details/{s.Id}");
|
||||
@@ -57,7 +59,9 @@ public class NotificationService
|
||||
.FirstOrDefaultAsync(x => x.Id == jobId);
|
||||
if (j is null) return;
|
||||
|
||||
var users = await MatchingUserIdsAsync(j.RoleId, j.Facility.CityId, null);
|
||||
var prefUsers = await MatchingUserIdsAsync(j.RoleId, j.Facility.CityId, null);
|
||||
var alertUsers = await JobAlertUserIdsAsync(j);
|
||||
var users = prefUsers.Union(alertUsers).Distinct().ToList();
|
||||
await AddAsync(users, $"استخدام جدید: {j.Title}", j.Facility.Name, $"/Jobs/Details/{j.Id}");
|
||||
}
|
||||
|
||||
@@ -75,6 +79,36 @@ public class NotificationService
|
||||
return await q.Distinct().ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>Owners of active job alerts that match this shift.</summary>
|
||||
private async Task<List<int>> ShiftAlertUserIdsAsync(Shift s)
|
||||
{
|
||||
var cityId = s.Facility.CityId;
|
||||
var districtId = s.Facility.DistrictId;
|
||||
return await _db.JobAlerts.Where(a => a.IsActive
|
||||
&& a.Scope != AlertScope.Jobs
|
||||
&& (a.RoleId == null || a.RoleId == s.RoleId)
|
||||
&& (a.CityId == null || a.CityId == cityId)
|
||||
&& (a.DistrictId == null || a.DistrictId == districtId)
|
||||
&& (a.ShiftType == null || a.ShiftType == s.ShiftType)
|
||||
&& (a.MinPay == null || (s.PayAmount != null && s.PayAmount >= a.MinPay)))
|
||||
.Select(a => a.UserId).Distinct().ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>Owners of active job alerts that match this hiring opening.</summary>
|
||||
private async Task<List<int>> JobAlertUserIdsAsync(JobOpening j)
|
||||
{
|
||||
var cityId = j.Facility.CityId;
|
||||
var districtId = j.Facility.DistrictId;
|
||||
return await _db.JobAlerts.Where(a => a.IsActive
|
||||
&& a.Scope != AlertScope.Shifts
|
||||
&& (a.RoleId == null || a.RoleId == j.RoleId)
|
||||
&& (a.CityId == null || a.CityId == cityId)
|
||||
&& (a.DistrictId == null || a.DistrictId == districtId)
|
||||
&& (a.EmploymentType == null || a.EmploymentType == j.EmploymentType)
|
||||
&& (a.MinPay == null || ((j.SalaryMax ?? j.SalaryMin) != null && (j.SalaryMax ?? j.SalaryMin) >= a.MinPay)))
|
||||
.Select(a => a.UserId).Distinct().ToListAsync();
|
||||
}
|
||||
|
||||
private async Task AddAsync(List<int> userIds, string title, string? body, string url)
|
||||
{
|
||||
if (userIds.Count == 0) return;
|
||||
|
||||
Reference in New Issue
Block a user