commit
390361ce83
|
|
@ -26,6 +26,9 @@ appsettings.json
|
||||||
# Publish
|
# Publish
|
||||||
publish/
|
publish/
|
||||||
|
|
||||||
|
# Publish Profiles
|
||||||
|
Properties/PublishProfiles/
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<ApplicationRevision>0</ApplicationRevision>
|
|
||||||
<ApplicationVersion>1.0.0.*</ApplicationVersion>
|
|
||||||
<BootstrapperEnabled>True</BootstrapperEnabled>
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<CreateWebPageOnPublish>False</CreateWebPageOnPublish>
|
|
||||||
<GenerateManifests>true</GenerateManifests>
|
|
||||||
<Install>True</Install>
|
|
||||||
<InstallFrom>Disk</InstallFrom>
|
|
||||||
<IsRevisionIncremented>True</IsRevisionIncremented>
|
|
||||||
<IsWebBootstrapper>False</IsWebBootstrapper>
|
|
||||||
<MapFileExtensions>True</MapFileExtensions>
|
|
||||||
<OpenBrowserOnPublish>False</OpenBrowserOnPublish>
|
|
||||||
<Platform>Any CPU</Platform>
|
|
||||||
<PublishDir>bin\Release\net8.0-windows\win-x86\app.publish\</PublishDir>
|
|
||||||
<PublishUrl>D:\Publish\UtopiaCanteenSystem\</PublishUrl>
|
|
||||||
<PublishProtocol>ClickOnce</PublishProtocol>
|
|
||||||
<PublishReadyToRun>False</PublishReadyToRun>
|
|
||||||
<PublishSingleFile>False</PublishSingleFile>
|
|
||||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
|
||||||
<SelfContained>True</SelfContained>
|
|
||||||
<SignatureAlgorithm>(none)</SignatureAlgorithm>
|
|
||||||
<SignManifests>False</SignManifests>
|
|
||||||
<SkipPublishVerification>false</SkipPublishVerification>
|
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
|
||||||
<UpdateEnabled>False</UpdateEnabled>
|
|
||||||
<UpdateMode>Foreground</UpdateMode>
|
|
||||||
<UpdateRequired>False</UpdateRequired>
|
|
||||||
<WebPageFileName>Publish.html</WebPageFileName>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>Any CPU</Platform>
|
|
||||||
<PublishDir>D:\</PublishDir>
|
|
||||||
<PublishProtocol>FileSystem</PublishProtocol>
|
|
||||||
<_TargetId>Folder</_TargetId>
|
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
|
||||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
|
||||||
<SelfContained>true</SelfContained>
|
|
||||||
<PublishSingleFile>false</PublishSingleFile>
|
|
||||||
<PublishReadyToRun>false</PublishReadyToRun>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
|
|
@ -42,6 +42,159 @@ public class RfidService : IRfidService
|
||||||
return (result.Success, result.Message);
|
return (result.Success, result.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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;
|
||||||
|
|
||||||
|
// // Phase 2.5: DB-driven meal windows (MealSchedules table). Reject if no matching session.
|
||||||
|
// var siteIdForSession = !string.IsNullOrWhiteSpace(employee.LocationSiteId)
|
||||||
|
// ? employee.LocationSiteId.Trim()
|
||||||
|
// : _configService.GetSiteId();
|
||||||
|
// //var session = _mealSessionResolver.GetCurrentSession(nowLocal, siteIdForSession);
|
||||||
|
// //if (session == MealSession.None)
|
||||||
|
// // return new ScanResult(false, "This scan is outside of valid meal timings.", 0);
|
||||||
|
|
||||||
|
// //var sessionCode = (int)session;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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 = GetMealSessionDisplayName(session);
|
||||||
|
// // return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0);
|
||||||
|
// //}
|
||||||
|
// if (alreadyScannedThisSessionToday != null)
|
||||||
|
// {
|
||||||
|
// //var sessionName = session.ToString();
|
||||||
|
// var sessionName = resolvedSession.MealName;
|
||||||
|
// return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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 (store on record so order history shows actual items)
|
||||||
|
// //var mealLabel = GetMealSessionDisplayName(session);
|
||||||
|
// //var mealLabel = session.ToString();
|
||||||
|
// 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)
|
||||||
|
// .GetAwaiter()
|
||||||
|
// .GetResult();
|
||||||
|
|
||||||
|
// //var matching = menuItems
|
||||||
|
// // .Where(i => string.Equals(i.MealName, mealLabel, StringComparison.OrdinalIgnoreCase))
|
||||||
|
// // .ToList();
|
||||||
|
// 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
|
||||||
|
// };
|
||||||
|
// db.LunchOrderTransactions.Add(record);
|
||||||
|
// db.SaveChanges();
|
||||||
|
|
||||||
|
// return new ScanResult(true, "Order recorded successfully.", 0, employee, session);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
public ScanResult ProcessScanDetailed(string cardId)
|
public ScanResult ProcessScanDetailed(string cardId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(cardId))
|
if (string.IsNullOrWhiteSpace(cardId))
|
||||||
|
|
@ -57,19 +210,61 @@ public class RfidService : IRfidService
|
||||||
var nowUtc = DateTime.UtcNow;
|
var nowUtc = DateTime.UtcNow;
|
||||||
var nowLocal = DateTime.Now;
|
var nowLocal = DateTime.Now;
|
||||||
|
|
||||||
// Phase 2.5: DB-driven meal windows (MealSchedules table). Reject if no matching session.
|
// 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)
|
var siteIdForSession = !string.IsNullOrWhiteSpace(employee.LocationSiteId)
|
||||||
? employee.LocationSiteId.Trim()
|
? employee.LocationSiteId.Trim()
|
||||||
: _configService.GetSiteId();
|
: _configService.GetSiteId();
|
||||||
//var session = _mealSessionResolver.GetCurrentSession(nowLocal, siteIdForSession);
|
|
||||||
//if (session == MealSession.None)
|
|
||||||
// return new ScanResult(false, "This scan is outside of valid meal timings.", 0);
|
|
||||||
|
|
||||||
//var sessionCode = (int)session;
|
|
||||||
|
|
||||||
var resolvedSession = _mealSessionResolver.GetCurrentSession(nowLocal, siteIdForSession);
|
var resolvedSession = _mealSessionResolver.GetCurrentSession(nowLocal, siteIdForSession);
|
||||||
if (resolvedSession == null || resolvedSession.Session == MealSession.None)
|
if (resolvedSession == null || resolvedSession.Session == MealSession.None)
|
||||||
return new ScanResult(false, "This scan is outside of valid meal timings.", 0);
|
return new ScanResult(false, "This scan is outside of valid meal timings.", 0, employee);
|
||||||
|
|
||||||
var session = resolvedSession.Session;
|
var session = resolvedSession.Session;
|
||||||
var sessionCode = (int)session;
|
var sessionCode = (int)session;
|
||||||
|
|
@ -89,16 +284,10 @@ public class RfidService : IRfidService
|
||||||
.OrderByDescending(r => r.ScanTime)
|
.OrderByDescending(r => r.ScanTime)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
//if (alreadyScannedThisSessionToday != null)
|
|
||||||
//{
|
|
||||||
// var sessionName = GetMealSessionDisplayName(session);
|
|
||||||
// return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0);
|
|
||||||
//}
|
|
||||||
if (alreadyScannedThisSessionToday != null)
|
if (alreadyScannedThisSessionToday != null)
|
||||||
{
|
{
|
||||||
//var sessionName = session.ToString();
|
|
||||||
var sessionName = resolvedSession.MealName;
|
var sessionName = resolvedSession.MealName;
|
||||||
return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0);
|
return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0, employee, session);
|
||||||
}
|
}
|
||||||
|
|
||||||
var interval = _configService.GetScanInterval();
|
var interval = _configService.GetScanInterval();
|
||||||
|
|
@ -119,7 +308,9 @@ public class RfidService : IRfidService
|
||||||
return new ScanResult(
|
return new ScanResult(
|
||||||
false,
|
false,
|
||||||
$"One order per customer within {FormatTimeout(timeoutSeconds)}. Ask this customer to rescan after countdown.",
|
$"One order per customer within {FormatTimeout(timeoutSeconds)}. Ask this customer to rescan after countdown.",
|
||||||
remaining);
|
remaining,
|
||||||
|
employee,
|
||||||
|
session);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullName = string.Join(" ", new[] { employee.FirstName, employee.MiddleName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
var fullName = string.Join(" ", new[] { employee.FirstName, employee.MiddleName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||||
|
|
@ -127,9 +318,7 @@ public class RfidService : IRfidService
|
||||||
? employee.LocationSiteId.Trim()
|
? employee.LocationSiteId.Trim()
|
||||||
: _configService.GetSiteId();
|
: _configService.GetSiteId();
|
||||||
|
|
||||||
// Resolve menu items for this scan (store on record so order history shows actual items)
|
// Resolve menu items for this scan
|
||||||
//var mealLabel = GetMealSessionDisplayName(session);
|
|
||||||
//var mealLabel = session.ToString();
|
|
||||||
var mealLabel = resolvedSession.MealName;
|
var mealLabel = resolvedSession.MealName;
|
||||||
var mealItemsDisplay = string.Empty;
|
var mealItemsDisplay = string.Empty;
|
||||||
double totalPrice = 0;
|
double totalPrice = 0;
|
||||||
|
|
@ -142,9 +331,6 @@ public class RfidService : IRfidService
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
//var matching = menuItems
|
|
||||||
// .Where(i => string.Equals(i.MealName, mealLabel, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// .ToList();
|
|
||||||
var matching = menuItems.ToList();
|
var matching = menuItems.ToList();
|
||||||
|
|
||||||
var names = matching
|
var names = matching
|
||||||
|
|
@ -191,7 +377,29 @@ public class RfidService : IRfidService
|
||||||
db.LunchOrderTransactions.Add(record);
|
db.LunchOrderTransactions.Add(record);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
|
|
||||||
return new ScanResult(true, "Order recorded successfully.", 0, employee, session);
|
return new ScanResult(true, "Order recorded successfully.", 0, employee, session, normalizedEmployeeSite, normalizedCurrentSite);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes a site ID to a consistent format for comparison
|
||||||
|
/// Handles formats like "1", "01", "SITE : 1", "Site 01", etc.
|
||||||
|
/// </summary>
|
||||||
|
private static string NormalizeSiteId(string? siteId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(siteId))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Extract digits only
|
||||||
|
var digits = new string(siteId.Where(char.IsDigit).ToArray());
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(digits))
|
||||||
|
return siteId.Trim(); // Return original if no digits
|
||||||
|
|
||||||
|
// Pad to 2 digits if it's a single digit
|
||||||
|
if (digits.Length == 1)
|
||||||
|
return digits.PadLeft(2, '0');
|
||||||
|
|
||||||
|
return digits;
|
||||||
}
|
}
|
||||||
|
|
||||||
//private static string GetMealSessionDisplayName(MealSession session)
|
//private static string GetMealSessionDisplayName(MealSession session)
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,7 @@ public readonly record struct ScanResult(
|
||||||
string Message,
|
string Message,
|
||||||
int CooldownSecondsRemaining,
|
int CooldownSecondsRemaining,
|
||||||
HrmsEmployeeInfo? EmployeeInfo = null,
|
HrmsEmployeeInfo? EmployeeInfo = null,
|
||||||
MealSession MealSession = MealSession.None);
|
MealSession MealSession = MealSession.None,
|
||||||
|
string? EmployeeSiteId = null,
|
||||||
|
string? CurrentSiteId = null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -297,14 +297,31 @@ public partial class MealSchedulesViewModel : ObservableObject
|
||||||
ApplyFilter();
|
ApplyFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyFilter()
|
private void ApplyFilter()
|
||||||
{
|
{
|
||||||
var site = string.IsNullOrWhiteSpace(_currentSiteId) ? "01" : _currentSiteId;
|
// Remove "SITE : " from the config SiteId and normalize it (pad with leading zeros if necessary)
|
||||||
var filtered = _allSchedules
|
var site = string.IsNullOrWhiteSpace(_currentSiteId)
|
||||||
.Where(s => string.Equals(s.LocationSiteId?.Trim(), site, StringComparison.OrdinalIgnoreCase))
|
? "01"
|
||||||
.ToList();
|
: _currentSiteId.Replace("SITE : ", "").PadLeft(2, '0'); // Normalize to two digits if empty or contains "SITE : "
|
||||||
Schedules = new ObservableCollection<MealSchedule>(filtered);
|
|
||||||
}
|
// Log the site ID we're filtering by (for debugging purposes).
|
||||||
|
Console.WriteLine($"Filtering for site: {site}");
|
||||||
|
|
||||||
|
// Filter the meal schedules by the normalized site ID.
|
||||||
|
var filtered = _allSchedules
|
||||||
|
.Where(s =>
|
||||||
|
{
|
||||||
|
// Normalize the LocationSiteId from the schedule to ensure consistency.
|
||||||
|
var normalizedLocationSiteId = s.LocationSiteId?.Trim().PadLeft(2, '0') ?? "00";
|
||||||
|
Console.WriteLine($"Checking: {normalizedLocationSiteId} vs {site}");
|
||||||
|
|
||||||
|
return string.Equals(normalizedLocationSiteId, site, StringComparison.OrdinalIgnoreCase);
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Update the UI with the filtered results.
|
||||||
|
Schedules = new ObservableCollection<MealSchedule>(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadSchedulesAsync()
|
private async Task LoadSchedulesAsync()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -485,6 +485,77 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
||||||
_navigation.NavigateToAdminSettingsAuth();
|
_navigation.NavigateToAdminSettingsAuth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//[RelayCommand]
|
||||||
|
//private void Scan()
|
||||||
|
//{
|
||||||
|
// lock (_submitLock)
|
||||||
|
// {
|
||||||
|
// if (_isSubmitting || IsProcessing || _isCooldownActive)
|
||||||
|
// return;
|
||||||
|
// _isSubmitting = true;
|
||||||
|
// IsProcessing = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// _debounceTimer.Stop();
|
||||||
|
|
||||||
|
// Message = string.Empty;
|
||||||
|
// IsSuccess = false;
|
||||||
|
|
||||||
|
// var cardId = CardIdInput?.Trim() ?? string.Empty;
|
||||||
|
// if (string.IsNullOrWhiteSpace(cardId))
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// var result = _rfidService.ProcessScanDetailed(cardId);
|
||||||
|
// IsSuccess = result.Success;
|
||||||
|
// Message = result.Message;
|
||||||
|
|
||||||
|
// if (!result.Success && result.CooldownSecondsRemaining > 0)
|
||||||
|
// {
|
||||||
|
// // Requirement: when blocked due to cooldown, keep the scanned ID visible
|
||||||
|
// // so users understand which card was blocked.
|
||||||
|
// CardIdInput = cardId;
|
||||||
|
// StartCooldownCountdown(result.CooldownSecondsRemaining);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Clear input for normal success/failure so the next scan starts cleanly.
|
||||||
|
// CardIdInput = string.Empty;
|
||||||
|
|
||||||
|
// if (result.Success)
|
||||||
|
// {
|
||||||
|
// // Update left panel with HRMS employee data from this scan
|
||||||
|
// if (result.EmployeeInfo is { } info)
|
||||||
|
// {
|
||||||
|
// EmployeeId = !string.IsNullOrWhiteSpace(info.EmployeeId) ? info.EmployeeId : "—";
|
||||||
|
// var fullName = string.Join(" ", new[] { info.FirstName, info.MiddleName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||||
|
// EmployeeName = string.IsNullOrEmpty(fullName) ? "—" : fullName;
|
||||||
|
// EmployeeDepartment = !string.IsNullOrWhiteSpace(info.DepartmentTitle) ? info.DepartmentTitle : "—";
|
||||||
|
// EmployeeDepartmentType = !string.IsNullOrWhiteSpace(info.DepartmentType) ? info.DepartmentType : "—";
|
||||||
|
// // Load menu using site from employee_rfid_tag.location_site_id (not config)
|
||||||
|
// if (int.TryParse(info.LocationSiteId?.Trim(), out var siteFromRfid))
|
||||||
|
// //_ = LoadMenuForSiteAsync(siteFromRfid, result.MealSession);
|
||||||
|
// // Load employee photo from hrms.employee_photo by parent_document_id (employee document id)
|
||||||
|
// _ = LoadEmployeePhotoAsync(info.ParentDocumentId);
|
||||||
|
// }
|
||||||
|
// // Refresh dashboard stats and order history
|
||||||
|
// _ = RefreshDashboardAfterSuccessfulScanAsync(cardId);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// lock (_submitLock)
|
||||||
|
// {
|
||||||
|
// _isSubmitting = false;
|
||||||
|
// // Cooldown keeps the UI locked; otherwise unlock after scan completes.
|
||||||
|
// if (!_isCooldownActive)
|
||||||
|
// IsProcessing = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void Scan()
|
private void Scan()
|
||||||
{
|
{
|
||||||
|
|
@ -511,10 +582,34 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
||||||
IsSuccess = result.Success;
|
IsSuccess = result.Success;
|
||||||
Message = result.Message;
|
Message = result.Message;
|
||||||
|
|
||||||
|
// Check if this is a site mismatch error
|
||||||
|
if (!result.Success && result.Message.Contains("not allowed to scan here"))
|
||||||
|
{
|
||||||
|
// Clear input for site mismatch so the next scan can start fresh
|
||||||
|
CardIdInput = string.Empty;
|
||||||
|
|
||||||
|
// The employee info is included so we can show who tried to scan
|
||||||
|
if (result.EmployeeInfo != null)
|
||||||
|
{
|
||||||
|
var fullName = string.Join(" ", new[] { result.EmployeeInfo.FirstName, result.EmployeeInfo.MiddleName }
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||||
|
|
||||||
|
// Optionally update the UI with the employee info even though scan failed
|
||||||
|
EmployeeName = string.IsNullOrEmpty(fullName) ? "—" : fullName;
|
||||||
|
EmployeeId = !string.IsNullOrWhiteSpace(result.EmployeeInfo.EmployeeId) ? result.EmployeeInfo.EmployeeId : "—";
|
||||||
|
EmployeeDepartment = !string.IsNullOrWhiteSpace(result.EmployeeInfo.DepartmentTitle) ? result.EmployeeInfo.DepartmentTitle : "—";
|
||||||
|
EmployeeDepartmentType = !string.IsNullOrWhiteSpace(result.EmployeeInfo.DepartmentType) ? result.EmployeeInfo.DepartmentType : "—";
|
||||||
|
|
||||||
|
// Load employee photo
|
||||||
|
_ = LoadEmployeePhotoAsync(result.EmployeeInfo.ParentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!result.Success && result.CooldownSecondsRemaining > 0)
|
if (!result.Success && result.CooldownSecondsRemaining > 0)
|
||||||
{
|
{
|
||||||
// Requirement: when blocked due to cooldown, keep the scanned ID visible
|
// Keep the scanned ID visible during cooldown
|
||||||
// so users understand which card was blocked.
|
|
||||||
CardIdInput = cardId;
|
CardIdInput = cardId;
|
||||||
StartCooldownCountdown(result.CooldownSecondsRemaining);
|
StartCooldownCountdown(result.CooldownSecondsRemaining);
|
||||||
return;
|
return;
|
||||||
|
|
@ -533,10 +628,8 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
||||||
EmployeeName = string.IsNullOrEmpty(fullName) ? "—" : fullName;
|
EmployeeName = string.IsNullOrEmpty(fullName) ? "—" : fullName;
|
||||||
EmployeeDepartment = !string.IsNullOrWhiteSpace(info.DepartmentTitle) ? info.DepartmentTitle : "—";
|
EmployeeDepartment = !string.IsNullOrWhiteSpace(info.DepartmentTitle) ? info.DepartmentTitle : "—";
|
||||||
EmployeeDepartmentType = !string.IsNullOrWhiteSpace(info.DepartmentType) ? info.DepartmentType : "—";
|
EmployeeDepartmentType = !string.IsNullOrWhiteSpace(info.DepartmentType) ? info.DepartmentType : "—";
|
||||||
// Load menu using site from employee_rfid_tag.location_site_id (not config)
|
|
||||||
if (int.TryParse(info.LocationSiteId?.Trim(), out var siteFromRfid))
|
// Load employee photo
|
||||||
//_ = LoadMenuForSiteAsync(siteFromRfid, result.MealSession);
|
|
||||||
// Load employee photo from hrms.employee_photo by parent_document_id (employee document id)
|
|
||||||
_ = LoadEmployeePhotoAsync(info.ParentDocumentId);
|
_ = LoadEmployeePhotoAsync(info.ParentDocumentId);
|
||||||
}
|
}
|
||||||
// Refresh dashboard stats and order history
|
// Refresh dashboard stats and order history
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue