feat(audit): show actor full name + role in logs, click to view details
CI/CD / CI · API (dotnet build + test) (push) Successful in 44s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 31s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m11s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 46s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 3m39s

Logs showed the raw User ID (ActorName was almost never stored) and an English
role enum. Now:

- AuditController resolves each entry's actor to the employee's CURRENT full name
  and localized role at read time (joins Employees with IgnoreQueryFilters, so it
  also names soft-deleted staff and fixes all historical rows — no migration).
- The audit table renders "Full name (Role)" with the role localized (fa/en/ar);
  the name is a button that opens an employee-details dialog.
- New EmployeeDetailsDialog: fetches the employee and shows name, role, phone,
  base salary, and an "Open in HR" link; handles removed staff gracefully.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-21 11:24:06 +03:30
parent 6d71770f2e
commit 2a24798a59
6 changed files with 219 additions and 16 deletions
+40 -14
View File
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Meezi.API.Models.Audit;
using Meezi.Core.Authorization;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Meezi.Shared;
@@ -67,25 +68,50 @@ public class AuditController : CafeApiControllerBase
var total = await query.CountAsync(ct);
var items = await query
var rows = await query
.OrderByDescending(x => x.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new AuditLogDto(
x.Id,
x.Category,
x.Action,
x.EntityType,
x.EntityId,
x.BranchId,
x.ActorId,
x.ActorName,
x.ActorRole,
x.Summary,
x.DetailsJson,
x.CreatedAt))
.Select(x => new
{
x.Id, x.Category, x.Action, x.EntityType, x.EntityId, x.BranchId,
x.ActorId, x.ActorName, x.ActorRole, x.Summary, x.DetailsJson, x.CreatedAt
})
.ToListAsync(ct);
// Resolve the actor's CURRENT full name + role from the employee record.
// This fixes historical rows (where ActorName was never stored) and keeps
// names current. IgnoreQueryFilters so we still name soft-deleted staff.
var actorIds = rows
.Where(r => !string.IsNullOrEmpty(r.ActorId))
.Select(r => r.ActorId!)
.Distinct()
.ToList();
var employees = actorIds.Count == 0
? new Dictionary<string, (string Name, EmployeeRole Role)>()
: (await _db.Employees
.IgnoreQueryFilters()
.AsNoTracking()
.Where(e => e.CafeId == cafeId && actorIds.Contains(e.Id))
.Select(e => new { e.Id, e.Name, e.Role })
.ToListAsync(ct))
.ToDictionary(e => e.Id, e => (e.Name, e.Role));
var items = rows.Select(r =>
{
string? name = r.ActorName;
string? role = r.ActorRole;
if (!string.IsNullOrEmpty(r.ActorId) && employees.TryGetValue(r.ActorId, out var emp))
{
name = emp.Name; // prefer the live employee name
role ??= emp.Role.ToString();
}
return new AuditLogDto(
r.Id, r.Category, r.Action, r.EntityType, r.EntityId, r.BranchId,
r.ActorId, name, role, r.Summary, r.DetailsJson, r.CreatedAt);
}).ToList();
return Ok(new PagedApiResponse<AuditLogDto>(true, items, new PagedMeta(total, page, pageSize)));
}
}