Files
Fuchs_Intranet/.github/copilot-instructions.md
T
Stefan c741535aec
Playwright Tests / test (push) Canceled after 0s
feat: Implement mfr truncation fix and enhance invoice handling
- Added a new SQL script to address the "String or binary data would be truncated" error in mfr sync by widening the Note fields in service objects and companies to NVARCHAR(MAX).
- Updated the table types and associated stored procedures to accommodate the changes, ensuring compatibility with existing data.
- Queued a re-sync for previously rejected rows due to truncation issues.
- Created application logins for fuchs_fds and fuchs_web with placeholder passwords for initial setup.
- Introduced functions for invoice backdating and invoice date retrieval to streamline invoice processing.
- Developed a password generation function with customizable parameters for length and character types.
- Added a random number generation function to support password creation.
2026-09-30 16:56:08 +02:00

14 KiB

Copilot Instructions

⚠️ Instruction Sync

This file (.github/copilot-instructions.md), the Claude Code instructions (/CLAUDE.md), and the Codex instructions (/CODEX.md) are three views of the same project rules and must stay in sync. Whenever you change one, make the equivalent change in the other two in the same commit. CLAUDE.md and CODEX.md may add tool-specific workflow notes, but the shared project facts (architecture, coding standards, configuration, libraries, secrets, observability) must match.

Project Overview

  • Fuchs Intranet is an ASP.NET Core (.NET 10) web application — the intranet IS the entire website, served from /.
  • Routes: /{fn?}/{id?}/{code?} → IntranetController.Index; /do/{fn?}/{id?}/{code?} → IntranetController.Do.
  • Build app: dotnet build Fuchs/Fuchs.csproj -c Debug. Build all: dotnet build Fuchs_Intranet.slnx -c Debug.
  • Build the database project whenever anything under Fuchs_Database/ changes: msbuild Fuchs_Database/FuchsDatabase.sqlproj -p:Configuration=Debug (MSBuild from the Visual Studio install, e.g. "C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe"; dotnet build cannot build the legacy SSDT project). T-SQL warnings are build errors there (ADR 0036): an unresolved or ambiguous column (SQL71502) fails the build, and a red database build is a failure like a red C# build. The working-tree SQL is not on any database until it is published, so this build is the only check the SQL gets before review.
  • Frontend assets are source-built: run the gulp tasks in Fuchs/ (npx gulp min, or npx gulp all when copied/static assets also need refreshing) whenever JS or SCSS/CSS sources change. The generated files under Fuchs/wwwroot/web/ are what the app serves.
  • Test: dotnet test --project Fuchs.Tests/Fuchs.Tests.csproj -c Debug (the root global.json opts dotnet test into the Microsoft.Testing.Platform runner that xunit.v3 needs on the .NET 10 SDK; the positional-path form no longer works).
  • Always build and run the test suite before committing. CA1416 (platform-compatibility) is suppressed solution-wide via the root .editorconfig — the app is Windows-only. The build may still emit other pre-existing analyzer warnings; only treat : error lines as failures.
  • Submodules include the OCORE projects and eRechnungLib (ZUGFeRD/Factur-X + XRechnung generation) — invoices are moving to eRechnung output.
  • Project structure (relative to Fuchs/):
    • Controllers/ — IntranetController partials (no area)
    • code/ — business logic, PDF, email, widgets, data models
    • css/intranet/ — intranet SCSS source files
    • js/intranet/ — intranet JS source files (modules in js/intranet/modules/)
    • Data/ — static data assets (images for PDF, HTML files)
    • Views/Intranet/ — Razor views; Views/Shared/_Layout.cshtml; Views/Partials/

Coding Standards

  • All code must be written in C#.
  • Keep files to a limit of 400 (max 600) lines of code to ensure maintainability and readability. Proactively refactor larger files into smaller, focused classes or components as needed.
  • Follow standard C# naming conventions (PascalCase for classes and methods, camelCase for variables and parameters).
  • Use modern, performance-oriented C# .NET 10 features and best practices, such as async/await for asynchronous programming, LINQ for data manipulation, and dependency injection for better testability and maintainability.

Configuration

  • All application settings live in Fuchs/appsettings.json — do not use Web.config or System.Configuration.ConfigurationManager.
  • App-specific settings are nested under the "Fuchs" key (e.g., _config["Fuchs:SMS_APIKey"]).
  • Connection strings are stored under the standard "ConnectionStrings" key and read via IConfiguration.GetConnectionString(...).
  • FuchsOcmsIntranet.Initialize(configuration) must be called at app start (in Program.cs) before DI registration; Fuchs_intranet receives IConfiguration via its constructor.
  • appsettings.Development.json (git-ignored) can override secrets for local development.
  • Fuchs:Email:OverrideRecipient (bound via IOptions<FuchsEmailSettings>, section Fuchs:Email) is a dev/test safety net: when non-empty, ProcessWebComService.SendEmailAsync discards the real recipient of every outbound email and redirects it to this single address instead, so a locally-enabled mailer can never reach a real tenant-owner or end-customer. Set this only in appsettings.Development.json — it must stay empty in Production (appsettings.json documents the safe empty default).

