diff --git a/App.xaml.cs b/App.xaml.cs
index 39c3e97..67bf3d6 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -54,7 +54,7 @@ public partial class App : Application
};
mainWindow.Show();
- // Background sync: every 15 minutes, POST unsynced ScanRecords to API
+ // Background sync: every 15 minutes, POST unsynced lunch_order_transactions to API
_syncTimer = new System.Timers.Timer(TimeSpan.FromMinutes(15).TotalMilliseconds)
{
AutoReset = true
diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs
index a912085..6b9bbe3 100644
--- a/Data/AppDbContext.cs
+++ b/Data/AppDbContext.cs
@@ -6,7 +6,7 @@ using UtopiaCanteenSystem.Models;
namespace UtopiaCanteenSystem.Data;
///
-/// SQLite DbContext for Labour and ScanRecord tables.
+/// SQLite DbContext for Labour and lunch_order_transactions (scan records) tables.
/// Database file is created in application directory on first run.
///
public class AppDbContext : DbContext
@@ -16,7 +16,7 @@ public class AppDbContext : DbContext
"utopia_canteen.db");
public DbSet Labour { get; set; }
- public DbSet ScanRecords { get; set; }
+ public DbSet LunchOrderTransactions { get; set; }
public DbSet AdminLoginRecords { get; set; }
public AppDbContext() { }
@@ -38,9 +38,10 @@ public class AppDbContext : DbContext
e.HasIndex(x => x.CardId);
});
- // ScanRecord: Index for unsynced queries and by ScanTime.
+ // lunch_order_transactions (scan records): Index for unsynced queries and by ScanTime.
modelBuilder.Entity(e =>
{
+ e.ToTable("lunch_order_transactions");
e.HasKey(x => x.Id);
e.HasIndex(x => x.IsSynced);
e.HasIndex(x => x.ScanTime);
@@ -59,15 +60,72 @@ public class AppDbContext : DbContext
public void EnsureDatabaseCreated()
{
Database.EnsureCreated();
- UpgradeScanRecordsSchemaIfNeeded();
+ MigrateScanRecordsToLunchOrderTransactionsIfNeeded();
+ UpgradeLunchOrderTransactionsSchemaIfNeeded();
EnsureAdminLoginTableExists();
}
///
- /// Lightweight schema upgrade: add SiteId and DeviceId to ScanRecords if missing (no EF migrations).
+ /// One-time migration: rename old tables (ScanRecords or CanteenScanEvents) to lunch_order_transactions for existing databases.
+ ///
+ private void MigrateScanRecordsToLunchOrderTransactionsIfNeeded()
+ {
+ try
+ {
+ var conn = Database.GetDbConnection();
+ if (conn.State != ConnectionState.Open)
+ conn.Open();
+
+ var hasOldScanRecords = false;
+ var hasOldCanteenScanEvents = false;
+ var hasNew = false;
+
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='ScanRecords'";
+ using var r = cmd.ExecuteReader();
+ hasOldScanRecords = r.Read();
+ }
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='CanteenScanEvents'";
+ using var r = cmd.ExecuteReader();
+ hasOldCanteenScanEvents = r.Read();
+ }
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table' AND name='lunch_order_transactions'";
+ using var r = cmd.ExecuteReader();
+ hasNew = r.Read();
+ }
+
+ if (!hasNew)
+ {
+ if (hasOldScanRecords)
+ {
+ using var cmd = conn.CreateCommand();
+ cmd.CommandText = "ALTER TABLE ScanRecords RENAME TO lunch_order_transactions";
+ cmd.ExecuteNonQuery();
+ }
+ else if (hasOldCanteenScanEvents)
+ {
+ using var cmd = conn.CreateCommand();
+ cmd.CommandText = "ALTER TABLE CanteenScanEvents RENAME TO lunch_order_transactions";
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+ catch
+ {
+ // Ignore; DB may be new or already migrated
+ }
+ }
+
+ ///
+ /// Lightweight schema upgrade: add SiteId and DeviceId to lunch_order_transactions if missing (no EF migrations).
/// Does not delete any data.
///
- private void UpgradeScanRecordsSchemaIfNeeded()
+ private void UpgradeLunchOrderTransactionsSchemaIfNeeded()
{
try
{
@@ -78,23 +136,23 @@ public class AppDbContext : DbContext
var columns = new List();
using (var cmd = conn.CreateCommand())
{
- cmd.CommandText = "SELECT name FROM pragma_table_info('ScanRecords')";
+ cmd.CommandText = "SELECT name FROM pragma_table_info('lunch_order_transactions')";
using var r = cmd.ExecuteReader();
while (r.Read())
columns.Add(r.GetString(0));
}
- if (!columns.Contains("SiteId", StringComparer.OrdinalIgnoreCase))
+ if (columns.Count > 0 && !columns.Contains("SiteId", StringComparer.OrdinalIgnoreCase))
{
using var cmd = conn.CreateCommand();
- cmd.CommandText = "ALTER TABLE ScanRecords ADD COLUMN SiteId TEXT DEFAULT ''";
+ cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN SiteId TEXT DEFAULT ''";
cmd.ExecuteNonQuery();
}
- if (!columns.Contains("DeviceId", StringComparer.OrdinalIgnoreCase))
+ if (columns.Count > 0 && !columns.Contains("DeviceId", StringComparer.OrdinalIgnoreCase))
{
using var cmd = conn.CreateCommand();
- cmd.CommandText = "ALTER TABLE ScanRecords ADD COLUMN DeviceId TEXT DEFAULT ''";
+ cmd.CommandText = "ALTER TABLE lunch_order_transactions ADD COLUMN DeviceId TEXT DEFAULT ''";
cmd.ExecuteNonQuery();
}
}
diff --git a/Services/IRfidService.cs b/Services/IRfidService.cs
index 77f71c4..73a80ea 100644
--- a/Services/IRfidService.cs
+++ b/Services/IRfidService.cs
@@ -11,7 +11,7 @@ public interface IRfidService
{
///
/// Processes a scan for the given card ID. Applies configured timeout,
- /// saves to ScanRecords if allowed, and returns result message.
+ /// saves to lunch_order_transactions if allowed, and returns result message.
///
(bool Success, string Message) ProcessScan(string cardId);
diff --git a/Services/ISyncService.cs b/Services/ISyncService.cs
index 921839c..970dd60 100644
--- a/Services/ISyncService.cs
+++ b/Services/ISyncService.cs
@@ -1,7 +1,7 @@
namespace UtopiaCanteenSystem.Services;
///
-/// Syncs unsynced ScanRecords to the configured UIND API endpoint.
+/// Syncs unsynced lunch_order_transactions to the configured UIND API endpoint.
///
public interface ISyncService
{
diff --git a/Services/RfidService.cs b/Services/RfidService.cs
index 842510f..12bbf0f 100644
--- a/Services/RfidService.cs
+++ b/Services/RfidService.cs
@@ -9,7 +9,7 @@ namespace UtopiaCanteenSystem.Services;
///
/// Handles RFID scan logic: validates input, enforces configurable timeout,
-/// and saves ScanRecord to SQLite.
+/// and saves scan events to SQLite (lunch_order_transactions table).
///
public class RfidService : IRfidService
{
@@ -45,7 +45,7 @@ public class RfidService : IRfidService
var windowStart = nowUtc.AddSeconds(-timeoutSeconds);
// Enforce timeout: no duplicate scan within the timeout window
- var lastInWindow = db.ScanRecords
+ var lastInWindow = db.LunchOrderTransactions
.Where(r => r.CardId == cardId)
.Where(r => r.ScanTime >= windowStart)
.OrderByDescending(r => r.ScanTime)
@@ -61,7 +61,7 @@ public class RfidService : IRfidService
}
// Optional: prevent any scan within the timeout window (any card).
- var lastAnyScanInWindow = db.ScanRecords
+ var lastAnyScanInWindow = db.LunchOrderTransactions
.Where(r => r.ScanTime >= windowStart)
.OrderByDescending(r => r.ScanTime)
.FirstOrDefault();
@@ -83,7 +83,7 @@ public class RfidService : IRfidService
SiteId = _configService.GetSiteId(),
DeviceId = _configService.GetDeviceId()
};
- db.ScanRecords.Add(record);
+ db.LunchOrderTransactions.Add(record);
db.SaveChanges();
return new ScanResult(true, "Order recorded successfully.", 0);
@@ -92,7 +92,7 @@ public class RfidService : IRfidService
public ScanRecord? GetLastScan()
{
using var db = _dbFactory.CreateDbContext();
- return db.ScanRecords
+ return db.LunchOrderTransactions
.OrderByDescending(r => r.ScanTime)
.FirstOrDefault();
}
@@ -101,7 +101,7 @@ public class RfidService : IRfidService
{
if (count <= 0) return Array.Empty();
using var db = _dbFactory.CreateDbContext();
- return db.ScanRecords
+ return db.LunchOrderTransactions
.OrderByDescending(r => r.ScanTime)
.Take(count)
.ToList();
@@ -114,7 +114,7 @@ public class RfidService : IRfidService
var startUtc = startOfTodayLocal.ToUniversalTime();
var endUtc = startOfTomorrowLocal.ToUniversalTime();
using var db = _dbFactory.CreateDbContext();
- return db.ScanRecords
+ return db.LunchOrderTransactions
.Where(r => r.ScanTime >= startUtc && r.ScanTime < endUtc)
.OrderByDescending(r => r.ScanTime)
.ToList();
@@ -130,7 +130,7 @@ public class RfidService : IRfidService
var endUtc = startOfTomorrowLocal.ToUniversalTime();
using var db = _dbFactory.CreateDbContext();
- return db.ScanRecords
+ return db.LunchOrderTransactions
.Count(r => r.ScanTime >= startUtc && r.ScanTime < endUtc);
}
@@ -144,7 +144,7 @@ public class RfidService : IRfidService
var endUtc = startOfTomorrowLocal.ToUniversalTime();
await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- return await db.ScanRecords
+ return await db.LunchOrderTransactions
.CountAsync(r => r.ScanTime >= startUtc && r.ScanTime < endUtc, cancellationToken)
.ConfigureAwait(false);
}
@@ -152,7 +152,7 @@ public class RfidService : IRfidService
public async Task GetTotalScanCountAsync(CancellationToken cancellationToken = default)
{
await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- return await db.ScanRecords.CountAsync(cancellationToken).ConfigureAwait(false);
+ return await db.LunchOrderTransactions.CountAsync(cancellationToken).ConfigureAwait(false);
}
public async Task GetTotalScanCountForCardAsync(string cardId, CancellationToken cancellationToken = default)
@@ -163,7 +163,7 @@ public class RfidService : IRfidService
cardId = cardId.Trim();
await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- return await db.ScanRecords
+ return await db.LunchOrderTransactions
.CountAsync(r => r.CardId == cardId, cancellationToken)
.ConfigureAwait(false);
}
@@ -183,7 +183,7 @@ public class RfidService : IRfidService
var endUtc = startOfTomorrowLocal.ToUniversalTime();
await using var db = await _dbFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
- return await db.ScanRecords
+ return await db.LunchOrderTransactions
.CountAsync(r => r.CardId == cardId && r.ScanTime >= startUtc && r.ScanTime < endUtc, cancellationToken)
.ConfigureAwait(false);
}
diff --git a/Services/SyncService.cs b/Services/SyncService.cs
index f2151b3..0e34011 100644
--- a/Services/SyncService.cs
+++ b/Services/SyncService.cs
@@ -8,7 +8,7 @@ using System.Net.Http;
namespace UtopiaCanteenSystem.Services;
///
-/// Scheduled sync: fetches unsynced ScanRecords, POSTs them to the configured API,
+/// Scheduled sync: fetches unsynced lunch_order_transactions, POSTs them to the configured API,
/// and deletes uploaded records from local SQLite on success.
///
public class SyncService : ISyncService
@@ -32,7 +32,7 @@ public class SyncService : ISyncService
List toSync;
using (var db = _dbFactory.CreateDbContext())
{
- toSync = await db.ScanRecords
+ toSync = await db.LunchOrderTransactions
.Where(r => !r.IsSynced)
.OrderBy(r => r.ScanTime)
.ToListAsync(cancellationToken)
@@ -63,12 +63,12 @@ public class SyncService : ISyncService
var ids = toSync.Select(r => r.Id).ToList();
using (var db = _dbFactory.CreateDbContext())
{
- var records = await db.ScanRecords
+ var records = await db.LunchOrderTransactions
.Where(r => ids.Contains(r.Id))
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
- // On successful upload, delete uploaded scan records from local SQLite.
- db.ScanRecords.RemoveRange(records);
+ // On successful upload, delete uploaded scan events from local SQLite.
+ db.LunchOrderTransactions.RemoveRange(records);
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
}
diff --git a/Views/ScannerDashboardView.xaml b/Views/ScannerDashboardView.xaml
index 91cdab3..56cd05c 100644
--- a/Views/ScannerDashboardView.xaml
+++ b/Views/ScannerDashboardView.xaml
@@ -454,11 +454,13 @@
+
-
+
+
@@ -468,18 +470,17 @@
+
-
-
-
-
-
-
+
+
+
+
-
+
+