Admin: bulk-delete published ingested posts; talent: point to source when no phone
CI/CD / CI · dotnet build (push) Successful in 1m52s
CI/CD / Deploy · hamkadr (push) Successful in 2m41s

- /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:
soroush.asadi
2026-06-08 08:36:12 +03:30
parent a5d6e212e2
commit b092a5cfe5
3 changed files with 53 additions and 4 deletions
@@ -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 });
}
}