PR C: mechanical cleanup (typos, packages, formatting, culture)

- Consolidate decimal.ToFormattedString into Schaad.Accounting.Formatting
- Rename EnsureFileExisits -> EnsureFileExists and
  SplitPredefiniton* -> SplitPredefinition* (class, interface, files, DI reg)
- AccountRepository: single-save year-rollover start-balance seed
- Drop legacy .NET Standard packages redundant on net9.0 and the stale
  Interfaces\Extensions\ folder entry
- Set culture via RequestLocalizationOptions middleware
- Plan: item 9 (unit of work) split off into a dedicated PR D

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Claudio Schaad 2026-07-02 20:46:55 +02:00
parent 152d4c3d57
commit 8961bb234c
15 changed files with 82 additions and 80 deletions

View file

@ -97,6 +97,7 @@ Analysis and phased plan produced 2026-07-02. See conversation history for full
- **PR A** — Phase 1 items 1–7 + CLAUDE.md fix.
- **PR B** — Phase 1 item 8 (atomic writes + lock).
- **PR C** — Phase 2 items 9 + 11 + 13 + 15 + 16 + 17.
- **PR D** — Phase 3 (async).
- **PR E** — Phase 4 (tests) — done alongside PR C to lock in behavior.
- **PR C** — Phase 2 mechanical cleanup: items 11, 13, 15, 16, 17.
- **PR D** — Phase 2 item 9: per-request unit of work / cache. Split off from PR C because it is invasive enough to warrant its own review.
- **PR E** — Phase 3 (async).
- **PR F** — Phase 4 (tests) — done alongside PR D to lock in behavior.

View file

@ -0,0 +1,19 @@
using System.Globalization;
namespace Schaad.Accounting
{
public static class Formatting
{
private static readonly CultureInfo SwissCulture = CreateSwissCulture();
public static string ToFormattedString(this decimal value)
=> value.ToString("#,0.00", SwissCulture);
private static CultureInfo CreateSwissCulture()
{
var culture = new CultureInfo("de-CH");
culture.NumberFormat.NumberGroupSeparator = "'";
return culture;
}
}
}

View file

@ -1,12 +1,12 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Schaad.Accounting.Models;
namespace Schaad.Accounting.Interfaces
{
public interface ISplitPredefinitonRepository
public interface ISplitPredefinitionRepository
{
List<SplitPredefinition> GetSplitPredefinitionList();
void SaveSplitPredefinition(SplitPredefinition splitPredefinition);
}
}
}

View file

@ -4,17 +4,9 @@
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Schaad.Accounting</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Schaad.Finance.Api">
<HintPath>..\dlls\Schaad.Finance.Api.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Interfaces\Extensions\" />
</ItemGroup>
</Project>

View file

@ -13,7 +13,7 @@ namespace Schaad.Accounting.Repositories
public AccountRepository(ISettingsService settingsService) : base(settingsService)
{
EnsureFileExisits(ACCOUNTS);
EnsureAccountsFile();
}
@ -79,35 +79,40 @@ namespace Schaad.Accounting.Repositories
SaveAccount(account);
}
private new void EnsureFileExisits(string file)
private void EnsureAccountsFile()
{
string filePath = Path.Combine(settingsService.GetDbPath(), file);
if (File.Exists(filePath) == false)
string filePath = Path.Combine(settingsService.GetDbPath(), ACCOUNTS);
if (File.Exists(filePath))
{
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), file);
if (File.Exists(lastYearFile))
File.Copy(lastYearFile, filePath);
return;
}
// set start balance to last bank balance
if (file.IndexOf("Accounts") > -1)
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), ACCOUNTS);
if (File.Exists(lastYearFile))
{
File.Copy(lastYearFile, filePath);
}
// Year rollover: seed start balances from the previous year's closing values.
// Bank accounts start at the last known bank balance; cash accounts start at zero.
var accounts = GetAccountList();
var modified = false;
foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa))
{
if (account.LastBankBalance > 0)
{
var accounts = GetAccountList();
foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa))
{
// bank accounts
if (account.LastBankBalance > 0)
{
account.StartBalance = account.LastBankBalance;
SaveAccount(account);
}
// cash accounts
else if (account.Number < 1010)
{
account.StartBalance = 0;
SaveAccount(account);
}
}
account.StartBalance = account.LastBankBalance;
modified = true;
}
else if (account.Number < 1010)
{
account.StartBalance = 0;
modified = true;
}
}
if (modified)
{
Save(accounts, ACCOUNTS);
}
}
}

View file

