Fix config overwrite on login/navigation; use production DB for meal schedule CRUD, remove default SiteId normalization, add config load/save logging, and support legacy SiteId format.
parent
a42f43b2cc
commit
4d2433fa28
|
|
@ -92,44 +92,16 @@ public class ConfigService : IConfigService
|
||||||
|
|
||||||
public string GetSiteId()
|
public string GetSiteId()
|
||||||
{
|
{
|
||||||
// Normalize to a 2-digit string (e.g. "02", "07", "12").
|
// Return the configured SiteId as-is. No normalization.
|
||||||
// Accepts legacy values like "SITE : 2" or "2" and converts them.
|
// Returns empty string if not configured.
|
||||||
var raw = _config.SiteId;
|
return _config.SiteId ?? string.Empty;
|
||||||
if (string.IsNullOrWhiteSpace(raw))
|
|
||||||
return "01";
|
|
||||||
|
|
||||||
var digits = new string(raw.Where(char.IsDigit).ToArray());
|
|
||||||
if (string.IsNullOrWhiteSpace(digits))
|
|
||||||
return "01";
|
|
||||||
|
|
||||||
if (!int.TryParse(digits, out var numeric))
|
|
||||||
return "01";
|
|
||||||
|
|
||||||
return Math.Clamp(numeric, 0, 99).ToString("D2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSiteId(string siteId)
|
public void SetSiteId(string siteId)
|
||||||
{
|
{
|
||||||
// Store the normalized 2-digit site code.
|
// Store the SiteId exactly as provided. No automatic normalization.
|
||||||
var normalized = siteId;
|
// Caller is responsible for formatting.
|
||||||
if (string.IsNullOrWhiteSpace(normalized))
|
_config.SiteId = siteId ?? string.Empty;
|
||||||
{
|
|
||||||
normalized = "01";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var digits = new string(normalized.Where(char.IsDigit).ToArray());
|
|
||||||
if (string.IsNullOrWhiteSpace(digits) || !int.TryParse(digits, out var numeric))
|
|
||||||
{
|
|
||||||
normalized = "01";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
normalized = Math.Clamp(numeric, 0, 99).ToString("D2");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_config.SiteId = normalized;
|
|
||||||
SaveConfig();
|
SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,22 +218,30 @@ public class ConfigService : IConfigService
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(_configPath))
|
if (!File.Exists(_configPath))
|
||||||
{
|
{
|
||||||
var defaults = new AppConfig();
|
System.Diagnostics.Debug.WriteLine("[ConfigService] Config file not found. Creating with defaults.");
|
||||||
|
var defaults = new AppConfig();
|
||||||
SaveConfig(defaults);
|
SaveConfig(defaults);
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
var json = File.ReadAllText(_configPath);
|
var json = File.ReadAllText(_configPath);
|
||||||
var config = JsonSerializer.Deserialize<AppConfig>(json);
|
var config = JsonSerializer.Deserialize<AppConfig>(json);
|
||||||
config ??= new AppConfig();
|
config ??= new AppConfig();
|
||||||
MigrateScanIntervalFromSecondsIfNeeded(config);
|
MigrateScanIntervalFromSecondsIfNeeded(config);
|
||||||
return config;
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] Config loaded from {_configPath}");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] SiteId: '{config.SiteId}' (empty=not configured)");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] MySqlConnectionString: {(string.IsNullOrWhiteSpace(config.MySqlConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(config.MySqlConnectionString))}");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] HrmsLookupConnectionString: {(string.IsNullOrWhiteSpace(config.HrmsLookupConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(config.HrmsLookupConnectionString))}");
|
||||||
|
|
||||||
|
return config;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new AppConfig();
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] ERROR loading config: {ex.Message}");
|
||||||
|
return new AppConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,18 +288,49 @@ public class ConfigService : IConfigService
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions
|
// Log what's being saved
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] Saving config...");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] SiteId: '{_config.SiteId}'");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] MySqlConnectionString: {(string.IsNullOrWhiteSpace(_config.MySqlConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(_config.MySqlConnectionString))}");
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] HrmsLookupConnectionString: {(string.IsNullOrWhiteSpace(_config.HrmsLookupConnectionString) ? "NOT CONFIGURED" : MaskConnectionString(_config.HrmsLookupConnectionString))}");
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(config, new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
});
|
});
|
||||||
File.WriteAllText(_configPath, json);
|
File.WriteAllText(_configPath, json);
|
||||||
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] Config saved to {_configPath}");
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Ignore write failures (e.g., read-only location).
|
System.Diagnostics.Debug.WriteLine($"[ConfigService] ERROR saving config: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Masks sensitive connection string values for logging.
|
||||||
|
/// Shows server and database but hides password.
|
||||||
|
/// </summary>
|
||||||
|
private static string MaskConnectionString(string connectionString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Simple masking: show everything before 'Password=' and mask the rest
|
||||||
|
var passwordIndex = connectionString.IndexOf("Password=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (passwordIndex >= 0)
|
||||||
|
{
|
||||||
|
var semicolonAfterPassword = connectionString.IndexOf(';', passwordIndex);
|
||||||
|
if (semicolonAfterPassword >= 0)
|
||||||
|
{
|
||||||
|
return connectionString.Substring(0, passwordIndex) + "Password=***" + connectionString.Substring(semicolonAfterPassword);
|
||||||
|
}
|
||||||
|
return connectionString.Substring(0, passwordIndex) + "Password=***";
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectionString; // No password found, return as-is
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class AppConfig
|
private sealed class AppConfig
|
||||||
{
|
{
|
||||||
public string SyncApiEndpoint { get; set; } = "https://api.example.com/uind/sync";
|
public string SyncApiEndpoint { get; set; } = "https://api.example.com/uind/sync";
|
||||||
|
|
@ -328,10 +339,10 @@ public class ConfigService : IConfigService
|
||||||
public int ScanTimeoutSeconds { get; set; } = 0;
|
public int ScanTimeoutSeconds { get; set; } = 0;
|
||||||
public int ScanIntervalDays { get; set; } = 0;
|
public int ScanIntervalDays { get; set; } = 0;
|
||||||
public int ScanIntervalHours { get; set; } = 0;
|
public int ScanIntervalHours { get; set; } = 0;
|
||||||
public int ScanIntervalMinutes { get; set; } = 1;
|
public int ScanIntervalMinutes { get; set; } = 0;
|
||||||
public int ScanIntervalSeconds { get; set; } = 0;
|
public int ScanIntervalSeconds { get; set; } = 2;
|
||||||
public string AdminCardId { get; set; } = "ADMIN";
|
public string AdminCardId { get; set; } = "ADMIN";
|
||||||
public string SiteId { get; set; } = "SITE : 1";
|
public string SiteId { get; set; } = string.Empty; // No default - must be explicitly configured
|
||||||
public string DeviceId { get; set; } = string.Empty;
|
public string DeviceId { get; set; } = string.Empty;
|
||||||
|
|
||||||
// Admin credential persistence (optional).
|
// Admin credential persistence (optional).
|
||||||
|
|
@ -343,6 +354,6 @@ public class ConfigService : IConfigService
|
||||||
public string MySqlConnectionString { get; set; } = string.Empty;
|
public string MySqlConnectionString { get; set; } = string.Empty;
|
||||||
|
|
||||||
// Local HRMS MySQL for employee lookup by RFID. Separate from production sync.
|
// Local HRMS MySQL for employee lookup by RFID. Separate from production sync.
|
||||||
public string HrmsLookupConnectionString { get; set; } = "Server=192.168.90.147;Port=3306;Database=hrms;User Id=utopia;Password=Utopia01;SslMode=None;";
|
public string HrmsLookupConnectionString { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace UtopiaCanteenSystem.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Meal schedule CRUD against production HRMS MySQL table hrms.meal_schedule.
|
/// Meal schedule CRUD against production HRMS MySQL table hrms.meal_schedule.
|
||||||
/// Uses IConfigService.GetHrmsLookupConnectionString() (same DB as employee lookup).
|
/// Uses IConfigService.GetMySqlConnectionString() (production database connection).
|
||||||
/// Schema: id (bigint), meal_name (varchar), start_time (time), end_time (time), created_at, updated_at, location_site_id (int).
|
/// Schema: id (bigint), meal_name (varchar), start_time (time), end_time (time), created_at, updated_at, location_site_id (int).
|
||||||
/// No is_active column; all rows are treated as active.
|
/// No is_active column; all rows are treated as active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -22,7 +22,7 @@ public class ProductionMealScheduleService : IMealScheduleService
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<MealSchedule> GetActiveSchedulesForSite(string siteId)
|
public IReadOnlyList<MealSchedule> GetActiveSchedulesForSite(string siteId)
|
||||||
{
|
{
|
||||||
var connStr = _configService.GetHrmsLookupConnectionString();
|
var connStr = _configService.GetMySqlConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connStr))
|
if (string.IsNullOrWhiteSpace(connStr))
|
||||||
return Array.Empty<MealSchedule>();
|
return Array.Empty<MealSchedule>();
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class ProductionMealScheduleService : IMealScheduleService
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<MealSchedule>> GetAllSchedulesAsync(CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<MealSchedule>> GetAllSchedulesAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var connStr = _configService.GetHrmsLookupConnectionString();
|
var connStr = _configService.GetMySqlConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connStr))
|
if (string.IsNullOrWhiteSpace(connStr))
|
||||||
return Array.Empty<MealSchedule>();
|
return Array.Empty<MealSchedule>();
|
||||||
|
|
||||||
|
|
@ -67,9 +67,9 @@ public class ProductionMealScheduleService : IMealScheduleService
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<long> CreateAsync(MealSchedule schedule, CancellationToken cancellationToken = default)
|
public async Task<long> CreateAsync(MealSchedule schedule, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var connStr = _configService.GetHrmsLookupConnectionString();
|
var connStr = _configService.GetMySqlConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connStr))
|
if (string.IsNullOrWhiteSpace(connStr))
|
||||||
throw new InvalidOperationException("HRMS MySQL connection string not configured.");
|
throw new InvalidOperationException("Production MySQL connection string not configured.");
|
||||||
|
|
||||||
var utc = DateTime.UtcNow;
|
var utc = DateTime.UtcNow;
|
||||||
var mealName = (schedule.MealName ?? string.Empty).Trim();
|
var mealName = (schedule.MealName ?? string.Empty).Trim();
|
||||||
|
|
@ -93,9 +93,9 @@ public class ProductionMealScheduleService : IMealScheduleService
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task UpdateAsync(MealSchedule schedule, CancellationToken cancellationToken = default)
|
public async Task UpdateAsync(MealSchedule schedule, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var connStr = _configService.GetHrmsLookupConnectionString();
|
var connStr = _configService.GetMySqlConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connStr))
|
if (string.IsNullOrWhiteSpace(connStr))
|
||||||
throw new InvalidOperationException("HRMS MySQL connection string not configured.");
|
throw new InvalidOperationException("Production MySQL connection string not configured.");
|
||||||
|
|
||||||
var utc = DateTime.UtcNow;
|
var utc = DateTime.UtcNow;
|
||||||
var mealName = (schedule.MealName ?? string.Empty).Trim();
|
var mealName = (schedule.MealName ?? string.Empty).Trim();
|
||||||
|
|
@ -117,9 +117,9 @@ public class ProductionMealScheduleService : IMealScheduleService
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task DeleteAsync(long id, CancellationToken cancellationToken = default)
|
public async Task DeleteAsync(long id, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var connStr = _configService.GetHrmsLookupConnectionString();
|
var connStr = _configService.GetMySqlConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connStr))
|
if (string.IsNullOrWhiteSpace(connStr))
|
||||||
throw new InvalidOperationException("HRMS MySQL connection string not configured.");
|
throw new InvalidOperationException("Production MySQL connection string not configured.");
|
||||||
|
|
||||||
await using var conn = new MySqlConnection(connStr);
|
await using var conn = new MySqlConnection(connStr);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
|
||||||
|
|
@ -215,13 +215,21 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
||||||
_clockTimer.Tick += (_, _) => CurrentTime = DateTime.Now.ToString("MM/dd/yyyy, hh:mm:ss tt");
|
_clockTimer.Tick += (_, _) => CurrentTime = DateTime.Now.ToString("MM/dd/yyyy, hh:mm:ss tt");
|
||||||
_clockTimer.Start();
|
_clockTimer.Start();
|
||||||
|
|
||||||
// Load initial site from config (e.g. "SITE : 1" -> "1").
|
// Load initial site from config.
|
||||||
var siteId = _configService.GetSiteId();
|
var siteId = _configService.GetSiteId();
|
||||||
if (!string.IsNullOrWhiteSpace(siteId) && siteId.StartsWith("SITE : ", StringComparison.OrdinalIgnoreCase))
|
if (!string.IsNullOrWhiteSpace(siteId))
|
||||||
{
|
{
|
||||||
var num = siteId.Substring("SITE : ".Length).Trim();
|
// Extract digits from legacy format "SITE : X"or use value directly if already numeric
|
||||||
if (num.Length > 0 && num.All(char.IsDigit))
|
if (siteId.StartsWith("SITE : ", StringComparison.OrdinalIgnoreCase))
|
||||||
SiteNumber = num;
|
{
|
||||||
|
var num = siteId.Substring("SITE : ".Length).Trim();
|
||||||
|
if (num.Length > 0 && num.All(char.IsDigit))
|
||||||
|
SiteNumber = num;
|
||||||
|
}
|
||||||
|
else if (siteId.All(char.IsDigit))
|
||||||
|
{
|
||||||
|
SiteNumber = siteId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshScannerStatus();
|
RefreshScannerStatus();
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@
|
||||||
"ScannerConnected": false,
|
"ScannerConnected": false,
|
||||||
"ScanIntervalDays": 0,
|
"ScanIntervalDays": 0,
|
||||||
"ScanIntervalHours": 0,
|
"ScanIntervalHours": 0,
|
||||||
"ScanIntervalMinutes": 1,
|
"ScanIntervalMinutes": 0,
|
||||||
|
"ScanIntervalSeconds": 2,
|
||||||
"AdminCardId": "ADMIN",
|
"AdminCardId": "ADMIN",
|
||||||
"SiteId": "02",
|
"SiteId": "02",
|
||||||
"DeviceId": "",
|
"DeviceId": "",
|
||||||
"RememberAdminCredentials": false,
|
"RememberAdminCredentials": false,
|
||||||
"SavedAdminUsername": "",
|
"SavedAdminUsername": "",
|
||||||
"SavedAdminPasswordProtected": "",
|
"SavedAdminPasswordProtected": "",
|
||||||
"MySqlConnectionString": "Server=localhost;Database=hrms;User=root;Password=CHANGE_ME;Port=3306;"
|
"MySqlConnectionString": "SERVER=utopia-industries-rr.c5qech8o9lgg.us-east-1.rds.amazonaws.com;DATABASE=hrms;UID=uind_hrms_user;PASSWORD=UINDHRMS01;"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue