commit
390361ce83
|
|
@ -26,6 +26,9 @@ appsettings.json
|
|||
# Publish
|
||||
publish/
|
||||
|
||||
# Publish Profiles
|
||||
Properties/PublishProfiles/
|
||||
|
||||
# OS
|
||||
Thumbs.db
|
||||
.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);
|
||||
}
|
||||
|
||||
//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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cardId))
|
||||
|
|
@ -57,19 +210,61 @@ public class RfidService : IRfidService
|
|||
var nowUtc = DateTime.UtcNow;
|
||||
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)
|
||||
? 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);
|
||||
return new ScanResult(false, "This scan is outside of valid meal timings.", 0, employee);
|
||||
|
||||
var session = resolvedSession.Session;
|
||||
var sessionCode = (int)session;
|
||||
|
|
@ -89,16 +284,10 @@ public class RfidService : IRfidService
|
|||
.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);
|
||||
return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0, employee, session);
|
||||
}
|
||||
|
||||
var interval = _configService.GetScanInterval();
|
||||
|
|
@ -119,7 +308,9 @@ public class RfidService : IRfidService
|
|||
return new ScanResult(
|
||||
false,
|
||||
$"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();
|
||||
|
|
@ -127,9 +318,7 @@ public class RfidService : IRfidService
|
|||
? 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();
|
||||
// Resolve menu items for this scan
|
||||
var mealLabel = resolvedSession.MealName;
|
||||
var mealItemsDisplay = string.Empty;
|
||||
double totalPrice = 0;
|
||||
|
|
@ -142,9 +331,6 @@ public class RfidService : IRfidService
|
|||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
//var matching = menuItems
|
||||
// .Where(i => string.Equals(i.MealName, mealLabel, StringComparison.OrdinalIgnoreCase))
|
||||
// .ToList();
|
||||
var matching = menuItems.ToList();
|
||||
|
||||
var names = matching
|
||||
|
|
@ -191,7 +377,29 @@ public class RfidService : IRfidService
|
|||
db.LunchOrderTransactions.Add(record);
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -12,5 +12,7 @@ public readonly record struct ScanResult(
|
|||
string Message,
|
||||
int CooldownSecondsRemaining,
|
||||
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();
|
||||
}
|
||||
|
||||
private void ApplyFilter()
|
||||
{
|
||||
var site = string.IsNullOrWhiteSpace(_currentSiteId) ? "01" : _currentSiteId;
|
||||
var filtered = _allSchedules
|
||||
.Where(s => string.Equals(s.LocationSiteId?.Trim(), site, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
Schedules = new ObservableCollection<MealSchedule>(filtered);
|
||||
}
|
||||
private void ApplyFilter()
|
||||
{
|
||||
// Remove "SITE : " from the config SiteId and normalize it (pad with leading zeros if necessary)
|
||||
var site = string.IsNullOrWhiteSpace(_currentSiteId)
|
||||
? "01"
|
||||
: _currentSiteId.Replace("SITE : ", "").PadLeft(2, '0'); // Normalize to two digits if empty or contains "SITE : "
|
||||
|
||||
// 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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -485,6 +485,77 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
|||
_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]
|
||||
private void Scan()
|
||||
{
|
||||
|
|
@ -511,10 +582,34 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
|||
IsSuccess = result.Success;
|
||||
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)
|
||||
{
|
||||
// Requirement: when blocked due to cooldown, keep the scanned ID visible
|
||||
// so users understand which card was blocked.
|
||||
// Keep the scanned ID visible during cooldown
|
||||
CardIdInput = cardId;
|
||||
StartCooldownCountdown(result.CooldownSecondsRemaining);
|
||||
return;
|
||||
|
|
@ -533,10 +628,8 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
|||
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)
|
||||
|
||||
// Load employee photo
|
||||
_ = LoadEmployeePhotoAsync(info.ParentDocumentId);
|
||||
}
|
||||
// Refresh dashboard stats and order history
|
||||
|
|
|
|||
Loading…
Reference in New Issue