Applicants: auto-tags + deep search w/ highlight; never delete (archive instead)
- 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>
This commit is contained in:
@@ -24,13 +24,13 @@
|
||||
@{ int publishedCount = Model.Counts.GetValueOrDefault(JobsMedical.Web.Models.RawListingStatus.Normalized); }
|
||||
@if (publishedCount > 0)
|
||||
{
|
||||
<form method="post" asp-page-handler="DeletePublished"
|
||||
onsubmit="return confirm('همه آگهیهای منتشرشده از جمعآوری (شیفت/استخدام/آمادهبهکار) و آیتمهای تأییدشدهی متناظر حذف میشوند. این کار بازگشتناپذیر است. ادامه میدهی؟');"
|
||||
<form method="post" asp-page-handler="ArchivePublished"
|
||||
onsubmit="return confirm('همه آگهیهای منتشرشده از جمعآوری از سایت پنهان (بایگانی) میشوند. دادهها حذف نمیشوند و برای تحلیل باقی میمانند. ادامه میدهی؟');"
|
||||
style="margin-bottom:14px;">
|
||||
<button type="submit" class="btn btn-outline" style="color:var(--danger); border-color:var(--danger);">
|
||||
🗑 حذف گروهی همهی منتشرشدهها (@JalaliDate.ToPersianDigits(publishedCount.ToString()))
|
||||
<button type="submit" class="btn btn-outline">
|
||||
🗄 بایگانی گروهی همهی منتشرشدهها (@JalaliDate.ToPersianDigits(publishedCount.ToString()))
|
||||
</button>
|
||||
<span class="muted" style="font-size:12px; margin-inline-start:8px;">آگهیهای منتشرشده روی سایت را که از جمعآوری ساخته شدهاند یکجا حذف میکند.</span>
|
||||
<span class="muted" style="font-size:12px; margin-inline-start:8px;">از سایت پنهان میکند ولی هیچچیز حذف نمیشود (آرشیو برای تحلیل).</span>
|
||||
</form>
|
||||
}
|
||||
|
||||
|
||||
@@ -47,21 +47,24 @@ public class IngestedModel : PageModel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bulk-delete everything that was published from ingestion: the aggregated Shift/Job/Talent
|
||||
/// posts on the site AND the approved (Normalized) raw items that produced them. Done in a
|
||||
/// transaction; the linked raw rows are removed first since they hold FKs to the posts.
|
||||
/// ARCHIVE (never delete) everything published from ingestion: the aggregated Shift/Job/Talent
|
||||
/// posts are flipped to Archived (hidden from the site but kept for analytics); the raw crawl
|
||||
/// rows are retained untouched as the permanent archive.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> OnPostDeletePublishedAsync()
|
||||
public async Task<IActionResult> OnPostArchivePublishedAsync()
|
||||
{
|
||||
await using var tx = await _db.Database.BeginTransactionAsync();
|
||||
var raws = await _db.RawListings.Where(r => r.Status == RawListingStatus.Normalized).ExecuteDeleteAsync();
|
||||
var shifts = await _db.Shifts.Where(s => s.Source == ShiftSource.Aggregated).ExecuteDeleteAsync();
|
||||
var jobs = await _db.JobOpenings.Where(j => j.Source == ShiftSource.Aggregated).ExecuteDeleteAsync();
|
||||
var talent = await _db.TalentListings.Where(t => t.Source == ShiftSource.Aggregated).ExecuteDeleteAsync();
|
||||
await tx.CommitAsync();
|
||||
var shifts = await _db.Shifts
|
||||
.Where(s => s.Source == ShiftSource.Aggregated && s.Status != ShiftStatus.Archived)
|
||||
.ExecuteUpdateAsync(u => u.SetProperty(s => s.Status, ShiftStatus.Archived));
|
||||
var jobs = await _db.JobOpenings
|
||||
.Where(j => j.Source == ShiftSource.Aggregated && j.Status != ShiftStatus.Archived)
|
||||
.ExecuteUpdateAsync(u => u.SetProperty(j => j.Status, ShiftStatus.Archived));
|
||||
var talent = await _db.TalentListings
|
||||
.Where(t => t.Source == ShiftSource.Aggregated && t.Status != ShiftStatus.Archived)
|
||||
.ExecuteUpdateAsync(u => u.SetProperty(t => t.Status, ShiftStatus.Archived));
|
||||
|
||||
string P(int n) => JalaliDate.ToPersianDigits(n.ToString());
|
||||
Message = $"حذف شد: {P(shifts)} شیفت، {P(jobs)} استخدام، {P(talent)} آمادهبهکار و {P(raws)} آیتم جمعآوری.";
|
||||
Message = $"بایگانی شد (از سایت پنهان، در پایگاهداده نگهداری شد): {P(shifts)} شیفت، {P(jobs)} استخدام، {P(talent)} آمادهبهکار.";
|
||||
return RedirectToPage(new { Status });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +134,11 @@ public class ReviewModel : PageModel
|
||||
Error = "شهری برای انتشار آگهی «آماده به کار» موجود نیست.";
|
||||
return RedirectToPage(new { id });
|
||||
}
|
||||
// Re-parse the raw text to recover all contact channels (phones/email/socials).
|
||||
// Re-parse the raw text to recover all contact channels (phones/email/socials) + tags.
|
||||
var roleNames = await _db.Roles.Select(r => r.Name).ToListAsync();
|
||||
var parsedContacts = _parser
|
||||
.Parse(Raw.RawText, roleNames, await CityNamesAsync(), await DistrictNamesAsync())
|
||||
.Contacts.Select((c, i) => new ContactMethod { Type = c.Type, Value = c.Value, SortOrder = i })
|
||||
var reparsed = _parser.Parse(Raw.RawText, roleNames, await CityNamesAsync(), await DistrictNamesAsync());
|
||||
var parsedContacts = reparsed.Contacts
|
||||
.Select((c, i) => new ContactMethod { Type = c.Type, Value = c.Value, SortOrder = i })
|
||||
.ToList();
|
||||
// Include the admin-typed phone if it isn't already captured.
|
||||
if (!string.IsNullOrWhiteSpace(Phone))
|
||||
@@ -167,6 +167,7 @@ public class ReviewModel : PageModel
|
||||
Source = ShiftSource.Aggregated,
|
||||
SourceUrl = Raw.SourceUrl,
|
||||
Contacts = parsedContacts,
|
||||
Tags = string.Join(" ", reparsed.Tags.Distinct()),
|
||||
};
|
||||
_db.TalentListings.Add(talent);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
Reference in New Issue
Block a user