Implement meal session filtering in menu lookup
- Added mealName parameter to IMenuLookupService.GetMenuItemsForSiteAndDateAsync - Updated SQL query to filter by li.meal_name in MenuLookupService - Added meal_name to SELECT and result mapping - Updated RfidService to pass mealLabel from resolved session - Maintained backward compatibility with existing methodpull/5/head
parent
3498b7be43
commit
92cdbc004b
|
|
@ -20,5 +20,5 @@ public interface IMenuLookupService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
//Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default);
|
//Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default);
|
Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, string mealName ,CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,16 +147,77 @@ public class MenuLookupService : IMenuLookupService
|
||||||
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAsync(int siteIdNumeric, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAsync(int siteIdNumeric, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Backwards-compatible: use today's local date.
|
// Backwards-compatible: use today's local date.
|
||||||
return await GetMenuItemsForSiteAndDateAsync(siteIdNumeric, DateTime.Today, "NonManagement", cancellationToken).ConfigureAwait(false);
|
return await GetMenuItemsForSiteAndDateAsync(siteIdNumeric, DateTime.Today, "NonManagement","no-active-meal-session", cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementing the method for fetching menu items by site, date, and gradeType
|
// Implementing the method for fetching menu items by site, date, and gradeType
|
||||||
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default)
|
//public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default)
|
||||||
|
//{
|
||||||
|
// var connectionString = _configService.GetHrmsLookupConnectionString();
|
||||||
|
// if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
// return Array.Empty<HrmsMenuItem>();
|
||||||
|
|
||||||
|
// const string sql = @"
|
||||||
|
// SELECT
|
||||||
|
// mi.id,
|
||||||
|
// mi.item_name,
|
||||||
|
// mi.item_type,
|
||||||
|
// mi.price,
|
||||||
|
// li.menu_date,
|
||||||
|
// li.day_of_week
|
||||||
|
// FROM lunch_menu_week w
|
||||||
|
// JOIN lunch_menu_item li ON li.lunch_menu_week_id = w.id
|
||||||
|
// JOIN menu_item mi ON mi.id = li.menu_item_id
|
||||||
|
// WHERE w.location_site_id = @siteId
|
||||||
|
// AND @menuDate BETWEEN w.week_start_date AND w.week_end_date
|
||||||
|
// AND li.menu_date = @menuDate
|
||||||
|
// AND mi.item_for = @itemFor
|
||||||
|
// ORDER BY mi.item_name;";
|
||||||
|
|
||||||
|
// var itemFor = GetItemForFromGradeType(gradeType);
|
||||||
|
|
||||||
|
// await using var conn = new MySqlConnection(connectionString);
|
||||||
|
// await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// await using var cmd = new MySqlCommand(sql, conn);
|
||||||
|
// cmd.Parameters.AddWithValue("@siteId", siteIdNumeric);
|
||||||
|
// cmd.Parameters.AddWithValue("@menuDate", menuDateLocal.Date);
|
||||||
|
// cmd.Parameters.AddWithValue("@itemFor", itemFor);
|
||||||
|
|
||||||
|
// var list = new List<HrmsMenuItem>();
|
||||||
|
// await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||||
|
// {
|
||||||
|
// list.Add(new HrmsMenuItem
|
||||||
|
// {
|
||||||
|
// MenuItemId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0),
|
||||||
|
// ItemName = GetString(reader, 1),
|
||||||
|
// ItemType = GetString(reader, 2),
|
||||||
|
// Price = GetDecimal(reader, 3),
|
||||||
|
// MenuDate = GetString(reader, 4),
|
||||||
|
// DayOfWeek = GetString(reader, 5),
|
||||||
|
// MealName = string.Empty
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return list;
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(
|
||||||
|
int siteIdNumeric,
|
||||||
|
DateTime menuDateLocal,
|
||||||
|
string gradeType,
|
||||||
|
string mealName, // Add mealName parameter
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var connectionString = _configService.GetHrmsLookupConnectionString();
|
var connectionString = _configService.GetHrmsLookupConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
return Array.Empty<HrmsMenuItem>();
|
return Array.Empty<HrmsMenuItem>();
|
||||||
|
|
||||||
|
// Updated SQL to include meal_name filter and select meal_name
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT
|
SELECT
|
||||||
mi.id,
|
mi.id,
|
||||||
|
|
@ -164,13 +225,15 @@ public class MenuLookupService : IMenuLookupService
|
||||||
mi.item_type,
|
mi.item_type,
|
||||||
mi.price,
|
mi.price,
|
||||||
li.menu_date,
|
li.menu_date,
|
||||||
li.day_of_week
|
li.day_of_week,
|
||||||
|
li.meal_name -- Add meal_name to SELECT
|
||||||
FROM lunch_menu_week w
|
FROM lunch_menu_week w
|
||||||
JOIN lunch_menu_item li ON li.lunch_menu_week_id = w.id
|
JOIN lunch_menu_item li ON li.lunch_menu_week_id = w.id
|
||||||
JOIN menu_item mi ON mi.id = li.menu_item_id
|
JOIN menu_item mi ON mi.id = li.menu_item_id
|
||||||
WHERE w.location_site_id = @siteId
|
WHERE w.location_site_id = @siteId
|
||||||
AND @menuDate BETWEEN w.week_start_date AND w.week_end_date
|
AND @menuDate BETWEEN w.week_start_date AND w.week_end_date
|
||||||
AND li.menu_date = @menuDate
|
AND li.menu_date = @menuDate
|
||||||
|
AND li.meal_name = @mealName -- Filter by meal session
|
||||||
AND mi.item_for = @itemFor
|
AND mi.item_for = @itemFor
|
||||||
ORDER BY mi.item_name;";
|
ORDER BY mi.item_name;";
|
||||||
|
|
||||||
|
|
@ -181,6 +244,7 @@ public class MenuLookupService : IMenuLookupService
|
||||||
await using var cmd = new MySqlCommand(sql, conn);
|
await using var cmd = new MySqlCommand(sql, conn);
|
||||||
cmd.Parameters.AddWithValue("@siteId", siteIdNumeric);
|
cmd.Parameters.AddWithValue("@siteId", siteIdNumeric);
|
||||||
cmd.Parameters.AddWithValue("@menuDate", menuDateLocal.Date);
|
cmd.Parameters.AddWithValue("@menuDate", menuDateLocal.Date);
|
||||||
|
cmd.Parameters.AddWithValue("@mealName", mealName); // Add mealName parameter
|
||||||
cmd.Parameters.AddWithValue("@itemFor", itemFor);
|
cmd.Parameters.AddWithValue("@itemFor", itemFor);
|
||||||
|
|
||||||
var list = new List<HrmsMenuItem>();
|
var list = new List<HrmsMenuItem>();
|
||||||
|
|
@ -195,13 +259,12 @@ public class MenuLookupService : IMenuLookupService
|
||||||
Price = GetDecimal(reader, 3),
|
Price = GetDecimal(reader, 3),
|
||||||
MenuDate = GetString(reader, 4),
|
MenuDate = GetString(reader, 4),
|
||||||
DayOfWeek = GetString(reader, 5),
|
DayOfWeek = GetString(reader, 5),
|
||||||
MealName = string.Empty
|
MealName = GetString(reader, 6) // Map meal_name from column index 6
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetItemForFromGradeType(string? gradeType)
|
private static string GetItemForFromGradeType(string? gradeType)
|
||||||
{
|
{
|
||||||
var value = (gradeType ?? string.Empty).Trim().ToLowerInvariant();
|
var value = (gradeType ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,7 @@ public class RfidService : IRfidService
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ScanResult ProcessScanDetailed(string cardId)
|
public ScanResult ProcessScanDetailed(string cardId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(cardId))
|
if (string.IsNullOrWhiteSpace(cardId))
|
||||||
|
|
@ -326,8 +327,9 @@ public class RfidService : IRfidService
|
||||||
{
|
{
|
||||||
if (int.TryParse(new string(siteIdForSession.Where(char.IsDigit).ToArray()), out var siteNumeric) && siteNumeric > 0)
|
if (int.TryParse(new string(siteIdForSession.Where(char.IsDigit).ToArray()), out var siteNumeric) && siteNumeric > 0)
|
||||||
{
|
{
|
||||||
|
// Pass mealLabel as the mealName parameter
|
||||||
var menuItems = _menuLookup
|
var menuItems = _menuLookup
|
||||||
.GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date, employee.GradeType)
|
.GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date, employee.GradeType, mealLabel)
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
|
|
@ -381,6 +383,193 @@ public class RfidService : IRfidService
|
||||||
return new ScanResult(true, "Order recorded successfully.", 0, employee, session, normalizedEmployeeSite, normalizedCurrentSite);
|
return new ScanResult(true, "Order recorded successfully.", 0, employee, session, normalizedEmployeeSite, normalizedCurrentSite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//public ScanResult ProcessScanDetailed(string cardId)
|
||||||
|
//{
|
||||||
|
// if (string.IsNullOrWhiteSpace(cardId))
|
||||||
|
// return new ScanResult(false, "Card ID cannot be empty.", 0);
|
||||||
|
|
||||||
|
// cardId = cardId.Trim();
|
||||||
|
|
||||||
|
// // HRMS lookup: reject if card not registered
|
||||||
|
// var employee = _employeeLookup.GetEmployeeByRfidAsync(cardId).GetAwaiter().GetResult();
|
||||||
|
// if (employee == null)
|
||||||
|
// return new ScanResult(false, "Card not registered in HRMS.", 0);
|
||||||
|
|
||||||
|
// var nowUtc = DateTime.UtcNow;
|
||||||
|
// var nowLocal = DateTime.Now;
|
||||||
|
|
||||||
|
// // SITE VERIFICATION: Check if employee is assigned to this site
|
||||||
|
// var currentSiteId = _configService.GetSiteId();
|
||||||
|
// var normalizedCurrentSite = NormalizeSiteId(currentSiteId);
|
||||||
|
|
||||||
|
// var employeeSiteId = !string.IsNullOrWhiteSpace(employee.LocationSiteId)
|
||||||
|
// ? employee.LocationSiteId.Trim()
|
||||||
|
// : string.Empty;
|
||||||
|
// var normalizedEmployeeSite = NormalizeSiteId(employeeSiteId);
|
||||||
|
|
||||||
|
// // If employee has a site assigned, verify it matches the current system
|
||||||
|
// if (!string.IsNullOrWhiteSpace(normalizedEmployeeSite))
|
||||||
|
// {
|
||||||
|
// if (!string.Equals(normalizedEmployeeSite, normalizedCurrentSite, StringComparison.OrdinalIgnoreCase))
|
||||||
|
// {
|
||||||
|
// var message = $"Employee from site {normalizedEmployeeSite} is not allowed to scan here. " +
|
||||||
|
// $"Only employees from site {normalizedCurrentSite} can scan.";
|
||||||
|
// return new ScanResult(
|
||||||
|
// false,
|
||||||
|
// message,
|
||||||
|
// 0,
|
||||||
|
// employee, // Include employee info so UI can show who tried to scan
|
||||||
|
// MealSession.None,
|
||||||
|
// normalizedEmployeeSite,
|
||||||
|
// normalizedCurrentSite);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// // Employee has no site assigned - log warning but allow scan
|
||||||
|
// // You can change this to block if required by commenting out the next line
|
||||||
|
// Logger.Log(
|
||||||
|
// new Exception($"Site Validation - Card: {cardId}, Employee Site: '{employeeSiteId}', Config Site: '{currentSiteId}'"),
|
||||||
|
// "RfidService");
|
||||||
|
|
||||||
|
// // If you want to BLOCK employees with no site, uncomment the following:
|
||||||
|
// /*
|
||||||
|
// return new ScanResult(
|
||||||
|
// false,
|
||||||
|
// "Employee has no site assignment. Please contact administrator.",
|
||||||
|
// 0,
|
||||||
|
// employee,
|
||||||
|
// MealSession.None,
|
||||||
|
// "UNASSIGNED",
|
||||||
|
// normalizedCurrentSite);
|
||||||
|
// */
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Continue with meal session validation
|
||||||
|
// var siteIdForSession = !string.IsNullOrWhiteSpace(employee.LocationSiteId)
|
||||||
|
// ? employee.LocationSiteId.Trim()
|
||||||
|
// : _configService.GetSiteId();
|
||||||
|
|
||||||
|
// var resolvedSession = _mealSessionResolver.GetCurrentSession(nowLocal, siteIdForSession);
|
||||||
|
// if (resolvedSession == null || resolvedSession.Session == MealSession.None)
|
||||||
|
// return new ScanResult(false, "This scan is outside of valid meal timings.", 0, employee);
|
||||||
|
|
||||||
|
// var session = resolvedSession.Session;
|
||||||
|
// var sessionCode = (int)session;
|
||||||
|
|
||||||
|
// using var db = _dbFactory.CreateDbContext();
|
||||||
|
|
||||||
|
// // Once-per-session-per-day rule: same card, same session, same local day is not allowed.
|
||||||
|
// var startOfTodayLocal = DateTime.Today;
|
||||||
|
// var startOfTomorrowLocal = startOfTodayLocal.AddDays(1);
|
||||||
|
// var startUtc = startOfTodayLocal.ToUniversalTime();
|
||||||
|
// var endUtc = startOfTomorrowLocal.ToUniversalTime();
|
||||||
|
|
||||||
|
// var alreadyScannedThisSessionToday = db.LunchOrderTransactions
|
||||||
|
// .Where(r => r.CardId == cardId)
|
||||||
|
// .Where(r => r.MealSessionCode == sessionCode)
|
||||||
|
// .Where(r => r.ScanTime >= startUtc && r.ScanTime < endUtc)
|
||||||
|
// .OrderByDescending(r => r.ScanTime)
|
||||||
|
// .FirstOrDefault();
|
||||||
|
|
||||||
|
// if (alreadyScannedThisSessionToday != null)
|
||||||
|
// {
|
||||||
|
// var sessionName = resolvedSession.MealName;
|
||||||
|
// return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0, employee, session);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var interval = _configService.GetScanInterval();
|
||||||
|
// var timeoutSeconds = (int)Math.Max(1, interval.TotalSeconds);
|
||||||
|
|
||||||
|
// var windowStart = nowUtc.AddSeconds(-timeoutSeconds);
|
||||||
|
|
||||||
|
// // Safety debounce: short cooldown per card to prevent accidental double-tap.
|
||||||
|
// var lastInWindow = db.LunchOrderTransactions
|
||||||
|
// .Where(r => r.CardId == cardId)
|
||||||
|
// .Where(r => r.ScanTime >= windowStart)
|
||||||
|
// .OrderByDescending(r => r.ScanTime)
|
||||||
|
// .FirstOrDefault();
|
||||||
|
|
||||||
|
// if (lastInWindow != null)
|
||||||
|
// {
|
||||||
|
// var remaining = GetCooldownRemainingSeconds(nowUtc, lastInWindow.ScanTime, timeoutSeconds);
|
||||||
|
// return new ScanResult(
|
||||||
|
// false,
|
||||||
|
// $"One order per customer within {FormatTimeout(timeoutSeconds)}. Ask this customer to rescan after countdown.",
|
||||||
|
// remaining,
|
||||||
|
// employee,
|
||||||
|
// session);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var fullName = string.Join(" ", new[] { employee.FirstName, employee.MiddleName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||||
|
// var siteId = !string.IsNullOrWhiteSpace(employee.LocationSiteId)
|
||||||
|
// ? employee.LocationSiteId.Trim()
|
||||||
|
// : _configService.GetSiteId();
|
||||||
|
|
||||||
|
// // Resolve menu items for this scan
|
||||||
|
// var mealLabel = resolvedSession.MealName;
|
||||||
|
// var mealItemsDisplay = string.Empty;
|
||||||
|
// double totalPrice = 0;
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// if (int.TryParse(new string(siteIdForSession.Where(char.IsDigit).ToArray()), out var siteNumeric) && siteNumeric > 0)
|
||||||
|
// {
|
||||||
|
// var menuItems = _menuLookup
|
||||||
|
// .GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date, employee.GradeType)
|
||||||
|
// .GetAwaiter()
|
||||||
|
// .GetResult();
|
||||||
|
|
||||||
|
// var matching = menuItems.ToList();
|
||||||
|
|
||||||
|
// var names = matching
|
||||||
|
// .Select(i => i.ItemName)
|
||||||
|
// .Where(n => !string.IsNullOrWhiteSpace(n))
|
||||||
|
// .Distinct()
|
||||||
|
// .ToList();
|
||||||
|
|
||||||
|
// if (names.Count > 0)
|
||||||
|
// {
|
||||||
|
// mealItemsDisplay = string.Join(" + ", names);
|
||||||
|
// totalPrice = matching.Sum(i => (double)i.Price);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// catch (Exception ex)
|
||||||
|
// {
|
||||||
|
// Logger.Log(ex, "RfidService.ProcessScanDetailed menu lookup");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var record = new ScanRecord
|
||||||
|
// {
|
||||||
|
// CardId = cardId,
|
||||||
|
// ScanTime = nowUtc,
|
||||||
|
// IsSynced = false,
|
||||||
|
// SiteId = siteId,
|
||||||
|
// DeviceId = _configService.GetDeviceId(),
|
||||||
|
// IpAddress = GetLocalIpAddress(),
|
||||||
|
// MealSessionCode = sessionCode,
|
||||||
|
// ParentDocumentId = employee.ParentDocumentId ?? string.Empty,
|
||||||
|
// EmployeeId = employee.EmployeeId ?? string.Empty,
|
||||||
|
// UindSerial = employee.UindSerial ?? string.Empty,
|
||||||
|
// FunctionId = employee.FunctionId,
|
||||||
|
// DepartmentId = employee.DepartmentId,
|
||||||
|
// TagCreatedAtUtc = employee.TagCreatedAtUtc,
|
||||||
|
// TagCreatedBy = employee.TagCreatedBy ?? string.Empty,
|
||||||
|
// EmployeeName = string.IsNullOrEmpty(fullName) ? string.Empty : fullName,
|
||||||
|
// Department = employee.DepartmentTitle ?? string.Empty,
|
||||||
|
// DepartmentType = employee.DepartmentType ?? string.Empty,
|
||||||
|
// MealLabel = mealLabel,
|
||||||
|
// MealItems = mealItemsDisplay,
|
||||||
|
// TotalPrice = totalPrice,
|
||||||
|
// grade_type = employee.GradeType ?? string.Empty
|
||||||
|
// };
|
||||||
|
// db.LunchOrderTransactions.Add(record);
|
||||||
|
// db.SaveChanges();
|
||||||
|
|
||||||
|
// return new ScanResult(true, "Order recorded successfully.", 0, employee, session, normalizedEmployeeSite, normalizedCurrentSite);
|
||||||
|
//}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalizes a site ID to a consistent format for comparison
|
/// Normalizes a site ID to a consistent format for comparison
|
||||||
/// Handles formats like "1", "01", "SITE : 1", "Site 01", etc.
|
/// Handles formats like "1", "01", "SITE : 1", "Site 01", etc.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue