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
parent
0fd22cc1d5
commit
4cdac8b4be
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue