This refactors the merchant field from a simple string column to a normalized entity with proper foreign key relationships: **Database Changes:** - Created Merchant entity/table with unique Name constraint - Replaced Transaction.Merchant (string) with Transaction.MerchantId (FK) - Replaced CategoryMapping.Merchant (string) with CategoryMapping.MerchantId (FK) - Added proper foreign key constraints with SET NULL on delete - Added indexes on MerchantId columns for performance **Backend Changes:** - Created MerchantService for finding/creating merchants - Updated CategorizationResult to return MerchantId instead of merchant name - Modified TransactionCategorizer to return MerchantId from pattern matches - Updated Upload, Recategorize, and CategoryMappings to use merchant service - Updated OpenAIReceiptParser to create/link merchants from parsed receipts - Registered IMerchantService in dependency injection **UI Changes:** - Updated CategoryMappings UI to handle merchant entities (display as Merchant.Name) - Updated Transactions page merchant filter to query by merchant entity - Modified category mapping add/edit/import to create merchants on-the-fly - Updated JavaScript to pass merchant names for edit modal **Migration:** - ConvertMerchantToEntity migration handles schema conversion - Drops old string columns and creates new FK relationships - All existing merchant data is lost (acceptable for this refactoring) **Benefits:** - Database normalization - merchants stored once, referenced many times - Referential integrity with foreign keys - Easier merchant management (rename once, updates everywhere) - Foundation for future merchant features (logos, categories, etc.) - Improved query performance with proper indexes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
64 lines
2.0 KiB
C#
64 lines
2.0 KiB
C#
using CsvHelper;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using MoneyMap.Data;
|
|
using MoneyMap.Pages;
|
|
using MoneyMap.Services; // Add this for the services
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
builder.Services.AddRazorPages();
|
|
builder.Services.AddDbContext<MoneyMapContext>(options =>
|
|
options.UseSqlServer(builder.Configuration.GetConnectionString("MoneyMapDb")));
|
|
|
|
// Add session support
|
|
builder.Services.AddDistributedMemoryCache();
|
|
builder.Services.AddSession(options =>
|
|
{
|
|
options.IdleTimeout = TimeSpan.FromMinutes(30);
|
|
options.Cookie.HttpOnly = true;
|
|
options.Cookie.IsEssential = true;
|
|
options.IOTimeout = TimeSpan.FromMinutes(5); // Increase timeout for large data
|
|
});
|
|
|
|
// Add the new services here
|
|
builder.Services.AddScoped<ITransactionImporter, TransactionImporter>();
|
|
builder.Services.AddScoped<ICardResolver, CardResolver>();
|
|
builder.Services.AddScoped<ITransactionCategorizer, TransactionCategorizer>();
|
|
builder.Services.AddScoped<IMerchantService, MerchantService>();
|
|
|
|
// Dashboard services
|
|
builder.Services.AddScoped<IDashboardService, DashboardService>();
|
|
builder.Services.AddScoped<IDashboardStatsCalculator, DashboardStatsCalculator>();
|
|
builder.Services.AddScoped<ITopCategoriesProvider, TopCategoriesProvider>();
|
|
builder.Services.AddScoped<IRecentTransactionsProvider, RecentTransactionsProvider>();
|
|
builder.Services.AddScoped<IReceiptManager, ReceiptManager>();
|
|
builder.Services.AddHttpClient<IReceiptParser, OpenAIReceiptParser>();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Seed default category mappings on startup
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var categorizer = scope.ServiceProvider.GetRequiredService<ITransactionCategorizer>();
|
|
await categorizer.SeedDefaultMappingsAsync();
|
|
}
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Error");
|
|
app.UseHsts();
|
|
}
|
|
|
|
app.UseHttpsRedirection();
|
|
app.UseStaticFiles();
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseSession();
|
|
|
|
app.UseAuthorization();
|
|
|
|
app.MapRazorPages();
|
|
|
|
app.Run(); |