diff --git a/Services/EmployeeLookupService.cs b/Services/EmployeeLookupService.cs
index 5551a9d..9dc914f 100644
--- a/Services/EmployeeLookupService.cs
+++ b/Services/EmployeeLookupService.cs
@@ -107,6 +107,35 @@ public class EmployeeLookupService : IEmployeeLookupService
return string.IsNullOrEmpty(raw) ? null : raw;
}
+ ///
+ public async Task IsMenuItemAuthorizedForRfidAsync(string rfid, int menuItemId, CancellationToken cancellationToken = default)
+ {
+ var card = rfid?.Trim() ?? string.Empty;
+ if (string.IsNullOrEmpty(card) || menuItemId <= 0)
+ return false;
+
+ var connectionString = _configService.GetHrmsLookupConnectionString();
+ if (string.IsNullOrWhiteSpace(connectionString))
+ return false;
+
+ const string sql = @"
+ SELECT 1
+ FROM employee_rfid_tag r
+ JOIN employee_menu_item_tag em ON em.employee_rfid_tag_id = r.id
+ WHERE r.manufacturer_serial = @rfid
+ AND em.item_id = @itemId
+ LIMIT 1";
+
+ await using var conn = new MySqlConnection(connectionString);
+ await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
+ await using var cmd = new MySqlCommand(sql, conn);
+ cmd.Parameters.AddWithValue("@rfid", card);
+ cmd.Parameters.AddWithValue("@itemId", menuItemId);
+
+ var exists = await cmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
+ return exists != null && exists != DBNull.Value;
+ }
+
private static string GetString(MySqlDataReader reader, int ordinal)
{
if (reader.IsDBNull(ordinal)) return string.Empty;
diff --git a/Services/IEmployeeLookupService.cs b/Services/IEmployeeLookupService.cs
index b7190c7..cd76b61 100644
--- a/Services/IEmployeeLookupService.cs
+++ b/Services/IEmployeeLookupService.cs
@@ -17,4 +17,9 @@ public interface IEmployeeLookupService
/// Reads employee.location_site_id where employee.serial_number matches the login employee id. Null if missing or not configured.
///
Task GetLocationSiteIdByEmployeeSerialAsync(string employeeSerial, CancellationToken cancellationToken = default);
+
+ ///
+ /// Validates that a menu item is authorized for the employee RFID via employee_menu_item_tag.
+ ///
+ Task IsMenuItemAuthorizedForRfidAsync(string rfid, int menuItemId, CancellationToken cancellationToken = default);
}
diff --git a/Services/RfidService.cs b/Services/RfidService.cs
index 24aea08..6172563 100644
--- a/Services/RfidService.cs
+++ b/Services/RfidService.cs
@@ -333,7 +333,32 @@ public class RfidService : IRfidService
.GetAwaiter()
.GetResult();
- var matching = menuItems.ToList();
+ var matching = menuItems
+ .Where(i => i.MenuItemId > 0)
+ .Where(i =>
+ {
+ try
+ {
+ return _employeeLookup
+ .IsMenuItemAuthorizedForRfidAsync(cardId, i.MenuItemId)
+ .GetAwaiter()
+ .GetResult();
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(ex, $"RfidService.EmployeeMenuValidation card={cardId}, menuItemId={i.MenuItemId}");
+ return false;
+ }
+ })
+ .ToList();
+
+ if (matching.Count == 0)
+ {
+ Logger.Log(
+ new Exception($"No authorized menu item found for card={cardId}, site={siteNumeric}, date={nowLocal:yyyy-MM-dd}, meal={mealLabel}, grade={employee.GradeType}"),
+ "RfidService.ProcessScanDetailed authorization");
+ return new ScanResult(false, "No menu item selected.", 0, employee, session);
+ }
var names = matching
.Select(i => i.ItemName)