diff --git a/Services/HrmsMenuItemForMapping.cs b/Services/HrmsMenuItemForMapping.cs
new file mode 100644
index 0000000..29d66e0
--- /dev/null
+++ b/Services/HrmsMenuItemForMapping.cs
@@ -0,0 +1,45 @@
+namespace UtopiaCanteenSystem.Services;
+
+///
+/// Maps HRMS employee_rfid_tag.grade_type to menu_item.item_for.
+/// Menu rows only use MANAGEMENT / NON_MANAGEMENT; other grades must map to one of those.
+/// Contractual and MTO/TE/Intern use the management menu; Unassigned uses non-management.
+///
+public static class HrmsMenuItemForMapping
+{
+ ///
+ /// Returns MANAGEMENT or NON_MANAGEMENT for menu queries. Never returns empty.
+ ///
+ public static string FromGradeType(string? gradeType)
+ {
+ var compact = ToCompactAlphaNum(gradeType);
+ if (string.IsNullOrEmpty(compact))
+ return "NON_MANAGEMENT";
+
+ if (compact == "management")
+ return "MANAGEMENT";
+
+ if (compact == "nonmanagement")
+ return "NON_MANAGEMENT";
+
+ if (compact == "contractual" || compact == "mtoteintern")
+ return "MANAGEMENT";
+
+ if (compact == "unassigned")
+ return "NON_MANAGEMENT";
+
+ if (compact.StartsWith("non", StringComparison.Ordinal) && compact.Contains("management", StringComparison.Ordinal))
+ return "NON_MANAGEMENT";
+
+ // Any other label (e.g. future grade codes) — use non-management menu so items still resolve.
+ return "NON_MANAGEMENT";
+ }
+
+ private static string ToCompactAlphaNum(string? s)
+ {
+ if (string.IsNullOrWhiteSpace(s))
+ return string.Empty;
+ var chars = s.Trim().ToLowerInvariant().Where(char.IsLetterOrDigit).ToArray();
+ return new string(chars);
+ }
+}
diff --git a/Services/IMenuLookupService.cs b/Services/IMenuLookupService.cs
index a96089a..a299041 100644
--- a/Services/IMenuLookupService.cs
+++ b/Services/IMenuLookupService.cs
@@ -20,5 +20,5 @@ public interface IMenuLookupService
///
//Task> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default);
- Task> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default);
+ Task> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, string mealName ,CancellationToken cancellationToken = default);
}
diff --git a/Services/MenuLookupService.cs b/Services/MenuLookupService.cs
index 8d36774..3b10075 100644
--- a/Services/MenuLookupService.cs
+++ b/Services/MenuLookupService.cs
@@ -147,40 +147,104 @@ public class MenuLookupService : IMenuLookupService
public async Task> GetMenuItemsForSiteAsync(int siteIdNumeric, CancellationToken cancellationToken = default)
{
// 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
- public async Task> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default)
+ //public async Task> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default)
+ //{
+ // var connectionString = _configService.GetHrmsLookupConnectionString();
+ // if (string.IsNullOrWhiteSpace(connectionString))
+ // return Array.Empty();
+
+ // 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();
+ // 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> GetMenuItemsForSiteAndDateAsync(
+ int siteIdNumeric,
+ DateTime menuDateLocal,
+ string gradeType,
+ string mealName, // Add mealName parameter
+ CancellationToken cancellationToken = default)
{
var connectionString = _configService.GetHrmsLookupConnectionString();
if (string.IsNullOrWhiteSpace(connectionString))
return Array.Empty();
+ // Updated SQL to include meal_name filter and select meal_name
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;";
+ SELECT
+ mi.id,
+ mi.item_name,
+ mi.item_type,
+ mi.price,
+ li.menu_date,
+ li.day_of_week,
+ li.meal_name -- Add meal_name to SELECT
+ 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 li.meal_name = @mealName -- Filter by meal session
+ AND mi.item_for = @itemFor
+ ORDER BY mi.item_name;";
- var itemFor = GetItemForFromGradeType(gradeType);
+ var itemFor = HrmsMenuItemForMapping.FromGradeType(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("@mealName", mealName); // Add mealName parameter
cmd.Parameters.AddWithValue("@itemFor", itemFor);
var list = new List();
@@ -195,26 +259,12 @@ public class MenuLookupService : IMenuLookupService
Price = GetDecimal(reader, 3),
MenuDate = GetString(reader, 4),
DayOfWeek = GetString(reader, 5),
- MealName = string.Empty
+ MealName = GetString(reader, 6) // Map meal_name from column index 6
});
}
return list;
}
-
- private static string GetItemForFromGradeType(string? gradeType)
- {
- var value = (gradeType ?? string.Empty).Trim().ToLowerInvariant();
-
- return value switch
- {
- "management" => "MANAGEMENT",
- "nonmanagement" => "NON_MANAGEMENT",
- "non_management" => "NON_MANAGEMENT",
- _ => string.Empty
- };
- }
-
private static string GetString(MySqlDataReader reader, int ordinal)
{
if (reader.IsDBNull(ordinal)) return string.Empty;
diff --git a/Services/RfidService.cs b/Services/RfidService.cs
index 7041308..24aea08 100644
--- a/Services/RfidService.cs
+++ b/Services/RfidService.cs
@@ -195,6 +195,7 @@ public class RfidService : IRfidService
//}
+
public ScanResult ProcessScanDetailed(string cardId)
{
if (string.IsNullOrWhiteSpace(cardId))
@@ -240,9 +241,9 @@ public class RfidService : IRfidService
{
// 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");
+ 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:
/*
@@ -326,8 +327,9 @@ public class RfidService : IRfidService
{
if (int.TryParse(new string(siteIdForSession.Where(char.IsDigit).ToArray()), out var siteNumeric) && siteNumeric > 0)
{
+ // Pass mealLabel as the mealName parameter
var menuItems = _menuLookup
- .GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date, employee.GradeType)
+ .GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date, employee.GradeType, mealLabel)
.GetAwaiter()
.GetResult();
@@ -381,6 +383,193 @@ public class RfidService : IRfidService
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);
+ //}
+
///
/// Normalizes a site ID to a consistent format for comparison
/// Handles formats like "1", "01", "SITE : 1", "Site 01", etc.
diff --git a/Services/SyncService.cs b/Services/SyncService.cs
index e034333..9cbbdf0 100644
--- a/Services/SyncService.cs
+++ b/Services/SyncService.cs
@@ -81,6 +81,52 @@ public class SyncService : ISyncService
SET code = @Code
WHERE id = @Id";
+ const string findLunchMenuItemSql = @"
+ SELECT
+ li.id AS lunch_menu_item_id,
+ mi.item_name,
+ mi.item_type,
+ mi.price
+ 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 li.meal_name = @MealName
+ AND mi.item_name = @ItemName
+ AND (@ItemFor = '' OR mi.item_for = @ItemFor)
+ LIMIT 1";
+
+ const string findLunchMenuItemFallbackSql = @"
+ SELECT
+ li.id AS lunch_menu_item_id,
+ mi.item_name,
+ mi.item_type,
+ mi.price
+ 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_name = @ItemName
+ AND (@ItemFor = '' OR mi.item_for = @ItemFor)
+ LIMIT 1";
+
+ const string existsOrderItemSql = @"
+ SELECT id
+ FROM lunch_order_item
+ WHERE lunch_order_id = @LunchOrderId
+ AND lunch_menu_item_id = @LunchMenuItemId
+ LIMIT 1";
+
+ const string insertOrderItemSql = @"
+ INSERT INTO lunch_order_item
+ (lunch_order_id, lunch_menu_item_id, quantity, price_at_order_time, item_name, item_type)
+ VALUES
+ (@LunchOrderId, @LunchMenuItemId, @Quantity, @PriceAtOrderTime, @ItemName, @ItemType)";
+
var syncedIds = new List(capacity: toSync.Count);
@@ -120,7 +166,7 @@ public class SyncService : ISyncService
{
await hrmsConn.OpenAsync(cancellationToken).ConfigureAwait(false);
- var alreadyExists = false;
+ long lunchOrderId = 0;
await using (var existsCmd = new MySqlCommand(existsOrderSql, hrmsConn))
{
existsCmd.Parameters.AddWithValue("@EmployeeId", employeeIdBigint);
@@ -129,10 +175,11 @@ public class SyncService : ISyncService
existsCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
existsCmd.Parameters.AddWithValue("@MealName", mealName);
var existing = await existsCmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
- alreadyExists = existing != null && existing != DBNull.Value;
+ if (existing != null && existing != DBNull.Value)
+ lunchOrderId = Convert.ToInt64(existing);
}
- if (!alreadyExists)
+ if (lunchOrderId <= 0)
{
await using var orderCmd = new MySqlCommand(insertOrderSql, hrmsConn);
orderCmd.Parameters.AddWithValue("@EmployeeId", employeeIdBigint);
@@ -152,6 +199,7 @@ public class SyncService : ISyncService
var insertedId = orderCmd.LastInsertedId;
if (insertedId > 0)
{
+ lunchOrderId = insertedId;
var code = GenerateLunchOrderCode(insertedId, createdAt);
await using var updateCodeCmd = new MySqlCommand(updateOrderCodeSql, hrmsConn);
@@ -160,6 +208,65 @@ public class SyncService : ISyncService
await updateCodeCmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
}
+
+ if (lunchOrderId > 0)
+ {
+ var mealItems = SplitMealItems(record.MealItems);
+ var itemFor = HrmsMenuItemForMapping.FromGradeType(record.grade_type);
+
+ foreach (var itemName in mealItems)
+ {
+ var menuLookup = await FindLunchMenuItemAsync(
+ hrmsConn,
+ findLunchMenuItemSql,
+ locationSiteId,
+ orderDateLocal,
+ mealName,
+ itemName,
+ itemFor,
+ cancellationToken).ConfigureAwait(false);
+
+ if (menuLookup is null)
+ {
+ menuLookup = await FindLunchMenuItemAsync(
+ hrmsConn,
+ findLunchMenuItemFallbackSql,
+ locationSiteId,
+ orderDateLocal,
+ mealName,
+ itemName,
+ itemFor,
+ cancellationToken).ConfigureAwait(false);
+ }
+
+ if (menuLookup is null)
+ {
+ System.Diagnostics.Debug.WriteLine($"Menu item mapping not found for '{itemName}' (site={locationSiteId}, date={orderDateLocal:yyyy-MM-dd}, meal={mealName}).");
+ continue;
+ }
+
+ var itemExists = false;
+ await using (var existsItemCmd = new MySqlCommand(existsOrderItemSql, hrmsConn))
+ {
+ existsItemCmd.Parameters.AddWithValue("@LunchOrderId", lunchOrderId);
+ existsItemCmd.Parameters.AddWithValue("@LunchMenuItemId", menuLookup.Value.LunchMenuItemId);
+ var existingItem = await existsItemCmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
+ itemExists = existingItem != null && existingItem != DBNull.Value;
+ }
+
+ if (itemExists)
+ continue;
+
+ await using var insertItemCmd = new MySqlCommand(insertOrderItemSql, hrmsConn);
+ insertItemCmd.Parameters.AddWithValue("@LunchOrderId", lunchOrderId);
+ insertItemCmd.Parameters.AddWithValue("@LunchMenuItemId", menuLookup.Value.LunchMenuItemId);
+ insertItemCmd.Parameters.AddWithValue("@Quantity", 1);
+ insertItemCmd.Parameters.AddWithValue("@PriceAtOrderTime", menuLookup.Value.Price);
+ insertItemCmd.Parameters.AddWithValue("@ItemName", menuLookup.Value.ItemName);
+ insertItemCmd.Parameters.AddWithValue("@ItemType", menuLookup.Value.ItemType);
+ await insertItemCmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
}
syncedIds.Add(record.Id);
@@ -228,6 +335,50 @@ public class SyncService : ISyncService
}
}
+ private static IReadOnlyList SplitMealItems(string? mealItems)
+ {
+ if (string.IsNullOrWhiteSpace(mealItems))
+ return Array.Empty();
+
+ return mealItems
+ .Split('+', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.Trim())
+ .Where(x => !string.IsNullOrWhiteSpace(x))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+ }
+
+ private static async Task<(long LunchMenuItemId, string ItemName, string ItemType, decimal Price)?> FindLunchMenuItemAsync(
+ MySqlConnection conn,
+ string sql,
+ int siteId,
+ DateTime menuDate,
+ string mealName,
+ string itemName,
+ string itemFor,
+ CancellationToken cancellationToken)
+ {
+ await using var cmd = new MySqlCommand(sql, conn);
+ cmd.Parameters.AddWithValue("@SiteId", siteId);
+ cmd.Parameters.AddWithValue("@MenuDate", menuDate.Date);
+ cmd.Parameters.AddWithValue("@MealName", mealName);
+ cmd.Parameters.AddWithValue("@ItemName", itemName);
+ cmd.Parameters.AddWithValue("@ItemFor", itemFor);
+
+ await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
+ if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
+ return null;
+
+ var lunchMenuItemId = reader.IsDBNull(0) ? 0 : Convert.ToInt64(reader.GetValue(0));
+ if (lunchMenuItemId <= 0)
+ return null;
+
+ var resolvedItemName = reader.IsDBNull(1) ? string.Empty : reader.GetValue(1)?.ToString() ?? string.Empty;
+ var itemType = reader.IsDBNull(2) ? string.Empty : reader.GetValue(2)?.ToString() ?? string.Empty;
+ var price = reader.IsDBNull(3) ? 0m : Convert.ToDecimal(reader.GetValue(3));
+ return (lunchMenuItemId, resolvedItemName, itemType, price);
+ }
+
///
/// Deletes only synced rows (IsSynced = 1) where ScanTime is before the start of today (local day).
///