commit
e467831cfb
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Windows;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows;
|
||||||
using UtopiaCanteenSystem.Data;
|
using UtopiaCanteenSystem.Data;
|
||||||
using UtopiaCanteenSystem.Services;
|
using UtopiaCanteenSystem.Services;
|
||||||
using UtopiaCanteenSystem.ViewModels;
|
using UtopiaCanteenSystem.ViewModels;
|
||||||
|
|
@ -40,7 +41,9 @@ public partial class App : Application
|
||||||
|
|
||||||
var configService = new ConfigService();
|
var configService = new ConfigService();
|
||||||
var employeeLookupService = new EmployeeLookupService(configService);
|
var employeeLookupService = new EmployeeLookupService(configService);
|
||||||
var employeePhotoService = new EmployeePhotoService(configService);
|
//var employeePhotoService = new EmployeePhotoService(configService);
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
var employeePhotoService = new EmployeePhotoService(configService, httpClient);
|
||||||
var menuLookupService = new MenuLookupService(configService);
|
var menuLookupService = new MenuLookupService(configService);
|
||||||
var mealScheduleService = new ProductionMealScheduleService(configService);
|
var mealScheduleService = new ProductionMealScheduleService(configService);
|
||||||
var mealSessionResolver = new DbMealSessionResolver(mealScheduleService);
|
var mealSessionResolver = new DbMealSessionResolver(mealScheduleService);
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,5 @@ public class HrmsEmployeeInfo
|
||||||
public string DepartmentType { get; set; } = string.Empty;
|
public string DepartmentType { get; set; } = string.Empty;
|
||||||
/// <summary>Site from employee_rfid_tag.location_site_id; used for menu lookup (lunch_menu_week).</summary>
|
/// <summary>Site from employee_rfid_tag.location_site_id; used for menu lookup (lunch_menu_week).</summary>
|
||||||
public string LocationSiteId { get; set; } = string.Empty;
|
public string LocationSiteId { get; set; } = string.Empty;
|
||||||
|
public string GradeType { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,5 @@ public class ScanRecord
|
||||||
public string MealItems { get; set; } = string.Empty;
|
public string MealItems { get; set; } = string.Empty;
|
||||||
/// <summary>Total price for this scan's meal menu.</summary>
|
/// <summary>Total price for this scan's meal menu.</summary>
|
||||||
public double TotalPrice { get; set; }
|
public double TotalPrice { get; set; }
|
||||||
|
public string grade_type { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ public class EmployeeLookupService : IEmployeeLookupService
|
||||||
r.created_by AS tag_created_by,
|
r.created_by AS tag_created_by,
|
||||||
d.title AS department_title,
|
d.title AS department_title,
|
||||||
d.department_type,
|
d.department_type,
|
||||||
r.location_site_id AS location_site_id
|
r.location_site_id AS location_site_id,
|
||||||
|
r.grade_type AS grade_type
|
||||||
FROM employee_rfid_tag r
|
FROM employee_rfid_tag r
|
||||||
JOIN employee e ON e.id = r.parent_document_id
|
JOIN employee e ON e.id = r.parent_document_id
|
||||||
LEFT JOIN department d ON d.id = e.department_id
|
LEFT JOIN department d ON d.id = e.department_id
|
||||||
|
|
@ -68,7 +69,8 @@ public class EmployeeLookupService : IEmployeeLookupService
|
||||||
TagCreatedBy = GetString(reader, 8),
|
TagCreatedBy = GetString(reader, 8),
|
||||||
DepartmentTitle = GetString(reader, 9),
|
DepartmentTitle = GetString(reader, 9),
|
||||||
DepartmentType = GetString(reader, 10),
|
DepartmentType = GetString(reader, 10),
|
||||||
LocationSiteId = GetString(reader, 11)
|
LocationSiteId = GetString(reader, 11),
|
||||||
|
GradeType = GetString(reader, 12)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text;
|
|
||||||
using MySqlConnector;
|
using MySqlConnector;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace UtopiaCanteenSystem.Services;
|
namespace UtopiaCanteenSystem.Services;
|
||||||
|
|
||||||
|
|
@ -13,13 +14,83 @@ public class EmployeePhotoService : IEmployeePhotoService
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly ConcurrentDictionary<string, byte[]> _cache = new(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, byte[]> _cache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private const int MaxCacheEntries = 50;
|
private const int MaxCacheEntries = 50;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
public EmployeePhotoService(IConfigService configService)
|
public EmployeePhotoService(IConfigService configService, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
//public async Task<byte[]?> GetPhotoBytesAsync(string parentDocumentId, CancellationToken cancellationToken = default)
|
||||||
|
//{
|
||||||
|
// if (string.IsNullOrWhiteSpace(parentDocumentId))
|
||||||
|
// return null;
|
||||||
|
|
||||||
|
// var key = parentDocumentId.Trim();
|
||||||
|
// if (_cache.TryGetValue(key, out var cached))
|
||||||
|
// return cached;
|
||||||
|
|
||||||
|
// var connectionString = _configService.GetHrmsLookupConnectionString();
|
||||||
|
// if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
// return null;
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// await using var conn = new MySqlConnection(connectionString);
|
||||||
|
// await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// // employee_photo.employee_id stores the employee document id (parent_document_id from employee lookup)
|
||||||
|
// const string sql = "SELECT photo_blob FROM hrms.employee_photo WHERE employee_id = @parentDocumentId LIMIT 1";
|
||||||
|
// await using var cmd = new MySqlCommand(sql, conn);
|
||||||
|
// cmd.Parameters.AddWithValue("@parentDocumentId", key);
|
||||||
|
|
||||||
|
// await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
// if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||||
|
// return null;
|
||||||
|
|
||||||
|
// if (reader.IsDBNull(0))
|
||||||
|
// return null;
|
||||||
|
|
||||||
|
// byte[] blob;
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var len = reader.GetBytes(0, 0, null, 0, 0);
|
||||||
|
// blob = new byte[len];
|
||||||
|
// reader.GetBytes(0, 0, blob, 0, (int)len);
|
||||||
|
// }
|
||||||
|
// catch
|
||||||
|
// {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var imageBytes = DecodeBlobToImageBytes(blob);
|
||||||
|
// if (imageBytes == null || imageBytes.Length == 0)
|
||||||
|
// return null;
|
||||||
|
|
||||||
|
// // Cache (evict old if needed)
|
||||||
|
// while (_cache.Count >= MaxCacheEntries && _cache.Count > 0)
|
||||||
|
// {
|
||||||
|
// var first = _cache.Keys.FirstOrDefault();
|
||||||
|
// if (first != null)
|
||||||
|
// _cache.TryRemove(first, out _);
|
||||||
|
// else
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// _cache[key] = imageBytes;
|
||||||
|
// return imageBytes;
|
||||||
|
// }
|
||||||
|
// catch
|
||||||
|
// {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<byte[]?> GetPhotoBytesAsync(string parentDocumentId, CancellationToken cancellationToken = default)
|
public async Task<byte[]?> GetPhotoBytesAsync(string parentDocumentId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(parentDocumentId))
|
if (string.IsNullOrWhiteSpace(parentDocumentId))
|
||||||
|
|
@ -29,44 +100,25 @@ public class EmployeePhotoService : IEmployeePhotoService
|
||||||
if (_cache.TryGetValue(key, out var cached))
|
if (_cache.TryGetValue(key, out var cached))
|
||||||
return cached;
|
return cached;
|
||||||
|
|
||||||
var connectionString = _configService.GetHrmsLookupConnectionString();
|
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var conn = new MySqlConnection(connectionString);
|
var url = $"https://portal.utopiaindustries.pk/uind/employee-photo/{Uri.EscapeDataString(key)}.jpeg";
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// employee_photo.employee_id stores the employee document id (parent_document_id from employee lookup)
|
using var response = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
const string sql = "SELECT photo_blob FROM hrms.employee_photo WHERE employee_id = @parentDocumentId LIMIT 1";
|
if (!response.IsSuccessStatusCode)
|
||||||
await using var cmd = new MySqlCommand(sql, conn);
|
|
||||||
cmd.Parameters.AddWithValue("@parentDocumentId", key);
|
|
||||||
|
|
||||||
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (reader.IsDBNull(0))
|
var contentType = response.Content.Headers.ContentType?.MediaType;
|
||||||
return null;
|
if (!string.IsNullOrWhiteSpace(contentType) &&
|
||||||
|
!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||||
byte[] blob;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var len = reader.GetBytes(0, 0, null, 0, 0);
|
|
||||||
blob = new byte[len];
|
|
||||||
reader.GetBytes(0, 0, blob, 0, (int)len);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageBytes = DecodeBlobToImageBytes(blob);
|
var imageBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (imageBytes == null || imageBytes.Length == 0)
|
if (imageBytes == null || imageBytes.Length == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Cache (evict old if needed)
|
|
||||||
while (_cache.Count >= MaxCacheEntries && _cache.Count > 0)
|
while (_cache.Count >= MaxCacheEntries && _cache.Count > 0)
|
||||||
{
|
{
|
||||||
var first = _cache.Keys.FirstOrDefault();
|
var first = _cache.Keys.FirstOrDefault();
|
||||||
|
|
@ -75,6 +127,7 @@ public class EmployeePhotoService : IEmployeePhotoService
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache[key] = imageBytes;
|
_cache[key] = imageBytes;
|
||||||
return imageBytes;
|
return imageBytes;
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +136,6 @@ public class EmployeePhotoService : IEmployeePhotoService
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts blob to image bytes: if blob is UTF-8 "data:image...;base64,<payload>", decodes base64; else treats as raw image.
|
/// Converts blob to image bytes: if blob is UTF-8 "data:image...;base64,<payload>", decodes base64; else treats as raw image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,7 @@ public interface IMenuLookupService
|
||||||
/// Gets menu items for the given site and specific local date (yyyy-MM-dd).
|
/// Gets menu items for the given site and specific local date (yyyy-MM-dd).
|
||||||
/// Avoids server time mismatch by not relying on CURDATE().
|
/// Avoids server time mismatch by not relying on CURDATE().
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default);
|
//Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,133 @@ namespace UtopiaCanteenSystem.Services;
|
||||||
/// Fetches lunch menu from HRMS: lunch_menu_week (by location_site_id) → lunch_menu_item → menu_item.
|
/// Fetches lunch menu from HRMS: lunch_menu_week (by location_site_id) → lunch_menu_item → menu_item.
|
||||||
/// Uses IConfigService.GetHrmsLookupConnectionString(). Only current week is considered.
|
/// Uses IConfigService.GetHrmsLookupConnectionString(). Only current week is considered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
//public class MenuLookupService : IMenuLookupService
|
||||||
|
//{
|
||||||
|
// private readonly IConfigService _configService;
|
||||||
|
|
||||||
|
// public MenuLookupService(IConfigService configService)
|
||||||
|
// {
|
||||||
|
// _configService = configService;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// <inheritdoc />
|
||||||
|
// public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAsync(int siteIdNumeric, CancellationToken cancellationToken = default)
|
||||||
|
// {
|
||||||
|
// // Backwards-compatible: use today's local date.
|
||||||
|
// return await GetMenuItemsForSiteAndDateAsync(siteIdNumeric, DateTime.Today, cancellationToken).ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// <inheritdoc />
|
||||||
|
// public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default)
|
||||||
|
// {
|
||||||
|
// var connectionString = _configService.GetHrmsLookupConnectionString();
|
||||||
|
// if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
// return Array.Empty<HrmsMenuItem>();
|
||||||
|
|
||||||
|
// //const string sql = @"
|
||||||
|
// // SELECT DISTINCT mi.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 CURDATE() BETWEEN w.week_start_date AND w.week_end_date
|
||||||
|
// // ORDER BY mi.item_type, mi.item_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
|
||||||
|
// ORDER BY mi.item_name;";
|
||||||
|
|
||||||
|
// //const string sql = @"
|
||||||
|
// // SELECT
|
||||||
|
// // mi.id,
|
||||||
|
// // mi.item_name,
|
||||||
|
// // mi.item_type,
|
||||||
|
// // mi.price,
|
||||||
|
// // li.meal_name,
|
||||||
|
// // 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
|
||||||
|
// // ORDER BY li.meal_name, mi.item_name;";
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// var list = new List<HrmsMenuItem>();
|
||||||
|
// 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)
|
||||||
|
// //});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// });
|
||||||
|
|
||||||
|
// //list.Add(new HrmsMenuItem
|
||||||
|
// //{
|
||||||
|
// // MenuItemId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0),
|
||||||
|
// // ItemName = GetString(reader, 1),
|
||||||
|
// // ItemType = GetString(reader, 2),
|
||||||
|
// // Price = GetDecimal(reader, 3),
|
||||||
|
|
||||||
|
// // MealName = GetString(reader, 4),
|
||||||
|
// // MenuDate = GetString(reader, 5),
|
||||||
|
// // DayOfWeek = GetString(reader, 6),
|
||||||
|
// //});
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return list;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private static string GetString(MySqlDataReader reader, int ordinal)
|
||||||
|
// {
|
||||||
|
// if (reader.IsDBNull(ordinal)) return string.Empty;
|
||||||
|
// var v = reader.GetValue(ordinal);
|
||||||
|
// return v?.ToString() ?? string.Empty;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private static decimal GetDecimal(MySqlDataReader reader, int ordinal)
|
||||||
|
// {
|
||||||
|
// if (reader.IsDBNull(ordinal)) return 0m;
|
||||||
|
// var v = reader.GetValue(ordinal);
|
||||||
|
// return v is decimal d ? d : Convert.ToDecimal(v);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class MenuLookupService : IMenuLookupService
|
public class MenuLookupService : IMenuLookupService
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
|
|
@ -16,29 +143,20 @@ public class MenuLookupService : IMenuLookupService
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
// Implementing the method for fetching menu items by site
|
||||||
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAsync(int siteIdNumeric, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAsync(int siteIdNumeric, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Backwards-compatible: use today's local date.
|
// Backwards-compatible: use today's local date.
|
||||||
return await GetMenuItemsForSiteAndDateAsync(siteIdNumeric, DateTime.Today, cancellationToken).ConfigureAwait(false);
|
return await GetMenuItemsForSiteAndDateAsync(siteIdNumeric, DateTime.Today, "NonManagement", cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
// Implementing the method for fetching menu items by site, date, and gradeType
|
||||||
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<HrmsMenuItem>> GetMenuItemsForSiteAndDateAsync(int siteIdNumeric, DateTime menuDateLocal, string gradeType, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var connectionString = _configService.GetHrmsLookupConnectionString();
|
var connectionString = _configService.GetHrmsLookupConnectionString();
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
return Array.Empty<HrmsMenuItem>();
|
return Array.Empty<HrmsMenuItem>();
|
||||||
|
|
||||||
//const string sql = @"
|
|
||||||
// SELECT DISTINCT mi.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 CURDATE() BETWEEN w.week_start_date AND w.week_end_date
|
|
||||||
// ORDER BY mi.item_type, mi.item_name";
|
|
||||||
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT
|
SELECT
|
||||||
mi.id,
|
mi.id,
|
||||||
|
|
@ -53,43 +171,22 @@ public class MenuLookupService : IMenuLookupService
|
||||||
WHERE w.location_site_id = @siteId
|
WHERE w.location_site_id = @siteId
|
||||||
AND @menuDate BETWEEN w.week_start_date AND w.week_end_date
|
AND @menuDate BETWEEN w.week_start_date AND w.week_end_date
|
||||||
AND li.menu_date = @menuDate
|
AND li.menu_date = @menuDate
|
||||||
|
AND mi.item_for = @itemFor
|
||||||
ORDER BY mi.item_name;";
|
ORDER BY mi.item_name;";
|
||||||
|
|
||||||
//const string sql = @"
|
var itemFor = GetItemForFromGradeType(gradeType);
|
||||||
// SELECT
|
|
||||||
// mi.id,
|
|
||||||
// mi.item_name,
|
|
||||||
// mi.item_type,
|
|
||||||
// mi.price,
|
|
||||||
// li.meal_name,
|
|
||||||
// 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
|
|
||||||
// ORDER BY li.meal_name, mi.item_name;";
|
|
||||||
|
|
||||||
await using var conn = new MySqlConnection(connectionString);
|
await using var conn = new MySqlConnection(connectionString);
|
||||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await using var cmd = new MySqlCommand(sql, conn);
|
await using var cmd = new MySqlCommand(sql, conn);
|
||||||
cmd.Parameters.AddWithValue("@siteId", siteIdNumeric);
|
cmd.Parameters.AddWithValue("@siteId", siteIdNumeric);
|
||||||
cmd.Parameters.AddWithValue("@menuDate", menuDateLocal.Date);
|
cmd.Parameters.AddWithValue("@menuDate", menuDateLocal.Date);
|
||||||
|
cmd.Parameters.AddWithValue("@itemFor", itemFor);
|
||||||
|
|
||||||
var list = new List<HrmsMenuItem>();
|
var list = new List<HrmsMenuItem>();
|
||||||
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||||
while (await reader.ReadAsync(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)
|
|
||||||
//});
|
|
||||||
|
|
||||||
list.Add(new HrmsMenuItem
|
list.Add(new HrmsMenuItem
|
||||||
{
|
{
|
||||||
MenuItemId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0),
|
MenuItemId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0),
|
||||||
|
|
@ -100,23 +197,24 @@ public class MenuLookupService : IMenuLookupService
|
||||||
DayOfWeek = GetString(reader, 5),
|
DayOfWeek = GetString(reader, 5),
|
||||||
MealName = string.Empty
|
MealName = string.Empty
|
||||||
});
|
});
|
||||||
|
|
||||||
//list.Add(new HrmsMenuItem
|
|
||||||
//{
|
|
||||||
// MenuItemId = reader.IsDBNull(0) ? 0 : reader.GetInt32(0),
|
|
||||||
// ItemName = GetString(reader, 1),
|
|
||||||
// ItemType = GetString(reader, 2),
|
|
||||||
// Price = GetDecimal(reader, 3),
|
|
||||||
|
|
||||||
// MealName = GetString(reader, 4),
|
|
||||||
// MenuDate = GetString(reader, 5),
|
|
||||||
// DayOfWeek = GetString(reader, 6),
|
|
||||||
//});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
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)
|
private static string GetString(MySqlDataReader reader, int ordinal)
|
||||||
{
|
{
|
||||||
if (reader.IsDBNull(ordinal)) return string.Empty;
|
if (reader.IsDBNull(ordinal)) return string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,7 @@ public class RfidService : IRfidService
|
||||||
if (int.TryParse(new string(siteIdForSession.Where(char.IsDigit).ToArray()), out var siteNumeric) && siteNumeric > 0)
|
if (int.TryParse(new string(siteIdForSession.Where(char.IsDigit).ToArray()), out var siteNumeric) && siteNumeric > 0)
|
||||||
{
|
{
|
||||||
var menuItems = _menuLookup
|
var menuItems = _menuLookup
|
||||||
.GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date)
|
.GetMenuItemsForSiteAndDateAsync(siteNumeric, nowLocal.Date, employee.GradeType)
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
|
|
@ -372,7 +372,8 @@ public class RfidService : IRfidService
|
||||||
DepartmentType = employee.DepartmentType ?? string.Empty,
|
DepartmentType = employee.DepartmentType ?? string.Empty,
|
||||||
MealLabel = mealLabel,
|
MealLabel = mealLabel,
|
||||||
MealItems = mealItemsDisplay,
|
MealItems = mealItemsDisplay,
|
||||||
TotalPrice = totalPrice
|
TotalPrice = totalPrice,
|
||||||
|
grade_type = employee.GradeType ?? string.Empty
|
||||||
};
|
};
|
||||||
db.LunchOrderTransactions.Add(record);
|
db.LunchOrderTransactions.Add(record);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,20 @@ public class SyncService : ISyncService
|
||||||
AND order_date = @OrderDate
|
AND order_date = @OrderDate
|
||||||
AND total_cost = @TotalCost
|
AND total_cost = @TotalCost
|
||||||
AND created_at = @CreatedAt
|
AND created_at = @CreatedAt
|
||||||
|
AND meal_name = @MealName
|
||||||
LIMIT 1";
|
LIMIT 1";
|
||||||
|
|
||||||
const string insertOrderSql = @"
|
const string insertOrderSql = @"
|
||||||
INSERT INTO lunch_order
|
INSERT INTO lunch_order
|
||||||
(employee_id, employee_serial_number, order_date, shift, created_at, total_cost, is_cancelled, cancelled_by, created_by, function_id, department_id, location_site_id)
|
(employee_id, employee_serial_number, order_date, shift, created_at, total_cost, is_cancelled, cancelled_by, created_by, function_id, department_id, location_site_id, meal_name)
|
||||||
VALUES
|
VALUES
|
||||||
(@EmployeeId, @EmployeeSerialNumber, @OrderDate, @Shift, @CreatedAt, @TotalCost, 0, @CancelledBy, @CreatedBy, @FunctionId, @DepartmentId, @LocationSiteId)";
|
(@EmployeeId, @EmployeeSerialNumber, @OrderDate, @Shift, @CreatedAt, @TotalCost, 0, @CancelledBy, @CreatedBy, @FunctionId, @DepartmentId, @LocationSiteId, @MealName)";
|
||||||
|
|
||||||
|
const string updateOrderCodeSql = @"
|
||||||
|
UPDATE lunch_order
|
||||||
|
SET code = @Code
|
||||||
|
WHERE id = @Id";
|
||||||
|
|
||||||
|
|
||||||
var syncedIds = new List<int>(capacity: toSync.Count);
|
var syncedIds = new List<int>(capacity: toSync.Count);
|
||||||
|
|
||||||
|
|
@ -103,8 +110,11 @@ public class SyncService : ISyncService
|
||||||
|
|
||||||
var orderDateLocal = record.ScanTime.ToLocalTime().Date;
|
var orderDateLocal = record.ScanTime.ToLocalTime().Date;
|
||||||
var totalCost = Math.Round((decimal)record.TotalPrice, 2, MidpointRounding.AwayFromZero);
|
var totalCost = Math.Round((decimal)record.TotalPrice, 2, MidpointRounding.AwayFromZero);
|
||||||
var createdAt = record.TagCreatedAtUtc ?? record.ScanTime;
|
//var createdAt = record.TagCreatedAtUtc ?? record.ScanTime;
|
||||||
|
var createdAt = record.ScanTime.ToLocalTime();
|
||||||
var locationSiteId = SiteIdStringToInt(record.SiteId ?? string.Empty);
|
var locationSiteId = SiteIdStringToInt(record.SiteId ?? string.Empty);
|
||||||
|
var mealName = (record.MealLabel ?? string.Empty).Trim();
|
||||||
|
var shift = GetShiftFromMeal(mealName);
|
||||||
|
|
||||||
await using (var hrmsConn = new MySqlConnection(hrmsConnStr))
|
await using (var hrmsConn = new MySqlConnection(hrmsConnStr))
|
||||||
{
|
{
|
||||||
|
|
@ -117,6 +127,7 @@ public class SyncService : ISyncService
|
||||||
existsCmd.Parameters.AddWithValue("@OrderDate", orderDateLocal);
|
existsCmd.Parameters.AddWithValue("@OrderDate", orderDateLocal);
|
||||||
existsCmd.Parameters.AddWithValue("@TotalCost", totalCost);
|
existsCmd.Parameters.AddWithValue("@TotalCost", totalCost);
|
||||||
existsCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
|
existsCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
|
||||||
|
existsCmd.Parameters.AddWithValue("@MealName", mealName);
|
||||||
var existing = await existsCmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
var existing = await existsCmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||||
alreadyExists = existing != null && existing != DBNull.Value;
|
alreadyExists = existing != null && existing != DBNull.Value;
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +138,7 @@ public class SyncService : ISyncService
|
||||||
orderCmd.Parameters.AddWithValue("@EmployeeId", employeeIdBigint);
|
orderCmd.Parameters.AddWithValue("@EmployeeId", employeeIdBigint);
|
||||||
orderCmd.Parameters.AddWithValue("@EmployeeSerialNumber", record.UindSerial ?? string.Empty);
|
orderCmd.Parameters.AddWithValue("@EmployeeSerialNumber", record.UindSerial ?? string.Empty);
|
||||||
orderCmd.Parameters.AddWithValue("@OrderDate", orderDateLocal);
|
orderCmd.Parameters.AddWithValue("@OrderDate", orderDateLocal);
|
||||||
orderCmd.Parameters.AddWithValue("@Shift", string.Empty);
|
orderCmd.Parameters.AddWithValue("@Shift", shift);
|
||||||
orderCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
|
orderCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
|
||||||
orderCmd.Parameters.AddWithValue("@TotalCost", totalCost);
|
orderCmd.Parameters.AddWithValue("@TotalCost", totalCost);
|
||||||
orderCmd.Parameters.AddWithValue("@CancelledBy", string.Empty);
|
orderCmd.Parameters.AddWithValue("@CancelledBy", string.Empty);
|
||||||
|
|
@ -135,7 +146,19 @@ public class SyncService : ISyncService
|
||||||
orderCmd.Parameters.AddWithValue("@FunctionId", record.FunctionId);
|
orderCmd.Parameters.AddWithValue("@FunctionId", record.FunctionId);
|
||||||
orderCmd.Parameters.AddWithValue("@DepartmentId", record.DepartmentId);
|
orderCmd.Parameters.AddWithValue("@DepartmentId", record.DepartmentId);
|
||||||
orderCmd.Parameters.AddWithValue("@LocationSiteId", locationSiteId);
|
orderCmd.Parameters.AddWithValue("@LocationSiteId", locationSiteId);
|
||||||
|
orderCmd.Parameters.AddWithValue("@MealName", mealName);
|
||||||
await orderCmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
await orderCmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var insertedId = orderCmd.LastInsertedId;
|
||||||
|
if (insertedId > 0)
|
||||||
|
{
|
||||||
|
var code = GenerateLunchOrderCode(insertedId, createdAt);
|
||||||
|
|
||||||
|
await using var updateCodeCmd = new MySqlCommand(updateOrderCodeSql, hrmsConn);
|
||||||
|
updateCodeCmd.Parameters.AddWithValue("@Code", code);
|
||||||
|
updateCodeCmd.Parameters.AddWithValue("@Id", insertedId);
|
||||||
|
await updateCodeCmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,6 +193,26 @@ public class SyncService : ISyncService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GenerateLunchOrderCode(long id, DateTime date)
|
||||||
|
{
|
||||||
|
return $"LO-{date:yyyy-MM}-{id:D6}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static string GetShiftFromMeal(string mealName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(mealName))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
mealName = mealName.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
|
return mealName switch
|
||||||
|
{
|
||||||
|
"breakfast" => "MORNING",
|
||||||
|
"lunch" => "MORNING",
|
||||||
|
_ => "EVENING"
|
||||||
|
};
|
||||||
|
}
|
||||||
private static int SiteIdStringToInt(string siteId)
|
private static int SiteIdStringToInt(string siteId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -923,6 +923,13 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ToExcelText(string? value)
|
||||||
|
{
|
||||||
|
var safe = value ?? string.Empty;
|
||||||
|
safe = safe.Replace("\"", "\"\"");
|
||||||
|
return $"=\"{safe}\"";
|
||||||
|
}
|
||||||
|
|
||||||
private static string BuildCsvFromScanRecords(IReadOnlyList<ScanRecord> records)
|
private static string BuildCsvFromScanRecords(IReadOnlyList<ScanRecord> records)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
@ -931,7 +938,7 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
sb.Append(r.Id);
|
sb.Append(r.Id);
|
||||||
sb.Append(',');
|
sb.Append(',');
|
||||||
sb.Append(EscapeCsv(r.CardId));
|
sb.Append(ToExcelText(r.CardId));
|
||||||
sb.Append(',');
|
sb.Append(',');
|
||||||
sb.Append(EscapeCsv(r.ScanTime.ToString("O", CultureInfo.InvariantCulture)));
|
sb.Append(EscapeCsv(r.ScanTime.ToString("O", CultureInfo.InvariantCulture)));
|
||||||
sb.Append(',');
|
sb.Append(',');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue