chnage in layout of view all 'order' modal and chnage the local table name from ScanRecords to lunch_order_transactions

pull/1/head
SYED MUSTUFA AHMED NAQVI 2026-02-09 12:17:08 +05:00
parent cb35b6581a
commit 306e03e71a
7 changed files with 106 additions and 39 deletions

View File

@ -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

View File

@ -6,7 +6,7 @@ using UtopiaCanteenSystem.Models;
namespace UtopiaCanteenSystem.Data;
/// <summary>
/// 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.
/// </summary>
public class AppDbContext : DbContext
@ -16,7 +16,7 @@ public class AppDbContext : DbContext
"utopia_canteen.db");
public DbSet<Labour> Labour { get; set; }
public DbSet<ScanRecord> ScanRecords { get; set; }
public DbSet<ScanRecord> LunchOrderTransactions { get; set; }
public DbSet<AdminLoginRecord> 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<ScanRecord>(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();
}
/// <summary>
/// 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.
/// </summary>
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
}
}
/// <summary>
/// Lightweight schema upgrade: add SiteId and DeviceId to lunch_order_transactions if missing (no EF migrations).
/// Does not delete any data.
/// </summary>
private void UpgradeScanRecordsSchemaIfNeeded()
private void UpgradeLunchOrderTransactionsSchemaIfNeeded()
{
try
{
@ -78,23 +136,23 @@ public class AppDbContext : DbContext
var columns = new List<string>();
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();
}
}

View File

@ -11,7 +11,7 @@ public interface IRfidService
{
/// <summary>
/// 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.
/// </summary>
(bool Success, string Message) ProcessScan(string cardId);

View File

@ -1,7 +1,7 @@
namespace UtopiaCanteenSystem.Services;
/// <summary>
/// Syncs unsynced ScanRecords to the configured UIND API endpoint.
/// Syncs unsynced lunch_order_transactions to the configured UIND API endpoint.
/// </summary>
public interface ISyncService
{

View File

@ -9,7 +9,7 @@ namespace UtopiaCanteenSystem.Services;
/// <summary>
/// Handles RFID scan logic: validates input, enforces configurable timeout,
/// and saves ScanRecord to SQLite.
/// and saves scan events to SQLite (lunch_order_transactions table).
/// </summary>
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<ScanRecord>();
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<int> 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<int> 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);
}

View File

@ -8,7 +8,7 @@ using System.Net.Http;
namespace UtopiaCanteenSystem.Services;
/// <summary>
/// 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.
/// </summary>
public class SyncService : ISyncService
@ -32,7 +32,7 @@ public class SyncService : ISyncService
List<ScanRecord> 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);
}
}

View File

@ -454,11 +454,13 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="0"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Employee" FontSize="12" FontWeight="SemiBold" Foreground="{StaticResource MutedTextBrush}"/>
<TextBlock Grid.Column="1" Text="Scan ID" FontSize="12" FontWeight="SemiBold" Foreground="{StaticResource MutedTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="2" Text="Time" FontSize="12" FontWeight="SemiBold" Foreground="{StaticResource MutedTextBrush}" HorizontalAlignment="Right"/>
<TextBlock Grid.Column="2" Text="Order" FontSize="12" FontWeight="SemiBold" Foreground="{StaticResource MutedTextBrush}" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="3" Text="Time" FontSize="12" FontWeight="SemiBold" Foreground="{StaticResource MutedTextBrush}" HorizontalAlignment="Right"/>
</Grid>
<ItemsControl ItemsSource="{Binding TodayOrderHistory}">
<ItemsControl.ItemTemplate>
@ -468,18 +470,17 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="0"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="140"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
<Border Width="36" Height="36" Margin="0,0,10,0" CornerRadius="18" Background="{StaticResource AccentLightBrush}" BorderBrush="{StaticResource AccentBrush}" BorderThickness="1">
<TextBlock Text="—" FontSize="14" Foreground="{StaticResource AccentBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<StackPanel VerticalAlignment="Center">
<!-- Employee column: show EmployeeId (top) + EmployeeName (middle) + Order item (bottom, muted) -->
<TextBlock Text="{Binding EmployeeId}" FontSize="14" FontWeight="Bold" Foreground="{StaticResource TitleTextBrush}" TextTrimming="CharacterEllipsis"/>
<TextBlock Text="{Binding EmployeeName}" FontSize="12" Foreground="{StaticResource MutedTextBrush}" TextTrimming="CharacterEllipsis" Margin="0,1,0,0"/>
<TextBlock Text="{Binding OrderItem, StringFormat='Order: {0}'}" FontSize="11" Foreground="{StaticResource MutedTextBrush}" TextTrimming="CharacterEllipsis" Margin="0,1,0,0"/>
</StackPanel>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding EmployeeId}" FontSize="14" FontWeight="Bold" Foreground="{StaticResource TitleTextBrush}" TextTrimming="CharacterEllipsis"/>
<TextBlock Text="{Binding EmployeeName}" FontSize="12" Foreground="{StaticResource MutedTextBrush}" TextTrimming="CharacterEllipsis" Margin="0,1,0,0"/>
</StackPanel>
</StackPanel>
<TextBlock Grid.Column="1"
Text="{Binding ScanId}"
@ -489,7 +490,15 @@
HorizontalAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxWidth="110"/>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock Grid.Column="2"
Text="{Binding OrderItem}"
FontSize="13"
Foreground="{StaticResource TitleTextBrush}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextTrimming="CharacterEllipsis"
MaxWidth="140"/>
<StackPanel Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock Text="{Binding TimeDisplay}" FontSize="13" FontWeight="Bold" Foreground="{StaticResource TitleTextBrush}"/>
<TextBlock Text="{Binding RelativeDateLabel}" FontSize="11" Foreground="{StaticResource MutedTextBrush}" Margin="0,1,0,0"/>
</StackPanel>