Sync scans to lunch_order and enforce per-session daily scans
Extend HRMS RFID lookup to include uind_serial, function_id, department_id, created_by, and tag created timestamp Persist new tag fields + meal session code in SQLite scan records; auto-migrate schema and drop deprecated EmployeeDbId column Enforce “once per meal session per local day” scan rule (keep short per-card debounce; remove global cooldown) Enhance sync: insert scans into production lunch_order_transactions (MySqlConnectionString) and into HRMS/local lunch_order (HrmsLookupConnectionString) with duplicate guard; include location_site_id from SQLite SiteIdpull/1/head
parent
18a6b7a668
commit
a42f43b2cc
|
|
@ -1,4 +1,5 @@
|
|||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using UtopiaCanteenSystem.Models;
|
||||
|
|
@ -163,6 +164,13 @@ public class AppDbContext : DbContext
|
|||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("MealSessionCode", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN MealSessionCode INTEGER DEFAULT -1";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("ParentDocumentId", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
|
|
@ -177,6 +185,41 @@ public class AppDbContext : DbContext
|
|||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("UindSerial", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN UindSerial TEXT DEFAULT ''";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("FunctionId", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN FunctionId INTEGER DEFAULT 0";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("DepartmentId", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN DepartmentId INTEGER DEFAULT 0";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("TagCreatedAtUtc", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN TagCreatedAtUtc TEXT";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("TagCreatedBy", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN TagCreatedBy TEXT DEFAULT ''";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (columns.Count > 0 && !columns.Contains("EmployeeName", StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
|
|
@ -218,6 +261,9 @@ public class AppDbContext : DbContext
|
|||
cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN TotalPrice REAL DEFAULT 0";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Remove deprecated column if it exists (older installs).
|
||||
RemoveEmployeeDbIdColumnIfPresent(conn, columns);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -225,6 +271,110 @@ public class AppDbContext : DbContext
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes deprecated EmployeeDbId column from lunch_order_transactions if present.
|
||||
/// Uses DROP COLUMN when supported; otherwise rebuilds the table without that column.
|
||||
/// Best-effort and non-fatal.
|
||||
/// </summary>
|
||||
private static void RemoveEmployeeDbIdColumnIfPresent(DbConnection conn, List<string> existingColumns)
|
||||
{
|
||||
if (existingColumns.Count == 0 || !existingColumns.Contains("EmployeeDbId", StringComparer.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
// Try modern SQLite DROP COLUMN first.
|
||||
try
|
||||
{
|
||||
using var dropCmd = conn.CreateCommand();
|
||||
dropCmd.CommandText = "ALTER TABLE lunch_order_transactions DROP COLUMN EmployeeDbId";
|
||||
dropCmd.ExecuteNonQuery();
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fall back to table rebuild.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var tx = conn.BeginTransaction();
|
||||
|
||||
using (var createCmd = conn.CreateCommand())
|
||||
{
|
||||
createCmd.Transaction = tx;
|
||||
createCmd.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS lunch_order_transactions__new (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
CardId TEXT,
|
||||
ScanTime TEXT,
|
||||
IsSynced INTEGER,
|
||||
SiteId TEXT,
|
||||
DeviceId TEXT,
|
||||
IpAddress TEXT,
|
||||
ParentDocumentId TEXT,
|
||||
EmployeeId TEXT,
|
||||
UindSerial TEXT,
|
||||
FunctionId INTEGER,
|
||||
DepartmentId INTEGER,
|
||||
TagCreatedAtUtc TEXT,
|
||||
TagCreatedBy TEXT,
|
||||
EmployeeName TEXT,
|
||||
Department TEXT,
|
||||
DepartmentType TEXT,
|
||||
MealLabel TEXT,
|
||||
MealItems TEXT,
|
||||
TotalPrice REAL
|
||||
)";
|
||||
createCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var copyCmd = conn.CreateCommand())
|
||||
{
|
||||
copyCmd.Transaction = tx;
|
||||
copyCmd.CommandText = @"
|
||||
INSERT INTO lunch_order_transactions__new
|
||||
(Id, CardId, ScanTime, IsSynced, SiteId, DeviceId, IpAddress, ParentDocumentId, EmployeeId, UindSerial, FunctionId, DepartmentId, TagCreatedAtUtc, TagCreatedBy, EmployeeName, Department, DepartmentType, MealLabel, MealItems, TotalPrice)
|
||||
SELECT
|
||||
Id, CardId, ScanTime, IsSynced, SiteId, DeviceId, IpAddress, ParentDocumentId, EmployeeId, UindSerial, FunctionId, DepartmentId, TagCreatedAtUtc, TagCreatedBy, EmployeeName, Department, DepartmentType, MealLabel, MealItems, TotalPrice
|
||||
FROM lunch_order_transactions";
|
||||
copyCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var dropOldCmd = conn.CreateCommand())
|
||||
{
|
||||
dropOldCmd.Transaction = tx;
|
||||
dropOldCmd.CommandText = "DROP TABLE lunch_order_transactions";
|
||||
dropOldCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
using (var renameCmd = conn.CreateCommand())
|
||||
{
|
||||
renameCmd.Transaction = tx;
|
||||
renameCmd.CommandText = "ALTER TABLE lunch_order_transactions__new RENAME TO lunch_order_transactions";
|
||||
renameCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Recreate basic indexes used by the app.
|
||||
using (var idxCmd = conn.CreateCommand())
|
||||
{
|
||||
idxCmd.Transaction = tx;
|
||||
idxCmd.CommandText = "CREATE INDEX IF NOT EXISTS IX_lunch_order_transactions_IsSynced ON lunch_order_transactions(IsSynced)";
|
||||
idxCmd.ExecuteNonQuery();
|
||||
}
|
||||
using (var idxCmd = conn.CreateCommand())
|
||||
{
|
||||
idxCmd.Transaction = tx;
|
||||
idxCmd.CommandText = "CREATE INDEX IF NOT EXISTS IX_lunch_order_transactions_ScanTime ON lunch_order_transactions(ScanTime)";
|
||||
idxCmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
tx.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort; ignore failures.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight creation of AdminLoginRecords table for existing databases.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,9 +9,18 @@ public class HrmsEmployeeInfo
|
|||
public string ParentDocumentId { get; set; } = string.Empty;
|
||||
/// <summary>Employee serial number (employee.serial_number).</summary>
|
||||
public string EmployeeId { get; set; } = string.Empty;
|
||||
/// <summary>uind_serial from employee_rfid_tag (production lunch_order.employee_serial_number).</summary>
|
||||
public string UindSerial { get; set; } = string.Empty;
|
||||
/// <summary>function_id from employee_rfid_tag.</summary>
|
||||
public int FunctionId { get; set; }
|
||||
/// <summary>department_id from employee_rfid_tag (production lunch_order.department_id).</summary>
|
||||
public int DepartmentId { get; set; }
|
||||
/// <summary>date_time_created from employee_rfid_tag (production lunch_order.created_at).</summary>
|
||||
public DateTime? TagCreatedAtUtc { get; set; }
|
||||
/// <summary>created_by from employee_rfid_tag (production lunch_order.created_by).</summary>
|
||||
public string TagCreatedBy { get; set; } = string.Empty;
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
public string MiddleName { get; set; } = string.Empty;
|
||||
public string DepartmentId { get; set; } = string.Empty;
|
||||
public string DepartmentTitle { 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>
|
||||
|
|
|
|||
|
|
@ -6,9 +6,19 @@ namespace UtopiaCanteenSystem.Models;
|
|||
public class HrmsMenuItem
|
||||
{
|
||||
/// <summary>menu_item.id</summary>
|
||||
//public int MenuItemId { get; set; }
|
||||
//public string ItemName { get; set; } = string.Empty;
|
||||
//public string ItemType { get; set; } = string.Empty;
|
||||
///// <summary>menu_item.price (HRMS). Decimal is safest for money.</summary>
|
||||
//public decimal Price { get; set; }
|
||||
|
||||
public int MenuItemId { get; set; }
|
||||
public string ItemName { get; set; } = string.Empty;
|
||||
public string ItemType { get; set; } = string.Empty;
|
||||
/// <summary>menu_item.price (HRMS). Decimal is safest for money.</summary>
|
||||
public decimal Price { get; set; }
|
||||
|
||||
// NEW:
|
||||
public string MealName { get; set; } = string.Empty; // Sehri / Iftari / Dinner
|
||||
public string MenuDate { get; set; } = string.Empty; // "2026-03-05"
|
||||
public string DayOfWeek { get; set; } = string.Empty; // Thursday
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ namespace UtopiaCanteenSystem.Models;
|
|||
public class MealSchedule
|
||||
{
|
||||
public long Id { get; set; }
|
||||
/// <summary>Raw meal_name from hrms.meal_schedule (e.g. \"Sehri\", \"Iftari\", \"Tea\", \"Dinner\").</summary>
|
||||
public string MealName { get; set; } = string.Empty;
|
||||
/// <summary>Site identifier (matches ScanRecord.SiteId / config).</summary>
|
||||
public string LocationSiteId { get; set; } = string.Empty;
|
||||
/// <summary>Session type: 0=Breakfast, 1=Lunch, 2=Tea, 3=Dinner (MealSession enum value).</summary>
|
||||
|
|
|
|||
|
|
@ -18,10 +18,22 @@ public class ScanRecord
|
|||
public string DeviceId { get; set; } = string.Empty;
|
||||
/// <summary>IP address of the device at scan time.</summary>
|
||||
public string IpAddress { get; set; } = string.Empty;
|
||||
/// <summary>Meal session enum value at scan time (int cast of MealSession).</summary>
|
||||
public int MealSessionCode { get; set; }
|
||||
/// <summary>HRMS employee document id (employee.id) at scan time; used to load photo from hrms.employee_photo.</summary>
|
||||
public string ParentDocumentId { get; set; } = string.Empty;
|
||||
/// <summary>HRMS employee ID (serial_number) at scan time, when lookup succeeded.</summary>
|
||||
public string EmployeeId { get; set; } = string.Empty;
|
||||
/// <summary>uind_serial from employee_rfid_tag at scan time.</summary>
|
||||
public string UindSerial { get; set; } = string.Empty;
|
||||
/// <summary>function_id from employee_rfid_tag at scan time.</summary>
|
||||
public int FunctionId { get; set; }
|
||||
/// <summary>department_id from employee_rfid_tag at scan time.</summary>
|
||||
public int DepartmentId { get; set; }
|
||||
/// <summary>date_time_created from employee_rfid_tag (stored as DateTime; naming kept for clarity).</summary>
|
||||
public DateTime? TagCreatedAtUtc { get; set; }
|
||||
/// <summary>created_by from employee_rfid_tag.</summary>
|
||||
public string TagCreatedBy { get; set; } = string.Empty;
|
||||
/// <summary>Full name (first + middle) from HRMS at scan time, when lookup succeeded.</summary>
|
||||
public string EmployeeName { get; set; } = string.Empty;
|
||||
/// <summary>HRMS department title at scan time.</summary>
|
||||
|
|
|
|||
|
|
@ -2,37 +2,33 @@
|
|||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ApplicationRevision>13</ApplicationRevision>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.*</ApplicationVersion>
|
||||
<BootstrapperEnabled>True</BootstrapperEnabled>
|
||||
<Configuration>Release</Configuration>
|
||||
<CreateDesktopShortcut>True</CreateDesktopShortcut>
|
||||
<CreateWebPageOnPublish>False</CreateWebPageOnPublish>
|
||||
<GenerateManifests>true</GenerateManifests>
|
||||
<Install>True</Install>
|
||||
<InstallFrom>Unc</InstallFrom>
|
||||
<InstallUrl>\\FileServer\Edata\Deployments-by-DotNet-Team\UtopiaCanteenSystem\</InstallUrl>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<IsRevisionIncremented>True</IsRevisionIncremented>
|
||||
<IsWebBootstrapper>False</IsWebBootstrapper>
|
||||
<MapFileExtensions>True</MapFileExtensions>
|
||||
<OpenBrowserOnPublish>False</OpenBrowserOnPublish>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net8.0-windows\win-x86\app.publish\</PublishDir>
|
||||
<PublishUrl>\\FileServer\Edata\Deployments-by-DotNet-Team\UtopiaCanteenSystem\</PublishUrl>
|
||||
<PublishUrl>D:\Publish\UtopiaCanteenSystem\</PublishUrl>
|
||||
<PublishProtocol>ClickOnce</PublishProtocol>
|
||||
<PublishReadyToRun>False</PublishReadyToRun>
|
||||
<PublishSingleFile>True</PublishSingleFile>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||
<SelfContained>True</SelfContained>
|
||||
<SignatureAlgorithm>(none)</SignatureAlgorithm>
|
||||
<SignManifests>False</SignManifests>
|
||||
<SkipPublishVerification>false</SkipPublishVerification>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UpdateEnabled>True</UpdateEnabled>
|
||||
<UpdateEnabled>False</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateRequired>False</UpdateRequired>
|
||||
<UpdateUrl>\\FileServer\Edata\Deployments-by-DotNet-Team\UtopiaCanteenSystem\</UpdateUrl>
|
||||
<WebPageFileName>Publish.html</WebPageFileName>
|
||||
<History>False|2026-03-03T06:08:36.2813025Z||;False|2026-03-03T11:07:21.6156279+05:00||;True|2026-02-18T16:44:11.3509970+05:00||;True|2026-02-12T11:25:40.9881813+05:00||;True|2026-02-12T11:22:02.6537306+05:00||;True|2026-02-12T10:57:16.2775876+05:00||;True|2026-02-12T10:50:58.6968682+05:00||;False|2026-02-12T10:49:27.1867616+05:00||;True|2026-02-12T10:44:55.6022990+05:00||;True|2026-02-11T17:00:04.2786466+05:00||;True|2026-02-11T16:54:37.5052808+05:00||;True|2026-02-11T16:39:26.1892892+05:00||;True|2026-02-11T16:30:57.2420414+05:00||;True|2026-02-11T16:20:44.4749529+05:00||;</History>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>D:\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -224,7 +224,17 @@ public class ConfigService : IConfigService
|
|||
SaveConfig();
|
||||
}
|
||||
|
||||
public string GetHrmsLookupConnectionString() => (_config.HrmsLookupConnectionString ?? string.Empty).Trim();
|
||||
//public string GetHrmsLookupConnectionString() => (_config.HrmsLookupConnectionString ?? string.Empty).Trim();
|
||||
|
||||
public string GetHrmsLookupConnectionString()
|
||||
{
|
||||
var hrms = (_config.HrmsLookupConnectionString ?? string.Empty).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(hrms))
|
||||
return hrms;
|
||||
|
||||
// fallback to production/AWS connection string
|
||||
return GetMySqlConnectionString();
|
||||
}
|
||||
|
||||
public void SetHrmsLookupConnectionString(string connectionString)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,11 @@ public class EmployeeLookupService : IEmployeeLookupService
|
|||
e.serial_number AS employee_id,
|
||||
e.concatenated_name AS first_name,
|
||||
'' AS middle_name,
|
||||
e.department_id,
|
||||
r.uind_serial AS uind_serial,
|
||||
r.function_id AS function_id,
|
||||
r.department_id AS tag_department_id,
|
||||
r.date_time_created AS tag_date_time_created,
|
||||
r.created_by AS tag_created_by,
|
||||
d.title AS department_title,
|
||||
d.department_type,
|
||||
r.location_site_id AS location_site_id
|
||||
|
|
@ -50,16 +54,21 @@ public class EmployeeLookupService : IEmployeeLookupService
|
|||
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||
return null;
|
||||
|
||||
var parentDocumentId = GetString(reader, 0);
|
||||
return new HrmsEmployeeInfo
|
||||
{
|
||||
ParentDocumentId = GetString(reader, 0),
|
||||
ParentDocumentId = parentDocumentId,
|
||||
EmployeeId = GetString(reader, 1),
|
||||
FirstName = GetString(reader, 2),
|
||||
MiddleName = GetString(reader, 3),
|
||||
DepartmentId = GetString(reader, 4),
|
||||
DepartmentTitle = GetString(reader, 5),
|
||||
DepartmentType = GetString(reader, 6),
|
||||
LocationSiteId = GetString(reader, 7)
|
||||
UindSerial = GetString(reader, 4),
|
||||
FunctionId = GetInt(reader, 5),
|
||||
DepartmentId = GetInt(reader, 6),
|
||||
TagCreatedAtUtc = GetDateTimeNullable(reader, 7),
|
||||
TagCreatedBy = GetString(reader, 8),
|
||||
DepartmentTitle = GetString(reader, 9),
|
||||
DepartmentType = GetString(reader, 10),
|
||||
LocationSiteId = GetString(reader, 11)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -69,4 +78,36 @@ public class EmployeeLookupService : IEmployeeLookupService
|
|||
var v = reader.GetValue(ordinal);
|
||||
return v?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static int GetInt(MySqlDataReader reader, int ordinal)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.IsDBNull(ordinal)) return 0;
|
||||
var v = reader.GetValue(ordinal);
|
||||
if (v is int i) return i;
|
||||
if (v is long l) return (int)l;
|
||||
return int.TryParse(v?.ToString(), out var parsed) ? parsed : 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime? GetDateTimeNullable(MySqlDataReader reader, int ordinal)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.IsDBNull(ordinal)) return null;
|
||||
var v = reader.GetValue(ordinal);
|
||||
if (v is DateTime dt) return dt;
|
||||
if (DateTime.TryParse(v?.ToString(), out var parsed)) return parsed;
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,31 @@ public class MenuLookupService : IMenuLookupService
|
|||
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 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";
|
||||
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 CURDATE() BETWEEN w.week_start_date AND w.week_end_date
|
||||
AND li.menu_date = DATE_FORMAT(CURDATE(), '%Y-%m-%d')
|
||||
ORDER BY li.meal_name, mi.item_name;
|
||||
";
|
||||
|
||||
await using var conn = new MySqlConnection(connectionString);
|
||||
await conn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -41,12 +58,24 @@ public class MenuLookupService : IMenuLookupService
|
|||
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)
|
||||
Price = GetDecimal(reader, 3),
|
||||
|
||||
MealName = GetString(reader, 4),
|
||||
MenuDate = GetString(reader, 5),
|
||||
DayOfWeek = GetString(reader, 6),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public class ProductionMealScheduleService : IMealScheduleService
|
|||
throw new InvalidOperationException("HRMS MySQL connection string not configured.");
|
||||
|
||||
var utc = DateTime.UtcNow;
|
||||
var mealName = MealSessionToName(schedule.MealSession);
|
||||
var mealName = (schedule.MealName ?? string.Empty).Trim();
|
||||
var siteIdInt = SiteIdStringToInt(schedule.LocationSiteId);
|
||||
|
||||
await using var conn = new MySqlConnection(connStr);
|
||||
|
|
@ -98,7 +98,7 @@ public class ProductionMealScheduleService : IMealScheduleService
|
|||
throw new InvalidOperationException("HRMS MySQL connection string not configured.");
|
||||
|
||||
var utc = DateTime.UtcNow;
|
||||
var mealName = MealSessionToName(schedule.MealSession);
|
||||
var mealName = (schedule.MealName ?? string.Empty).Trim();
|
||||
var siteIdInt = SiteIdStringToInt(schedule.LocationSiteId);
|
||||
|
||||
await using var conn = new MySqlConnection(connStr);
|
||||
|
|
@ -135,6 +135,7 @@ public class ProductionMealScheduleService : IMealScheduleService
|
|||
return new MealSchedule
|
||||
{
|
||||
Id = r.GetInt64(0),
|
||||
MealName = GetString(r, 1),
|
||||
MealSession = MealNameToSession(GetString(r, 1)),
|
||||
StartTime = GetTimeString(r, 2),
|
||||
EndTime = GetTimeString(r, 3),
|
||||
|
|
@ -171,8 +172,15 @@ public class ProductionMealScheduleService : IMealScheduleService
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(mealName)) return 0;
|
||||
var n = mealName.Trim();
|
||||
if (n.Equals("Breakfast", StringComparison.OrdinalIgnoreCase) || n.Equals("BreakFast", StringComparison.OrdinalIgnoreCase)) return (int)MealSession.Breakfast;
|
||||
if (n.Equals("Lunch", StringComparison.OrdinalIgnoreCase)) return (int)MealSession.Lunch;
|
||||
// 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;
|
||||
|
|
|
|||
|
|
@ -58,14 +58,35 @@ public class RfidService : IRfidService
|
|||
if (session == MealSession.None)
|
||||
return new ScanResult(false, "This scan is outside of valid meal timings.", 0);
|
||||
|
||||
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 = GetMealSessionDisplayName(session);
|
||||
return new ScanResult(false, $"This card is already scanned for {sessionName} today.", 0);
|
||||
}
|
||||
|
||||
var interval = _configService.GetScanInterval();
|
||||
var timeoutSeconds = (int)Math.Max(1, interval.TotalSeconds);
|
||||
|
||||
var windowStart = nowUtc.AddSeconds(-timeoutSeconds);
|
||||
|
||||
// Enforce timeout: no duplicate scan within the timeout window
|
||||
// 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)
|
||||
|
|
@ -81,21 +102,6 @@ public class RfidService : IRfidService
|
|||
remaining);
|
||||
}
|
||||
|
||||
// Optional: prevent any scan within the timeout window (any card).
|
||||
var lastAnyScanInWindow = db.LunchOrderTransactions
|
||||
.Where(r => r.ScanTime >= windowStart)
|
||||
.OrderByDescending(r => r.ScanTime)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (lastAnyScanInWindow != null)
|
||||
{
|
||||
var remaining = GetCooldownRemainingSeconds(nowUtc, lastAnyScanInWindow.ScanTime, timeoutSeconds);
|
||||
return new ScanResult(
|
||||
false,
|
||||
$"Only one order at a time within {FormatTimeout(timeoutSeconds)}. Ask this customer to rescan after countdown.",
|
||||
remaining);
|
||||
}
|
||||
|
||||
var fullName = string.Join(" ", new[] { employee.FirstName, employee.MiddleName }.Where(s => !string.IsNullOrWhiteSpace(s))).Trim();
|
||||
var siteId = !string.IsNullOrWhiteSpace(employee.LocationSiteId)
|
||||
? employee.LocationSiteId.Trim()
|
||||
|
|
@ -108,8 +114,14 @@ public class RfidService : IRfidService
|
|||
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
|
||||
|
|
@ -120,6 +132,18 @@ public class RfidService : IRfidService
|
|||
return new ScanResult(true, "Order recorded successfully.", 0, employee, session);
|
||||
}
|
||||
|
||||
private static string GetMealSessionDisplayName(MealSession session)
|
||||
{
|
||||
return session switch
|
||||
{
|
||||
MealSession.Breakfast => "Sehri",
|
||||
MealSession.Lunch => "Iftari",
|
||||
MealSession.Tea => "Tea",
|
||||
MealSession.Dinner => "Dinner",
|
||||
_ => "this meal"
|
||||
};
|
||||
}
|
||||
|
||||
public ScanRecord? GetLastScan()
|
||||
{
|
||||
using var db = _dbFactory.CreateDbContext();
|
||||
|
|
|
|||
|
|
@ -22,10 +22,17 @@ public class SyncService : ISyncService
|
|||
|
||||
public async Task SyncNowAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var connectionString = _configService.GetMySqlConnectionString();
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
var productionConnStr = _configService.GetMySqlConnectionString();
|
||||
if (string.IsNullOrWhiteSpace(productionConnStr))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("MySQL connection string not configured; skipping sync.");
|
||||
System.Diagnostics.Debug.WriteLine("MySQL connection string (production) not configured; skipping sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
var hrmsConnStr = _configService.GetHrmsLookupConnectionString();
|
||||
if (string.IsNullOrWhiteSpace(hrmsConnStr))
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("HRMS lookup connection string not configured; skipping sync (need both for lunch_order).");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -45,85 +52,136 @@ public class SyncService : ISyncService
|
|||
if (toSync.Count == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
// Production: lunch_order_transactions (GetMySqlConnectionString)
|
||||
const string insertTxnSql = @"
|
||||
INSERT IGNORE INTO lunch_order_transactions
|
||||
(scan_date, site_id, device_id, card_id, ip_address, received_date)
|
||||
VALUES
|
||||
(@ScanTimeUtc, @SiteId, @DeviceId, @CardId, @IpAddress, UTC_TIMESTAMP(3))";
|
||||
|
||||
// HRMS/local: lunch_order (GetHrmsLookupConnectionString)
|
||||
const string existsOrderSql = @"
|
||||
SELECT id
|
||||
FROM lunch_order
|
||||
WHERE employee_id = @EmployeeId
|
||||
AND order_date = @OrderDate
|
||||
AND total_cost = @TotalCost
|
||||
AND created_at = @CreatedAt
|
||||
LIMIT 1";
|
||||
|
||||
const string insertOrderSql = @"
|
||||
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)
|
||||
VALUES
|
||||
(@EmployeeId, @EmployeeSerialNumber, @OrderDate, @Shift, @CreatedAt, @TotalCost, 0, @CancelledBy, @CreatedBy, @FunctionId, @DepartmentId, @LocationSiteId)";
|
||||
|
||||
var syncedIds = new List<int>(capacity: toSync.Count);
|
||||
|
||||
foreach (var record in toSync)
|
||||
{
|
||||
// Insert into MySQL using transaction for atomicity
|
||||
await using var mysqlConn = new MySqlConnection(connectionString);
|
||||
await mysqlConn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await using var transaction = await mysqlConn.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
// INSERT IGNORE: duplicates avoided by UNIQUE(site_id, device_id, card_id, scan_date) in production.
|
||||
// Column names match production: hrms.lunch_order_transactions (snake_case). No device_local_row_id.
|
||||
var insertSql = @"
|
||||
INSERT IGNORE INTO lunch_order_transactions
|
||||
(scan_date, site_id, device_id, card_id, ip_address, received_date)
|
||||
VALUES
|
||||
(@ScanTimeUtc, @SiteId, @DeviceId, @CardId, @IpAddress, UTC_TIMESTAMP(3))";
|
||||
|
||||
await using var cmd = new MySqlCommand(insertSql, mysqlConn, transaction);
|
||||
|
||||
foreach (var record in toSync)
|
||||
// 1) Insert into lunch_order_transactions (production – MySqlConnectionString)
|
||||
await using (var prodConn = new MySqlConnection(productionConnStr))
|
||||
{
|
||||
cmd.Parameters.Clear();
|
||||
await prodConn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using var cmd = new MySqlCommand(insertTxnSql, prodConn);
|
||||
cmd.Parameters.AddWithValue("@ScanTimeUtc", record.ScanTime);
|
||||
cmd.Parameters.AddWithValue("@SiteId", record.SiteId ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@DeviceId", record.DeviceId ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@CardId", record.CardId ?? string.Empty);
|
||||
cmd.Parameters.AddWithValue("@IpAddress", record.IpAddress ?? string.Empty);
|
||||
|
||||
var rowsAffected = await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
// Note: INSERT IGNORE returns 0 if row already exists (duplicate), 1 if inserted
|
||||
await cmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Commit MySQL transaction
|
||||
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
|
||||
// 2) Insert into lunch_order (HRMS/local – HrmsLookupConnectionString)
|
||||
if (string.IsNullOrWhiteSpace(record.ParentDocumentId) || record.FunctionId <= 0 || record.DepartmentId <= 0)
|
||||
throw new InvalidOperationException("Missing required HRMS tag fields for lunch_order insert.");
|
||||
|
||||
// Only mark as synced in SQLite after successful MySQL insert
|
||||
var ids = toSync.Select(r => r.Id).ToList();
|
||||
using (var db = _dbFactory.CreateDbContext())
|
||||
if (!long.TryParse(record.ParentDocumentId.Trim(), out var employeeIdBigint) || employeeIdBigint <= 0)
|
||||
throw new InvalidOperationException("Invalid ParentDocumentId for lunch_order.employee_id.");
|
||||
|
||||
var orderDateLocal = record.ScanTime.ToLocalTime().Date;
|
||||
var totalCost = Math.Round((decimal)record.TotalPrice, 2, MidpointRounding.AwayFromZero);
|
||||
var createdAt = record.TagCreatedAtUtc ?? record.ScanTime;
|
||||
var locationSiteId = SiteIdStringToInt(record.SiteId ?? string.Empty);
|
||||
|
||||
await using (var hrmsConn = new MySqlConnection(hrmsConnStr))
|
||||
{
|
||||
var records = await db.LunchOrderTransactions
|
||||
.Where(r => ids.Contains(r.Id))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var record in records)
|
||||
record.IsSynced = true;
|
||||
await hrmsConn.OpenAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
var alreadyExists = false;
|
||||
await using (var existsCmd = new MySqlCommand(existsOrderSql, hrmsConn))
|
||||
{
|
||||
existsCmd.Parameters.AddWithValue("@EmployeeId", employeeIdBigint);
|
||||
existsCmd.Parameters.AddWithValue("@OrderDate", orderDateLocal);
|
||||
existsCmd.Parameters.AddWithValue("@TotalCost", totalCost);
|
||||
existsCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
|
||||
var existing = await existsCmd.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
alreadyExists = existing != null && existing != DBNull.Value;
|
||||
}
|
||||
|
||||
if (!alreadyExists)
|
||||
{
|
||||
await using var orderCmd = new MySqlCommand(insertOrderSql, hrmsConn);
|
||||
orderCmd.Parameters.AddWithValue("@EmployeeId", employeeIdBigint);
|
||||
orderCmd.Parameters.AddWithValue("@EmployeeSerialNumber", record.UindSerial ?? string.Empty);
|
||||
orderCmd.Parameters.AddWithValue("@OrderDate", orderDateLocal);
|
||||
orderCmd.Parameters.AddWithValue("@Shift", string.Empty);
|
||||
orderCmd.Parameters.AddWithValue("@CreatedAt", createdAt);
|
||||
orderCmd.Parameters.AddWithValue("@TotalCost", totalCost);
|
||||
orderCmd.Parameters.AddWithValue("@CancelledBy", string.Empty);
|
||||
orderCmd.Parameters.AddWithValue("@CreatedBy", record.TagCreatedBy ?? string.Empty);
|
||||
orderCmd.Parameters.AddWithValue("@FunctionId", record.FunctionId);
|
||||
orderCmd.Parameters.AddWithValue("@DepartmentId", record.DepartmentId);
|
||||
orderCmd.Parameters.AddWithValue("@LocationSiteId", locationSiteId);
|
||||
await orderCmd.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Day-end cleanup: delete only synced rows from previous days.
|
||||
await CleanupOldSyncedRowsAsync(cancellationToken).ConfigureAwait(false);
|
||||
syncedIds.Add(record.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Rollback MySQL transaction on error
|
||||
try
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore rollback errors
|
||||
}
|
||||
|
||||
// Log error for debugging (you can replace with proper logging)
|
||||
System.Diagnostics.Debug.WriteLine($"Sync error: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||
|
||||
throw; // Re-throw to be caught by outer catch
|
||||
System.Diagnostics.Debug.WriteLine($"Sync record failed (SQLite Id={record.Id}): {ex.Message}");
|
||||
// Leave unsynced; will retry later.
|
||||
}
|
||||
}
|
||||
|
||||
if (syncedIds.Count > 0)
|
||||
{
|
||||
using var db = _dbFactory.CreateDbContext();
|
||||
var records = await db.LunchOrderTransactions
|
||||
.Where(r => syncedIds.Contains(r.Id))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var r in records)
|
||||
r.IsSynced = true;
|
||||
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await CleanupOldSyncedRowsAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log error for debugging (you can replace with proper logging)
|
||||
System.Diagnostics.Debug.WriteLine($"Sync failed: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||
System.Diagnostics.Debug.WriteLine($"Sync cleanup failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Leave records intact; will retry on next run
|
||||
private static int SiteIdStringToInt(string siteId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = (siteId ?? string.Empty).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;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public partial class MealSchedulesViewModel : ObservableObject
|
|||
private string _locationSiteId = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _mealSessionIndex; // 0=Breakfast, 1=Lunch, 2=Tea, 3=Dinner
|
||||
private string _mealName = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _startTime = "06:00:00";
|
||||
|
|
@ -52,8 +52,6 @@ public partial class MealSchedulesViewModel : ObservableObject
|
|||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
public static readonly string[] MealSessionNames = { "Breakfast", "Lunch", "Tea", "Dinner" };
|
||||
|
||||
public MealSchedulesViewModel(IMealScheduleService mealScheduleService, INavigationService navigation)
|
||||
{
|
||||
_mealScheduleService = mealScheduleService;
|
||||
|
|
@ -70,7 +68,7 @@ public partial class MealSchedulesViewModel : ObservableObject
|
|||
{
|
||||
if (value == null) return;
|
||||
LocationSiteId = value.LocationSiteId ?? string.Empty;
|
||||
MealSessionIndex = value.MealSession;
|
||||
MealName = value.MealName ?? string.Empty;
|
||||
StartTime = value.StartTime ?? "00:00:00";
|
||||
EndTime = value.EndTime ?? "23:59:59";
|
||||
}
|
||||
|
|
@ -122,7 +120,7 @@ public partial class MealSchedulesViewModel : ObservableObject
|
|||
{
|
||||
SelectedSchedule = null;
|
||||
LocationSiteId = "02";
|
||||
MealSessionIndex = 0;
|
||||
MealName = string.Empty;
|
||||
StartTime = "06:00:00";
|
||||
EndTime = "09:00:00";
|
||||
Message = string.Empty;
|
||||
|
|
@ -162,8 +160,8 @@ public partial class MealSchedulesViewModel : ObservableObject
|
|||
var dto = new MealSchedule
|
||||
{
|
||||
Id = SelectedSchedule.Id,
|
||||
MealName = (MealName ?? string.Empty).Trim(),
|
||||
LocationSiteId = siteId,
|
||||
MealSession = MealSessionIndex,
|
||||
StartTime = StartTime.Trim(),
|
||||
EndTime = EndTime.Trim()
|
||||
};
|
||||
|
|
@ -174,8 +172,8 @@ public partial class MealSchedulesViewModel : ObservableObject
|
|||
{
|
||||
var dto = new MealSchedule
|
||||
{
|
||||
MealName = (MealName ?? string.Empty).Trim(),
|
||||
LocationSiteId = siteId,
|
||||
MealSession = MealSessionIndex,
|
||||
StartTime = StartTime.Trim(),
|
||||
EndTime = EndTime.Trim()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -258,10 +258,18 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
|||
}
|
||||
else
|
||||
{
|
||||
//mealLabel = activeSession switch
|
||||
//{
|
||||
// MealSession.Breakfast => "Breakfast",
|
||||
// MealSession.Lunch => "Lunch",
|
||||
// MealSession.Tea => "Tea",
|
||||
// MealSession.Dinner => "Dinner",
|
||||
// _ => "No active meal session"
|
||||
//};
|
||||
mealLabel = activeSession switch
|
||||
{
|
||||
MealSession.Breakfast => "Breakfast",
|
||||
MealSession.Lunch => "Lunch",
|
||||
MealSession.Breakfast => "Sehri",
|
||||
MealSession.Lunch => "Iftari",
|
||||
MealSession.Tea => "Tea",
|
||||
MealSession.Dinner => "Dinner",
|
||||
_ => "No active meal session"
|
||||
|
|
@ -276,11 +284,15 @@ public partial class ScannerDashboardViewModel : ObservableObject
|
|||
!string.Equals(mealLabel, "No active meal session", StringComparison.OrdinalIgnoreCase) &&
|
||||
items.Count > 0)
|
||||
{
|
||||
//var matchingItems = items
|
||||
// .Where(i =>
|
||||
// !string.IsNullOrWhiteSpace(i.ItemType) &&
|
||||
// i.ItemType.IndexOf(mealLabel, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
// .ToList();
|
||||
|
||||
var matchingItems = items
|
||||
.Where(i =>
|
||||
!string.IsNullOrWhiteSpace(i.ItemType) &&
|
||||
i.ItemType.IndexOf(mealLabel, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
.ToList();
|
||||
.Where(i => string.Equals(i.MealName, mealLabel, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
var matchingItemNames = matchingItems
|
||||
.Select(i => i.ItemName)
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@
|
|||
Binding="{Binding LocationSiteId}"
|
||||
Width="*"/>
|
||||
<DataGridTextColumn Header="Session"
|
||||
Binding="{Binding MealSession, Converter={StaticResource MealSessionConverter}}"
|
||||
Binding="{Binding MealName}"
|
||||
Width="*"/>
|
||||
<DataGridTextColumn Header="Start"
|
||||
Binding="{Binding StartTime}"
|
||||
|
|
@ -473,13 +473,8 @@
|
|||
FontSize="12"
|
||||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,4"/>
|
||||
<ComboBox SelectedIndex="{Binding MealSessionIndex, Mode=TwoWay}"
|
||||
Style="{StaticResource ModernComboBox}">
|
||||
<ComboBoxItem Content="Breakfast"/>
|
||||
<ComboBoxItem Content="Lunch"/>
|
||||
<ComboBoxItem Content="Tea"/>
|
||||
<ComboBoxItem Content="Dinner"/>
|
||||
</ComboBox>
|
||||
<TextBox Text="{Binding MealName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource ModernTextBox}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue