Admin: bulk-delete published ingested posts; talent: point to source when no phone
- /Admin/Ingested: "حذف گروهی همهی منتشرشدهها" button removes, in one transaction, every aggregated Shift/Job/Talent published from ingestion plus the approved (Normalized) raw items that produced them. Confirms first and reports counts. Raw rows deleted before the posts (they hold the FKs); DB cascade clears applications/interest events. - Talent details: when the contact number couldn't be extracted (e.g. Divar's login-gated reveal), show a prominent "مشاهده شماره در دیوار/مدجابز ↗" link to the original ad instead of the call button. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,24 @@
|
||||
</div>
|
||||
|
||||
<div class="container section">
|
||||
@if (Model.Message is not null)
|
||||
{
|
||||
<div class="alert alert-success">✓ @Model.Message</div>
|
||||
}
|
||||
|
||||
@{ int publishedCount = Model.Counts.GetValueOrDefault(JobsMedical.Web.Models.RawListingStatus.Normalized); }
|
||||
@if (publishedCount > 0)
|
||||
{
|
||||
<form method="post" asp-page-handler="DeletePublished"
|
||||
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>
|
||||
<span class="muted" style="font-size:12px; margin-inline-start:8px;">آگهیهای منتشرشده روی سایت را که از جمعآوری ساخته شدهاند یکجا حذف میکند.</span>
|
||||
</form>
|
||||
}
|
||||
|
||||
<div class="ing-filters">
|
||||
@Html.Raw(Pill("all", "همه", Model.Counts.Values.Sum()))
|
||||
@Html.Raw(Pill("new", "در صف", C(JobsMedical.Web.Models.RawListingStatus.New)))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using JobsMedical.Web.Data;
|
||||
using JobsMedical.Web.Models;
|
||||
using JobsMedical.Web.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
@@ -18,6 +19,7 @@ public class IngestedModel : PageModel
|
||||
public List<RawListing> Items { get; private set; } = new();
|
||||
public int Total { get; private set; }
|
||||
public Dictionary<RawListingStatus, int> Counts { get; private set; } = new();
|
||||
[TempData] public string? Message { get; set; }
|
||||
|
||||
[BindProperty(SupportsGet = true)] public string? Status { get; set; } // new|flagged|published|discarded|all
|
||||
[BindProperty(SupportsGet = true)] public string? Source { get; set; }
|
||||
@@ -43,4 +45,23 @@ public class IngestedModel : PageModel
|
||||
Total = await q.CountAsync();
|
||||
Items = await q.OrderByDescending(r => r.FetchedAt).Take(200).ToListAsync();
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> OnPostDeletePublishedAsync()
|
||||
{
|
||||
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();
|
||||
|
||||
string P(int n) => JalaliDate.ToPersianDigits(n.ToString());
|
||||
Message = $"حذف شد: {P(shifts)} شیفت، {P(jobs)} استخدام، {P(talent)} آمادهبهکار و {P(raws)} آیتم جمعآوری.";
|
||||
return RedirectToPage(new { Status });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
var digits = new string(t.Phone.Where(char.IsDigit).ToArray());
|
||||
if (digits.Length >= 7) telHref = "tel:" + digits;
|
||||
}
|
||||
// Friendly source name (used to point users to the original ad when no number was extracted).
|
||||
string? sourceName = null;
|
||||
if (!string.IsNullOrWhiteSpace(t.SourceUrl))
|
||||
{
|
||||
var host = System.Uri.TryCreate(t.SourceUrl, UriKind.Absolute, out var su) ? su.Host : t.SourceUrl!;
|
||||
sourceName = host.Contains("divar") ? "دیوار" : host.Contains("medjobs") ? "مدجابز" : host;
|
||||
}
|
||||
}
|
||||
|
||||
<div class="page-head">
|
||||
@@ -68,14 +75,17 @@
|
||||
<a href="@telHref" class="btn btn-accent btn-block btn-lg" dir="ltr">📞 @t.Phone</a>
|
||||
<p class="muted" style="font-size:12px; margin:10px 0 0;">با این فرد مستقیم تماس بگیرید.</p>
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(t.SourceUrl))
|
||||
{
|
||||
@* Number wasn't extractable (e.g. behind a login-gated reveal) — point to the source. *@
|
||||
<p class="muted" style="margin-top:0;">شماره مستقیم استخراج نشد.</p>
|
||||
<a href="@t.SourceUrl" target="_blank" rel="nofollow noopener" class="btn btn-accent btn-block btn-lg">مشاهده شماره در @sourceName ↗</a>
|
||||
<p class="muted" style="font-size:12px; margin:10px 0 0;">این آگهی از @sourceName جمعآوری شده؛ برای دریافت شماره به آگهی اصلی مراجعه کن.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="muted">شماره تماس ثبت نشده است.</p>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(t.SourceUrl))
|
||||
{
|
||||
<a href="@t.SourceUrl" target="_blank" rel="nofollow noopener" class="btn btn-outline btn-block" style="margin-top:8px;">منبع آگهی ↗</a>
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user