Files
Fuchs_Intranet/Fuchs/Docs/Decisions/0010-mfr-sync-hosted-in-web-app.md
Stefan 49e3ed2673 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.
2026-07-16 13:34:23 +02:00

4.6 KiB

status, date, applyTo, supersededBy
status date applyTo supersededBy
Accepted 2026-07-15
Fuchs_DataService/**
Fuchs/Services/PeriodicHostedService.cs
Fuchs/Program.cs

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_asyncUpdateRequested_asyncGetInvoiceFiles_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.