@ -22,7 +22,7 @@ namespace Schaad.Accounting.Repositories
this.settingsService = settingsService;
}
protected void EnsureFileExisits(string fileName)
protected void EnsureFileExists(string fileName)
{
string filePath = Path.Combine(settingsService.GetDbPath(), fileName);
lock (GetLock(filePath))

View file

@ -12,7 +12,7 @@ namespace Schaad.Accounting.Repositories
public BookingRuleRepository(ISettingsService settingsService) : base(settingsService)
{
EnsureFileExisits(BOOKING_RULES);
EnsureFileExists(BOOKING_RULES);
}
/// <summary>

View file

@ -12,7 +12,7 @@ namespace Schaad.Accounting.Repositories
public BookingTextRepository(ISettingsService settingsService) : base(settingsService)
{
EnsureFileExisits(BOOKING_TEXTS);
EnsureFileExists(BOOKING_TEXTS);
}
/// <summary>

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Schaad.Accounting.Interfaces;
@ -6,17 +6,17 @@ using Schaad.Accounting.Models;
namespace Schaad.Accounting.Repositories
{
public class SplitPredefinitonRepository : BaseRepository, ISplitPredefinitonRepository
public class SplitPredefinitionRepository : BaseRepository, ISplitPredefinitionRepository
{
private readonly string SPLIT_PREDEFINITION = "SplitPredefinitions.xml";
public SplitPredefinitonRepository(ISettingsService settingsService) : base(settingsService)
public SplitPredefinitionRepository(ISettingsService settingsService) : base(settingsService)
{
EnsureFileExisits(SPLIT_PREDEFINITION);
EnsureFileExists(SPLIT_PREDEFINITION);
}
/// <summary>
/// Load booking rules
/// Load split predefinitions
/// </summary>
public List<SplitPredefinition> GetSplitPredefinitionList()
{
@ -25,7 +25,7 @@ namespace Schaad.Accounting.Repositories
}
/// <summary>
/// Save a booking rule (insert/update)
/// Save a split predefinition (insert/update)
/// </summary>
public void SaveSplitPredefinition(SplitPredefinition splitPredefinition)
{
@ -42,4 +42,4 @@ namespace Schaad.Accounting.Repositories
Save(definitions, SPLIT_PREDEFINITION);
}
}
}
}

View file

@ -13,7 +13,7 @@ namespace Schaad.Accounting.Repositories
public SubclassRepository(ISettingsService settingsService) : base(settingsService)
{
EnsureFileExisits(SUBCLASSES);
EnsureFileExists(SUBCLASSES);
classes.Add(ClassIds.Activa, "Aktiven");
classes.Add(ClassIds.Passiva, "Passiven");

View file

@ -4,9 +4,6 @@
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
</ItemGroup>

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO.Compression;
using System.Linq;
using System.Text;
@ -134,25 +133,18 @@ namespace Schaad.Accounting.Services
if (trx.OriginAccountId == accountId)
{
debit = ToFormattedString(trx.Value);
debit = trx.Value.ToFormattedString();
signedValue = -trx.Value;
}
else
{
credit = ToFormattedString(trx.Value);
credit = trx.Value.ToFormattedString();
}
balance += signedValue;
sb.AppendLine($"{trx.BookingDate:dd.MM.yyyy};{trx.ValueDate:dd.MM.yyyy};{trx.Text};{debit};{credit};{ToFormattedString(balance)}");
sb.AppendLine($"{trx.BookingDate:dd.MM.yyyy};{trx.ValueDate:dd.MM.yyyy};{trx.Text};{debit};{credit};{balance.ToFormattedString()}");
}
var fileBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(sb.ToString());
return fileBytes;
}
private string ToFormattedString(decimal value)
{
var culture = new CultureInfo("de-CH");
culture.NumberFormat.NumberGroupSeparator = "'";
return value.ToString("#,0.00", culture);
}
}
}

View file

@ -6,7 +6,6 @@
<ItemGroup>
<PackageReference Include="FreeSpire.PDF" Version="10.2.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />

View file

@ -1,5 +1,4 @@
using System.Globalization;
using Schaad.Accounting.Interfaces;
using Schaad.Accounting.Interfaces;
using Schaad.Accounting.Repositories;
using Schaad.Accounting.Services;
using Schaad.Finance.Api;
@ -9,13 +8,6 @@ namespace Schaad.Accounting.UI
{
public static class Extensions
{
public static string ToFormattedString(this decimal value)
{
var culture = new CultureInfo("de-CH");
culture.NumberFormat.NumberGroupSeparator = "'";
return value.ToString("#,0.00", culture);
}
public static IServiceCollection AddAccounting(this IServiceCollection services)
{
// Singleton because MyHeader triggers a full page reload (forceLoad: true) after
@ -28,7 +20,7 @@ namespace Schaad.Accounting.UI
services.AddScoped<IBankTransactionRepository, BankTransactionRepository>();
services.AddScoped<IBookingRuleRepository, BookingRuleRepository>();
services.AddScoped<IBookingTextRepository, BookingTextRepository>();
services.AddScoped<ISplitPredefinitonRepository, SplitPredefinitonRepository>();
services.AddScoped<ISplitPredefinitionRepository, SplitPredefinitionRepository>();
services.AddScoped<ISubclassRepository, SubclassRepository>();
services.AddScoped<ITransactionRepository, TransactionRepository>();

View file

@ -1,4 +1,5 @@
using System.Globalization;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.Options;
using Microsoft.FluentUI.AspNetCore.Components;
using Schaad.Accounting.Datasets;
@ -38,15 +39,19 @@ namespace Schaad.Accounting.UI
app.UseHttpsRedirection();
var supportedCultures = new[] { new CultureInfo("de-CH") };
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(supportedCultures[0]),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
var culture = new CultureInfo("de-CH");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
app.Run();
}
}