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:
parent
152d4c3d57
commit
8961bb234c
15 changed files with 82 additions and 80 deletions
|
|
@ -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 A** — Phase 1 items 1–7 + CLAUDE.md fix.
|
||||||
- **PR B** — Phase 1 item 8 (atomic writes + lock).
|
- **PR B** — Phase 1 item 8 (atomic writes + lock).
|
||||||
- **PR C** — Phase 2 items 9 + 11 + 13 + 15 + 16 + 17.
|
- **PR C** — Phase 2 mechanical cleanup: items 11, 13, 15, 16, 17.
|
||||||
- **PR D** — Phase 3 (async).
|
- **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 4 (tests) — done alongside PR C to lock in behavior.
|
- **PR E** — Phase 3 (async).
|
||||||
|
- **PR F** — Phase 4 (tests) — done alongside PR D to lock in behavior.
|
||||||
|
|
|
||||||
19
Schaad.Accounting.Common/Formatting.cs
Normal file
19
Schaad.Accounting.Common/Formatting.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Schaad.Accounting.Models;
|
using Schaad.Accounting.Models;
|
||||||
|
|
||||||
namespace Schaad.Accounting.Interfaces
|
namespace Schaad.Accounting.Interfaces
|
||||||
{
|
{
|
||||||
public interface ISplitPredefinitonRepository
|
public interface ISplitPredefinitionRepository
|
||||||
{
|
{
|
||||||
List<SplitPredefinition> GetSplitPredefinitionList();
|
List<SplitPredefinition> GetSplitPredefinitionList();
|
||||||
|
|
||||||
|
|
@ -4,17 +4,9 @@
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<RootNamespace>Schaad.Accounting</RootNamespace>
|
<RootNamespace>Schaad.Accounting</RootNamespace>
|
||||||
</PropertyGroup>
|
</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>
|
<ItemGroup>
|
||||||
<Reference Include="Schaad.Finance.Api">
|
<Reference Include="Schaad.Finance.Api">
|
||||||
<HintPath>..\dlls\Schaad.Finance.Api.dll</HintPath>
|
<HintPath>..\dlls\Schaad.Finance.Api.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Interfaces\Extensions\" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Schaad.Accounting.Repositories
|
||||||
|
|
||||||
public AccountRepository(ISettingsService settingsService) : base(settingsService)
|
public AccountRepository(ISettingsService settingsService) : base(settingsService)
|
||||||
{
|
{
|
||||||
EnsureFileExisits(ACCOUNTS);
|
EnsureAccountsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -79,35 +79,40 @@ namespace Schaad.Accounting.Repositories
|
||||||
SaveAccount(account);
|
SaveAccount(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
private new void EnsureFileExisits(string file)
|
private void EnsureAccountsFile()
|
||||||
{
|
{
|
||||||
string filePath = Path.Combine(settingsService.GetDbPath(), file);
|
string filePath = Path.Combine(settingsService.GetDbPath(), ACCOUNTS);
|
||||||
if (File.Exists(filePath) == false)
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), file);
|
return;
|
||||||
if (File.Exists(lastYearFile))
|
}
|
||||||
File.Copy(lastYearFile, filePath);
|
|
||||||
|
|
||||||
// set start balance to last bank balance
|
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), ACCOUNTS);
|
||||||
if (file.IndexOf("Accounts") > -1)
|
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 accounts = GetAccountList();
|
||||||
|
var modified = false;
|
||||||
foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa))
|
foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa))
|
||||||
{
|
{
|
||||||
// bank accounts
|
|
||||||
if (account.LastBankBalance > 0)
|
if (account.LastBankBalance > 0)
|
||||||
{
|
{
|
||||||
account.StartBalance = account.LastBankBalance;
|
account.StartBalance = account.LastBankBalance;
|
||||||
SaveAccount(account);
|
modified = true;
|
||||||
}
|
}
|
||||||
// cash accounts
|
|
||||||
else if (account.Number < 1010)
|
else if (account.Number < 1010)
|
||||||
{
|
{
|
||||||
account.StartBalance = 0;
|
account.StartBalance = 0;
|
||||||
SaveAccount(account);
|
modified = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (modified)
|
||||||
|
{
|
||||||
|
Save(accounts, ACCOUNTS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Schaad.Accounting.Repositories
|
||||||
this.settingsService = settingsService;
|
this.settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void EnsureFileExisits(string fileName)
|
protected void EnsureFileExists(string fileName)
|
||||||
{
|
{
|
||||||
string filePath = Path.Combine(settingsService.GetDbPath(), fileName);
|
string filePath = Path.Combine(settingsService.GetDbPath(), fileName);
|
||||||
lock (GetLock(filePath))
|
lock (GetLock(filePath))
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Schaad.Accounting.Repositories
|
||||||
|
|
||||||
public BookingRuleRepository(ISettingsService settingsService) : base(settingsService)
|
public BookingRuleRepository(ISettingsService settingsService) : base(settingsService)
|
||||||
{
|
{
|
||||||
EnsureFileExisits(BOOKING_RULES);
|
EnsureFileExists(BOOKING_RULES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Schaad.Accounting.Repositories
|
||||||
|
|
||||||
public BookingTextRepository(ISettingsService settingsService) : base(settingsService)
|
public BookingTextRepository(ISettingsService settingsService) : base(settingsService)
|
||||||
{
|
{
|
||||||
EnsureFileExisits(BOOKING_TEXTS);
|
EnsureFileExists(BOOKING_TEXTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Schaad.Accounting.Interfaces;
|
using Schaad.Accounting.Interfaces;
|
||||||
|
|
@ -6,17 +6,17 @@ using Schaad.Accounting.Models;
|
||||||
|
|
||||||
namespace Schaad.Accounting.Repositories
|
namespace Schaad.Accounting.Repositories
|
||||||
{
|
{
|
||||||
public class SplitPredefinitonRepository : BaseRepository, ISplitPredefinitonRepository
|
public class SplitPredefinitionRepository : BaseRepository, ISplitPredefinitionRepository
|
||||||
{
|
{
|
||||||
private readonly string SPLIT_PREDEFINITION = "SplitPredefinitions.xml";
|
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>
|
/// <summary>
|
||||||
/// Load booking rules
|
/// Load split predefinitions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<SplitPredefinition> GetSplitPredefinitionList()
|
public List<SplitPredefinition> GetSplitPredefinitionList()
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Schaad.Accounting.Repositories
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save a booking rule (insert/update)
|
/// Save a split predefinition (insert/update)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SaveSplitPredefinition(SplitPredefinition splitPredefinition)
|
public void SaveSplitPredefinition(SplitPredefinition splitPredefinition)
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Schaad.Accounting.Repositories
|
||||||
|
|
||||||
public SubclassRepository(ISettingsService settingsService) : base(settingsService)
|
public SubclassRepository(ISettingsService settingsService) : base(settingsService)
|
||||||
{
|
{
|
||||||
EnsureFileExisits(SUBCLASSES);
|
EnsureFileExists(SUBCLASSES);
|
||||||
|
|
||||||
classes.Add(ClassIds.Activa, "Aktiven");
|
classes.Add(ClassIds.Activa, "Aktiven");
|
||||||
classes.Add(ClassIds.Passiva, "Passiven");
|
classes.Add(ClassIds.Passiva, "Passiven");
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -134,25 +133,18 @@ namespace Schaad.Accounting.Services
|
||||||
|
|
||||||
if (trx.OriginAccountId == accountId)
|
if (trx.OriginAccountId == accountId)
|
||||||
{
|
{
|
||||||
debit = ToFormattedString(trx.Value);
|
debit = trx.Value.ToFormattedString();
|
||||||
signedValue = -trx.Value;
|
signedValue = -trx.Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
credit = ToFormattedString(trx.Value);
|
credit = trx.Value.ToFormattedString();
|
||||||
}
|
}
|
||||||
balance += signedValue;
|
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());
|
var fileBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(sb.ToString());
|
||||||
return fileBytes;
|
return fileBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ToFormattedString(decimal value)
|
|
||||||
{
|
|
||||||
var culture = new CultureInfo("de-CH");
|
|
||||||
culture.NumberFormat.NumberGroupSeparator = "'";
|
|
||||||
return value.ToString("#,0.00", culture);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FreeSpire.PDF" Version="10.2.0" />
|
<PackageReference Include="FreeSpire.PDF" Version="10.2.0" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
<ProjectReference Include="..\Schaad.Accounting.Common\Schaad.Accounting.Common.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Globalization;
|
using Schaad.Accounting.Interfaces;
|
||||||
using Schaad.Accounting.Interfaces;
|
|
||||||
using Schaad.Accounting.Repositories;
|
using Schaad.Accounting.Repositories;
|
||||||
using Schaad.Accounting.Services;
|
using Schaad.Accounting.Services;
|
||||||
using Schaad.Finance.Api;
|
using Schaad.Finance.Api;
|
||||||
|
|
@ -9,13 +8,6 @@ namespace Schaad.Accounting.UI
|
||||||
{
|
{
|
||||||
public static class Extensions
|
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)
|
public static IServiceCollection AddAccounting(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
// Singleton because MyHeader triggers a full page reload (forceLoad: true) after
|
// 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<IBankTransactionRepository, BankTransactionRepository>();
|
||||||
services.AddScoped<IBookingRuleRepository, BookingRuleRepository>();
|
services.AddScoped<IBookingRuleRepository, BookingRuleRepository>();
|
||||||
services.AddScoped<IBookingTextRepository, BookingTextRepository>();
|
services.AddScoped<IBookingTextRepository, BookingTextRepository>();
|
||||||
services.AddScoped<ISplitPredefinitonRepository, SplitPredefinitonRepository>();
|
services.AddScoped<ISplitPredefinitionRepository, SplitPredefinitionRepository>();
|
||||||
services.AddScoped<ISubclassRepository, SubclassRepository>();
|
services.AddScoped<ISubclassRepository, SubclassRepository>();
|
||||||
services.AddScoped<ITransactionRepository, TransactionRepository>();
|
services.AddScoped<ITransactionRepository, TransactionRepository>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using Microsoft.AspNetCore.Localization;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.FluentUI.AspNetCore.Components;
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
using Schaad.Accounting.Datasets;
|
using Schaad.Accounting.Datasets;
|
||||||
|
|
@ -38,15 +39,19 @@ namespace Schaad.Accounting.UI
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
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.UseAntiforgery();
|
||||||
app.MapStaticAssets();
|
app.MapStaticAssets();
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
var culture = new CultureInfo("de-CH");
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = culture;
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue