using MySqlConnector; using UtopiaCanteenSystem.Models; namespace UtopiaCanteenSystem.Services; /// /// Meal schedule CRUD against production HRMS MySQL table hrms.meal_schedule. /// 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). /// No is_active column; all rows are treated as active. /// public class ProductionMealScheduleService : IMealScheduleService { private readonly IConfigService _configService; private const string TableRef = "`hrms`.`meal_schedule`"; public ProductionMealScheduleService(IConfigService configService) { _configService = configService; } /// public IReadOnlyList GetActiveSchedulesForSite(string siteId) { var connStr = _configService.GetMySqlConnectionString(); if (string.IsNullOrWhiteSpace(connStr)) return Array.Empty(); var siteIdInt = SiteIdStringToInt(siteId); var list = new List(); try { using var conn = new MySqlConnection(connStr); conn.Open(); var sql = $"SELECT id, meal_name, start_time, end_time, created_at, updated_at, location_site_id FROM {TableRef} WHERE location_site_id = @siteId ORDER BY start_time"; using var cmd = new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue("@siteId", siteIdInt); using var r = cmd.ExecuteReader(); while (r.Read()) list.Add(ReadRow(r)); } catch { return Array.Empty(); } return list; } /// public async Task> GetAllSchedulesAsync(CancellationToken cancellationToken = default) { var connStr = _configService.GetMySqlConnectionString(); if (string.IsNullOrWhiteSpace(connStr)) return Array.Empty(); var list = new List(); await using var conn = new MySqlConnection(connStr); await conn.OpenAsync(cancellationToken).ConfigureAwait(false); var sql = $"SELECT id, meal_name, start_time, end_time, created_at, updated_at, location_site_id FROM {TableRef} ORDER BY location_site_id, start_time"; await using var cmd = new MySqlCommand(sql, conn); await using var r = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false); while (await r.ReadAsync(cancellationToken).ConfigureAwait(false)) list.Add(ReadRow(r)); return list; } /// public async Task CreateAsync(MealSchedule schedule, CancellationToken cancellationToken = default) { var connStr = _configService.GetMySqlConnectionString(); if (string.IsNullOrWhiteSpace(connStr)) throw new InvalidOperationException("Production MySQL connection string not configured."); var utc = DateTime.UtcNow; var mealName = (schedule.MealName ?? string.Empty).Trim(); var siteIdInt = SiteIdStringToInt(schedule.LocationSiteId); await using var conn = new MySqlConnection(connStr); await conn.OpenAsync(cancellationToken).ConfigureAwait(false); var sql = $@"INSERT INTO {TableRef} (meal_name, start_time, end_time, created_at, updated_at, location_site_id) VALUES (@mealName, @startTime, @endTime, @createdAt, @updatedAt, @siteId)"; await using var cmd = new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue("@mealName", mealName); cmd.Parameters.AddWithValue("@startTime", schedule.StartTime?.Trim() ?? "00:00:00"); cmd.Parameters.AddWithValue("@endTime", schedule.EndTime?.Trim() ?? "23:59:59"); cmd.Parameters.AddWithValue("@createdAt", utc); cmd.Parameters.AddWithValue("@updatedAt", utc); cmd.Parameters.AddWithValue("@siteId", siteIdInt); await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); return cmd.LastInsertedId; } /// public async Task UpdateAsync(MealSchedule schedule, CancellationToken cancellationToken = default) { var connStr = _configService.GetMySqlConnectionString(); if (string.IsNullOrWhiteSpace(connStr)) throw new InvalidOperationException("Production MySQL connection string not configured."); var utc = DateTime.UtcNow; var mealName = (schedule.MealName ?? string.Empty).Trim(); var siteIdInt = SiteIdStringToInt(schedule.LocationSiteId); await using var conn = new MySqlConnection(connStr); await conn.OpenAsync(cancellationToken).ConfigureAwait(false); var sql = $@"UPDATE {TableRef} SET meal_name = @mealName, start_time = @startTime, end_time = @endTime, location_site_id = @siteId, updated_at = @updatedAt WHERE id = @id"; await using var cmd = new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue("@id", schedule.Id); cmd.Parameters.AddWithValue("@mealName", mealName); cmd.Parameters.AddWithValue("@startTime", schedule.StartTime?.Trim() ?? "00:00:00"); cmd.Parameters.AddWithValue("@endTime", schedule.EndTime?.Trim() ?? "23:59:59"); cmd.Parameters.AddWithValue("@siteId", siteIdInt); cmd.Parameters.AddWithValue("@updatedAt", utc); await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); } /// public async Task DeleteAsync(long id, CancellationToken cancellationToken = default) { var connStr = _configService.GetMySqlConnectionString(); if (string.IsNullOrWhiteSpace(connStr)) throw new InvalidOperationException("Production MySQL connection string not configured."); await using var conn = new MySqlConnection(connStr); await conn.OpenAsync(cancellationToken).ConfigureAwait(false); var sql = $"DELETE FROM {TableRef} WHERE id = @id"; await using var cmd = new MySqlCommand(sql, conn); cmd.Parameters.AddWithValue("@id", id); await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); } /// Read row: id (bigint), meal_name, start_time (time), end_time (time), created_at, updated_at, location_site_id (int). private static MealSchedule ReadRow(MySqlDataReader r) { return new MealSchedule { Id = r.GetInt64(0), MealName = GetString(r, 1), MealSession = MealNameToSession(GetString(r, 1)), StartTime = GetTimeString(r, 2), EndTime = GetTimeString(r, 3), CreatedAt = GetDateTime(r, 4), UpdatedAt = GetDateTime(r, 5), LocationSiteId = IntToSiteIdString(r.IsDBNull(6) ? 0 : r.GetInt32(6)), IsActive = true }; } private static string GetString(MySqlDataReader r, int i) { if (r.IsDBNull(i)) return ""; return r.GetString(i) ?? ""; } private static string GetTimeString(MySqlDataReader r, int i) { if (r.IsDBNull(i)) return "00:00:00"; var v = r.GetValue(i); if (v is TimeSpan ts) return ts.ToString(@"hh\:mm\:ss"); return v?.ToString()?.Trim() ?? "00:00:00"; } private static DateTime GetDateTime(MySqlDataReader r, int i) { if (r.IsDBNull(i)) return DateTime.UtcNow; var dt = r.GetDateTime(i); return dt.Kind == DateTimeKind.Utc ? dt : DateTime.SpecifyKind(dt, DateTimeKind.Utc); } private static int MealNameToSession(string mealName) { if (string.IsNullOrWhiteSpace(mealName)) return 0; var n = mealName.Trim(); // Map legacy names as well as Ramadan labels used in production (Sehri/Iftari) if (n.Equals("Breakfast", StringComparison.OrdinalIgnoreCase) || n.Equals("BreakFast", StringComparison.OrdinalIgnoreCase) || n.Equals("Sehri", StringComparison.OrdinalIgnoreCase)) return (int)MealSession.Breakfast; if (n.Equals("Lunch", StringComparison.OrdinalIgnoreCase) || n.Equals("Iftari", StringComparison.OrdinalIgnoreCase) || n.Equals("Iftar", StringComparison.OrdinalIgnoreCase)) return (int)MealSession.Lunch; if (n.Equals("Tea", StringComparison.OrdinalIgnoreCase)) return (int)MealSession.Tea; if (n.Equals("Dinner", StringComparison.OrdinalIgnoreCase)) return (int)MealSession.Dinner; return 0; } private static string MealSessionToName(int mealSession) { return mealSession switch { (int)MealSession.Breakfast => "Breakfast", (int)MealSession.Lunch => "Lunch", (int)MealSession.Tea => "Tea", (int)MealSession.Dinner => "Dinner", _ => "Breakfast" }; } private static int SiteIdStringToInt(string siteId) { var s = (siteId ?? "").Trim(); if (string.IsNullOrEmpty(s)) return 0; var digits = new string(s.Where(char.IsDigit).ToArray()); return int.TryParse(digits, out var n) ? n : 0; } /// Normalize production location_site_id int to display string (e.g. 2 -> "02"). private static string IntToSiteIdString(int siteIdInt) { if (siteIdInt <= 0) return "01"; return siteIdInt <= 99 ? siteIdInt.ToString("D2") : siteIdInt.ToString(); } }