Libraries

  • Do not upgrade Spire.PDF beyond version 8.10.5.
  • Do not upgrade the vendored TinyMCE (Fuchs/wwwroot/lib/tinymce/; legacy copy at Fuchs/Scripts/tinymce/) beyond version 6.8.6 — the last MIT-licensed release; 7.0+ is commercially licensed. See Fuchs/Docs/Decisions/0013-tinymce-pinned-to-6-8-6.md.
  • Make use of OCORE libraries where possible, especially for common tasks such as logging, configuration management, and data access.
  • Whenever possible, prefer OCORE_web_pdf / OCORE PDF functions for PDF-related tasks over rewriting.
  • Do not use OCMS or OCMS_sharp; use only OCORE or OCORE_web.
  • For builds failing due to SixLabors.ImageSharp requiring a license (v4.0.0+), check copilot-instructions.md for the SixLabors license key/handling info before downgrading ImageSharp.

Services & Dependency Injection

  • Business logic lives in DI-registered services under Fuchs/Services/ behind interfaces; inject them into IntranetController (constructor injection). Do not reintroduce static God-classes or pass the whole controller into helpers.
    • IComService (email/SMS via ProcessWeb Mailer API — contract at https://processweb.de/api/mailer/fc, read it and Fuchs/Docs/Concepts/processweb-mailer-interface.md before changing it. ProcessWebComService is a thin adapter over the OCORE_web IPWMailerClientService (OCORE_web/comm/pwmailer/, owns the HTTP contract; ADR 0029) with FuchsMailerClient overriding ValidateMessage for attachment limits; a send only counts as successful when the receipt's attachmentsProcessed matches the number sent (ADR 0028); honors the Fuchs:Email:OverrideRecipient dev safety net — see Configuration), IPdfService (MigraDoc render), IInvoiceService, IReminderService, IReportService (SQL report engine via FuchsVisualization), IWidgetService, IBankingService, IMfrClientFactory, ISystemStatusService (Admin module diagnostics: config snapshot + DB/Key Vault/blob/MFR connectivity probes + test-email; restricted to fds_sys > 4).
    • Lifetimes: stateless services (IPdfService, IBankingService, IMfrClientFactory) are singletons; request-scoped DB services (IInvoiceService, IReminderService, IReportService, IWidgetService, IComService, ISystemStatusService) are scoped. Register in Program.cs.
  • FdsInvoiceData / FdsReminderData are pure data holders (parse + properties). Loading, persistence and PDF generation belong in the services — never Task.Run(...).Wait() sync-over-async.
  • Data access stays SQL-first via OCORE helpers (getSQLDataSet_async, setSQLValue_async) + stored procedures; no EF Core.

MFR ERP integration

  • MFR_RESTClient talks to the mfr (Mobile Field Report) ERP over REST/OData. Its contract (base URLs, auth, OData conventions, pagination, error/retry, deep-create + document-upload) is documented in MFR_RESTClient/Docs/mfr_interface_description.md — read it before changing the client.
  • The client uses HTTP Basic auth, a configurable timeout, and retries idempotent GETs on transient errors (429/5xx, network/timeout) with backoff. Create clients via IMfrClientFactory (don't new them). The legacy VB project files have been removed; the active project is MFR_RESTClient.csproj.

Database

  • The SQL schema source of truth is the Fuchs_Database SSDT project. The backend is SQL-first (stored procedures, table types like fds__tt__bankingtransactions, functions via OCORE helpers — no EF Core).
  • When you change a stored proc name/params or a table type, update both the SSDT project and the calling C# in the same change. Verify every [dbo].[…] the backend calls actually exists in Fuchs_Database.
  • Objects the SQL references must be in the project (a referenced-but-missing function only shows up as an SSDT build error now; the ocms_* helpers are vendored copies from the OCMSsharp ocms_mt_database project). Security/Logins.sql holds placeholder logins only so the users bind — real passwords live in Key Vault.
  • Fuchs.Tests/SqlContractTests checks every [dbo].[…] named in C# against the SSDT files (exists, EXECUTE binds, function arg counts; ADR 0036) — a proc signature change that breaks a caller fails the test suite.

Bank statement parsing (MT940 + CAMT)

  • Two parsers feed the same banking pipeline: the external MT940Parser (SWIFT text) and the in-repo CAMTParser project (ISO 20022 camt.052/053/054 XML).
  • BankingService.ParseToDatatable auto-detects the format (XML → CAMT, else MT940) and maps both into the fds__tt__bankingtransactions schema. The bam/up handler and the frontend file picker accept both (.sta/.mt940/.txt and .xml/.camt).
  • CAMTParser is namespace-agnostic (matches elements by local name) so it handles every camt schema version. Keep both parsers' column mappings aligned when changing the banking schema.

Observability

  • Use OpenTelemetry. The app's instrumentation is centralised in Fuchs/Observability/FuchsTelemetry.cs (one ActivitySource + one Meter).
  • When adding a meaningful operation: start an activity (FuchsTelemetry.StartActivity(...)), record the relevant counter/histogram, and log entry/result/timing/errors via the injected ILogger<T>. Prefer structured logging (named placeholders), never string interpolation in log messages — and never {@Foo}: plain Microsoft.Extensions.Logging (unlike Serilog) does not strip the @, it becomes a literal part of the key.
  • Tracing/metrics are always collected; OTLP export is opt-in via Fuchs:Telemetry:OtlpEndpoint. Don't add exporters that fail hard when no collector is present.
  • DB log sink (ADR 0024): every _logger.LogWarning/-Error/-Critical call additionally lands in [dbo].[fds__debuglog] via the OCORE MultiLogger SQL sink (fds__admin_logdebug_ext), gated by Fuchs:Logging:SqlSink:Enabled (default on). RequestLogScopeMiddleware (Fuchs/Logging/) attributes every log line in a request to a request id/path/authenticated user automatically. Give a log call an explicit EventId (its Name becomes the row's CodeReference) when the call site should be individually greppable — see FIS_SQLOptions, IntranetController.Do's catch-all, PdfPreviewAsync, and the banking-upload handlers for the pattern. Do not also call the retired Fuchs_intranet.debug_log — one ILogger call is the single write path; a second explicit DB write duplicates the row.

Testing

  • xUnit in Fuchs.Tests. Testing must be extensive, not superficial:
    • For every service/handler change add tests covering both an intentionally succeeding and an intentionally failing path where feasible (use stubs/mocks; the test project has InternalsVisibleTo).
    • Cover edge cases and boundary conditions (empty/null/invalid input, disabled/feature-flag-off states, API/network errors) via [Theory]/[InlineData]/[MemberData] rather than a single happy-path [Fact].
    • When behavior depends on configuration (e.g. appsettings.json vs appsettings.Development.json), prefer loading the real files layered the same way ASP.NET Core does (ConfigurationBuilder + AddJsonFile) over hand-typed literals only, so drift in the actual files is caught (see ProcessWebComServiceTests.SendEmailAsync_OverrideRecipientFromRealAppsettings_ClearsRecipientWheneverConfigured for the pattern).
    • Assert on the observable contract (e.g. the outgoing request payload) and on emitted telemetry (counters/histograms) when the code records them — not just the boolean return value.
    • Name tests MethodName_Scenario_ExpectedResult.
    • DB-bound paths that can't be unit-tested should at least have their pure logic covered.

Decisions & Concepts

  • Fuchs/Docs/Decisions/ holds immutable ADRs (architecture decision records); Fuchs/Docs/Concepts/ holds living design write-ups kept in sync with the code. Each folder's README.md explains the format, naming, and required YAML frontmatter — read it before creating or editing entries there.
  • Accepted decisions must be followed. Before working in an area covered by a decision, read it and conform to it; don't silently deviate. Every file's YAML frontmatter has an applyTo glob — scan frontmatter across the folder first (cheap) and only read the full body of entries relevant to the files you're touching.
  • Capture new decisions and concepts as they happen. When a non-obvious architectural or cross-cutting choice gets settled (by the user or in the course of implementation), add a decision in Docs/Decisions in the same change, and create/update the matching concept doc in Docs/Concepts if the subsystem's design is otherwise non-obvious from the code.

Azure Key Vault — Secret Naming

  • Secret names must satisfy the pattern ^[0-9a-zA-Z-]+$ (alphanumerics and hyphens only; no underscores, dots, or spaces).
  • Hierarchy levels are separated by -- (double hyphen), which maps to : in IConfiguration.
  • Underscores within a name segment are encoded as a single - in Key Vault and decoded back to _ when the key is reconstructed.
  • The app prefix fuchs is prepended to every secret name.
  • Format: {appname}--{Section}--{key-with-hyphens-for-underscores}
  • Examples:
    • fuchs--ConnectionStrings--ocms-ConnectionString → ConnectionStrings:ocms_ConnectionString
    • fuchs--Fuchs--SMS-APIKey → Fuchs:SMS_APIKey
    • fuchs--Fuchs--Email--Main--password → Fuchs:Email:Main:password
  • When adding new secrets: replace every _ in the original config key with - for the Key Vault name, and add the entry to ManagedSecretKeys in appsettings.json (using the same hyphenated form without the fuchs-- prefix).