Add unit tests for Fuchs_DataService and related components
- Introduced comprehensive unit tests for the Fuchs_DataService library, covering DATEV header formatting, CSV/XML generation, and FdsMfrClient construction. - Implemented tests for FdsMfr.UpdateNeed parsing and FdsShared utility helpers, ensuring correct functionality and stability. - Added tests for FdsConfig and FdsMfrClient to validate configuration resolution and client construction. Document decisions on backend-authoritative invoice and reminder handling - Created ADR 0008 to clarify that all invoice types and reminder stages are backend-authoritative during drafting and previewing. - Established that all calculations and settings must be processed server-side, ensuring consistency between online editor and PDF outputs. Define irreversible mutations for set-price modes in invoices - Documented ADR 0009 to specify that the "Set mit Preis" and "Nur Set mit Preis" operations are irreversible mutations affecting service request blocks. - Clarified that these operations are not display toggles but actual data changes, ensuring clear expectations for invoice handling. Transition MFR ERP sync to in-process execution within the web app - Created ADR 0010 to outline the migration of Fuchs_DataService from a standalone service to an in-process library within the Fuchs web application. - Updated configuration and logging management to be handled by the host application, streamlining the sync process. Add publish profile and periodic hosted service for job scheduling - Introduced a publish profile for deployment to a specified folder. - Implemented PeriodicHostedService to manage multiple independent jobs, including the MFR ERP sync, with configurable execution intervals. Add dotnet-tools.json for EF Core CLI tools - Included dotnet-tools.json to manage the version of dotnet-ef for Entity Framework Core migrations and commands.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
---
|
||||
status: Accepted
|
||||
date: 2026-07-15
|
||||
applyTo:
|
||||
- "Fuchs_DataService/**"
|
||||
- "Fuchs/Services/PeriodicHostedService.cs"
|
||||
- "Fuchs/Program.cs"
|
||||
supersededBy: ""
|
||||
---
|
||||
|
||||
# 0010 — MFR ERP sync runs in-process in the web app; Fuchs_DataService is a library
|
||||
|
||||
## Context
|
||||
`Fuchs_DataService` was a standalone console/Windows Service hosted by **Topshelf**.
|
||||
It carried its own `appsettings.json`, its own file-based configuration bootstrap
|
||||
(`FdsConfig.Initialize()` reading the file), its own logging provider
|
||||
(`FdsLoggerProvider`/`AddFdsLogging`), and a machine-name guard in `Main()` that
|
||||
disabled the service on developer PCs. In practice the web app (`Fuchs`) already
|
||||
referenced the project, already called `fds.FdsConfig.Initialize(builder.Configuration)`,
|
||||
already registered `IFdsMfr`, and already created `FdsMfrClient` via
|
||||
`IMfrClientFactory` — so the sync logic and the web app were sharing the same code
|
||||
and the same connection strings while the service kept a second, parallel copy of
|
||||
configuration/logging/hosting.
|
||||
|
||||
Maintaining a separate process, a second `appsettings.json` (duplicating connection
|
||||
strings + MFR credentials), Topshelf, and a machine-name guard added drift risk and
|
||||
operational overhead for no benefit the web host couldn't provide.
|
||||
|
||||
## Decision
|
||||
- **`Fuchs_DataService` is now a class library** (no `OutputType Exe`, no Topshelf,
|
||||
no own `appsettings.json`, no `install.bat`/`un-install.bat`, no
|
||||
`System.Configuration.ConfigurationManager`). It contains only the MFR sync
|
||||
logic (`FdsMfr`/`IFdsMfr`, `FdsMfrClient`), the DATEV/zip helpers, `FdsShared`,
|
||||
`FdsDebug`, and `FdsConfig`.
|
||||
- **The host owns configuration.** `FdsConfig` keeps only
|
||||
`Initialize(IConfiguration)` (the file-based overload is gone). The Fuchs web app
|
||||
injects its `IConfiguration`; connection strings (`fuchs_fds_ConnectionString`)
|
||||
and MFR credentials (`Fds:MFR_*`, Key Vault-managed) come from Fuchs.
|
||||
- **The host owns logging.** `FdsLoggerProvider`/`AddFdsLogging` were removed; the
|
||||
library uses only `ILogger`/`ILoggerFactory` injected from Fuchs's logging
|
||||
(`AddFuchsLogging`). The library depends only on
|
||||
`Microsoft.Extensions.Logging.Abstractions` + `Microsoft.Extensions.Configuration.Binder`.
|
||||
- **The sync runs in-process.** `PeriodicHostedService` (the generic
|
||||
multi-job `BackgroundService`) moved to `Fuchs/Services/` and is registered in
|
||||
`Program.cs` as a hosted service. The single `MfrSync` job calls
|
||||
`UpdateIfNecessary_async` → `UpdateRequested_async` → `GetInvoiceFiles_async`.
|
||||
- **A config flag replaces the machine-name guard.** Registration is gated by
|
||||
`Fds:SyncEnabled` (default `false` when unset): `true` in production
|
||||
`appsettings.json`, `false` in `appsettings.Development.json`, so developer
|
||||
machines never poll the ERP. Interval (`Fds:ExecutionFrequency_Minutes`, default
|
||||
15) and debug verbosity (`Fds:DebugDetails`) also come from the `Fds` section.
|
||||
|
||||
## Consequences
|
||||
- One process, one configuration surface, one logging pipeline. The sync inherits
|
||||
the web app's OpenTelemetry, DI, and lifetime automatically.
|
||||
- **Instance fan-out is a consideration:** the sync now runs in *every* web instance
|
||||
where `Fds:SyncEnabled` is true. The intranet is deployed single-instance, so this
|
||||
is acceptable; if Fuchs is ever scaled out, gate the sync to a single instance
|
||||
(leader election / dedicated instance flag) to avoid concurrent MFR polling.
|
||||
- Enabling/disabling the sync per environment is now a config change, not a
|
||||
redeploy of a separate service.
|
||||
- `Fuchs_DataService` is intentionally kept as a separate project (not folded into
|
||||
`Fuchs`) so the sync logic stays isolated and unit-testable; `Fuchs.Tests` covers
|
||||
it via `InternalsVisibleTo`.
|
||||
- **The `Squid-Box.SevenZipSharp` native dependency (and the bundled `7z.dll`) was
|
||||
removed** from both `Fuchs_DataService` and `Fuchs`. The only live archive use — the
|
||||
DATEV export — is a plain, unencrypted zip, now produced via the native
|
||||
`OCORE.zip.filesToZipArchive` (`System.IO.Compression`). The 7-Zip-only paths
|
||||
(`.7z`/LZMA2, AES-encrypted archives, extraction, `FastAppend`) had no callers.
|
||||
Trade-off accepted: `System.IO.Compression` cannot produce `.7z` or password/AES
|
||||
archives; if that is ever required, a compression library must be reintroduced.
|
||||
|
||||
## Alternatives considered
|
||||
- **Native `dotnet` Worker Service (separate process).** Would modernize off
|
||||
Topshelf but keep the duplicate-config/duplicate-logging/second-process problem.
|
||||
Rejected because the web app already hosts everything the sync needs.
|
||||
- **Fold the code directly into `Fuchs`.** Rejected to preserve a clean, separately
|
||||
testable sync library and avoid enlarging the web project.
|
||||
Reference in New Issue
Block a user