HRMS lunch_order_item rows after lunch_order / production transaction insert

Extend SyncService so each unsynced SQLite scan still posts to production lunch_order_transactions and upserts HRMS lunch_order, then creates matching lunch_order_item rows. Item lines are derived from SQLite MealItems (split on +); each name is resolved via lunch_menu_week → lunch_menu_item → menu_item using site, menu date, meal name, and optional item_for from grade_type, with a fallback lookup if the meal filter misses. Inserts use lunch_order_id, lunch_menu_item_id, quantity (1), price_at_order_time, item_name, and item_type from HRMS, and skips duplicates for the same order and menu item.
pull/5/head
SYED MUSTUFA AHMED NAQVI 2026-04-02 09:44:16 +05:00
parent 0fd22cc1d5
commit 4cdac8b4be
1 changed files with 166 additions and 3 deletions

View File

@ -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<int>(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 = GradeTypeToItemFor(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,62 @@ public class SyncService : ISyncService
}
}
private static string GradeTypeToItemFor(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 IReadOnlyList<string> SplitMealItems(string? mealItems)
{
if (string.IsNullOrWhiteSpace(mealItems))
return Array.Empty<string>();
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);
}
/// <summary>
/// Deletes only synced rows (IsSynced = 1) where ScanTime is before the start of today (local day).
/// </summary>