Feature: expand MerchantService with full CRUD operations
Extend MerchantService with additional operations: - GetMerchantByIdAsync: Retrieve merchant with optional related data - GetAllMerchantsWithStatsAsync: Get all merchants with transaction/mapping counts - UpdateMerchantAsync: Update merchant name with duplicate validation - DeleteMerchantAsync: Delete merchant (unlinks transactions and mappings) Includes DTOs for merchant stats, update results, and delete results. Consolidates merchant management logic previously scattered across PageModels. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,10 @@ namespace MoneyMap.Services
|
||||
Task<Merchant?> FindByNameAsync(string name);
|
||||
Task<Merchant> GetOrCreateAsync(string name);
|
||||
Task<int?> GetOrCreateIdAsync(string? name);
|
||||
Task<Merchant?> GetMerchantByIdAsync(int id, bool includeRelated = false);
|
||||
Task<List<MerchantWithStats>> GetAllMerchantsWithStatsAsync();
|
||||
Task<MerchantUpdateResult> UpdateMerchantAsync(int id, string newName);
|
||||
Task<MerchantDeleteResult> DeleteMerchantAsync(int id);
|
||||
}
|
||||
|
||||
public class MerchantService : IMerchantService
|
||||
@@ -54,5 +58,127 @@ namespace MoneyMap.Services
|
||||
var merchant = await GetOrCreateAsync(name);
|
||||
return merchant.Id;
|
||||
}
|
||||
|
||||
public async Task<Merchant?> GetMerchantByIdAsync(int id, bool includeRelated = false)
|
||||
{
|
||||
var query = _db.Merchants.AsQueryable();
|
||||
|
||||
if (includeRelated)
|
||||
{
|
||||
query = query
|
||||
.Include(m => m.Transactions)
|
||||
.Include(m => m.CategoryMappings);
|
||||
}
|
||||
|
||||
return await query.FirstOrDefaultAsync(m => m.Id == id);
|
||||
}
|
||||
|
||||
public async Task<List<MerchantWithStats>> GetAllMerchantsWithStatsAsync()
|
||||
{
|
||||
var merchants = await _db.Merchants
|
||||
.Include(m => m.Transactions)
|
||||
.Include(m => m.CategoryMappings)
|
||||
.OrderBy(m => m.Name)
|
||||
.ToListAsync();
|
||||
|
||||
return merchants.Select(m => new MerchantWithStats
|
||||
{
|
||||
Id = m.Id,
|
||||
Name = m.Name,
|
||||
TransactionCount = m.Transactions.Count,
|
||||
MappingCount = m.CategoryMappings.Count
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<MerchantUpdateResult> UpdateMerchantAsync(int id, string newName)
|
||||
{
|
||||
var merchant = await _db.Merchants.FindAsync(id);
|
||||
if (merchant == null)
|
||||
{
|
||||
return new MerchantUpdateResult
|
||||
{
|
||||
Success = false,
|
||||
Message = "Merchant not found."
|
||||
};
|
||||
}
|
||||
|
||||
var trimmedName = newName.Trim();
|
||||
|
||||
// Check if another merchant with the same name exists
|
||||
var existing = await _db.Merchants
|
||||
.FirstOrDefaultAsync(m => m.Name == trimmedName && m.Id != id);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
return new MerchantUpdateResult
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Merchant '{trimmedName}' already exists."
|
||||
};
|
||||
}
|
||||
|
||||
merchant.Name = trimmedName;
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return new MerchantUpdateResult
|
||||
{
|
||||
Success = true,
|
||||
Message = "Merchant updated successfully."
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<MerchantDeleteResult> DeleteMerchantAsync(int id)
|
||||
{
|
||||
var merchant = await _db.Merchants
|
||||
.Include(m => m.Transactions)
|
||||
.Include(m => m.CategoryMappings)
|
||||
.FirstOrDefaultAsync(m => m.Id == id);
|
||||
|
||||
if (merchant == null)
|
||||
{
|
||||
return new MerchantDeleteResult
|
||||
{
|
||||
Success = false,
|
||||
Message = "Merchant not found."
|
||||
};
|
||||
}
|
||||
|
||||
var transactionCount = merchant.Transactions.Count;
|
||||
var mappingCount = merchant.CategoryMappings.Count;
|
||||
|
||||
_db.Merchants.Remove(merchant);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return new MerchantDeleteResult
|
||||
{
|
||||
Success = true,
|
||||
Message = $"Deleted merchant '{merchant.Name}'. {transactionCount} transactions and {mappingCount} category mappings are now unlinked.",
|
||||
TransactionCount = transactionCount,
|
||||
MappingCount = mappingCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// DTOs
|
||||
public class MerchantWithStats
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public int TransactionCount { get; set; }
|
||||
public int MappingCount { get; set; }
|
||||
}
|
||||
|
||||
public class MerchantUpdateResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
|
||||
public class MerchantDeleteResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = "";
|
||||
public int TransactionCount { get; set; }
|
||||
public int MappingCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user