Initial Commit after switching from SVN to git

This commit is contained in:
2026-05-03 01:43:52 +02:00
parent ab8638e5bb
commit a4284234b2
910 changed files with 359931 additions and 0 deletions
@@ -0,0 +1,57 @@
---
applyTo: "Fuchs_DataService/**"
---
# Fuchs_DataService Instructions
## Overview
`Fuchs_DataService` is a .NET 10 Windows Service (via **Topshelf**) that synchronises MFR entity data into the Fuchs SQL database on a configurable schedule.
## Key Classes
| Class | Role |
|-------|------|
| `FdsMainModule` | Entry point — builds `LoggerFactory`, creates `FdsService`, runs Topshelf |
| `FdsService` | Topshelf `ServiceControl` — owns `PeriodicHostedService` lifecycle |
| `PeriodicHostedService` | Runs one or more `PeriodicJobDefinition` jobs independently |
| `FdsMfr : IFdsMfr` | Singleton business logic — registered via DI constructor injection |
| `FdsMfrClient` | Wraps `MFRClient` + SQL write logic; instantiated per-use inside `FdsMfr` |
| `Archive` (FdsZip) | SevenZip wrapper; created per-use, accepts optional `ILogger<Archive>` |
| `FdsConfig` | Static config accessor — reads from `appsettings.json` under `Fds:` key |
| `FdsShared` | Static SQL/stream helpers — use `FdsDebug.DebugLog` for errors in static methods |
| `FdsDebug` | Legacy static logger — **only for use in static helper methods** |
| `FdsLoggerProvider` | Custom `ILoggerProvider`: Debug output + file + prepared DB logging |
## Dependency Injection
`FdsMfr` is the main business singleton. It receives:
- `ILogger<FdsMfr>` — for its own logging
- `ILoggerFactory` — to create loggers for `FdsMfrClient` and `Archive` it instantiates
```csharp
// FdsService constructor
var mfr = new FdsMfr(loggerFactory.CreateLogger<FdsMfr>(), loggerFactory);
```
## Configuration (`appsettings.json`)
```json
{
"ConnectionStrings": {
"fuchs_fds_ConnectionString": "...",
"fuchs_ConnectionString": "..."
},
"Fds": {
"ExecutionFrequency_Minutes": 15,
"DebugDetails": false,
"MFR_UserName": "...",
"MFR_Password": "...",
"MFR_host": "https://..."
}
}
```
## Rules
- Do **not** use `System.Configuration.ConfigurationManager` — always use `FdsConfig` / `IConfiguration`.
- Do **not** use `OCMS` or `OCMS_sharp`.
- Use `FdsSqlOptions` for all SQL calls (`getSQLDatatable_async`, `getSQLDataSet_async`, `setSQLValue_async`).
- Classes that run async work must use `async/await` — never `.Wait()` or `.Result` in new code.
- Add new periodic jobs as `PeriodicJobDefinition` entries in `FdsService` — see `periodic_service.instructions.md`.
- Logging: see `logging.instructions.md`.
@@ -0,0 +1,176 @@
# Frontend Instructions
## General Rules
- **Never edit files inside `wwwroot/` directly.** All output files are generated by the Gulp build pipeline.
- All stylesheet sources must be written in **SCSS** (`.scss`), not plain CSS.
- After adding or changing source files, ensure they are listed in `Fuchs/bdlconfig.json` so they are picked up by the build.
## Source File Locations
| Type | Source path |
|------|-------------|
| SCSS (shared/global) | `Fuchs/css/intranet/` |
| SCSS (module-specific) | `Fuchs/js/intranet/modules/<module>/` (co-located with the module JS) |
| JavaScript (core) | `Fuchs/js/intranet/` |
| JavaScript (modules) | `Fuchs/js/intranet/modules/<module>/` |
## Output / Bundle Locations
All bundles are written to `Fuchs/web/` by the Gulp pipeline. Do not reference these paths as source.
| Bundle | Context | Description |
|--------|---------|-------------|
| `web/fisb.min.css` | `intranet` | Bootstrap/login CSS |
| `web/fis.min.css` | `intranet` | Main intranet CSS |
| `web/fis.inv.min.css` | `intranet:inv` | Invoices module CSS |
| `web/fis.req.min.css` | `intranet:req` | Requests module CSS |
| `web/fis.rep.min.css` | `intranet:rep` | Reports module CSS |
| `web/fis.bam.min.css` | `intranet:bam` | BAM module CSS |
| `web/fisb.min.js` | `intranet` | Bootstrap/basic JS |
| `web/fis.min.js` | `intranet` | Main intranet JS |
| `web/fis.inv.de.js` | `intranet:inv` | Invoices module JS |
| `web/fis.req.de.js` | `intranet:req` | Requests module JS |
| `web/fis.rep.de.js` | `intranet:rep` | Reports module JS |
| `web/fis.bam.de.js` | `intranet:bam` | BAM module JS |
## bdlconfig.json Entry Format
`Fuchs/bdlconfig.json` drives all bundle definitions. Each entry has this shape:
```json
{
"context": "intranet",
"outputFileName": "web/<bundle-name>.min.css",
"inputFiles": [
"css/intranet/oci_variables.scss",
"css/intranet/fis_variables.scss",
"css/intranet/<your-file>.scss"
],
"minify": { "enabled": true }
}
```
### SCSS Variable Prepend Rule
Every CSS bundle **must** include the two variable files as the first `inputFiles` entries, in this order:
```
css/intranet/oci_variables.scss
css/intranet/fis_variables.scss
```
These define the shared design tokens used across all SCSS files. Omitting them will cause compilation errors.
### Module Co-location Pattern
Module-specific SCSS files live alongside their JavaScript counterparts under `js/intranet/modules/<module>/`. When adding a new module:
1. Create `js/intranet/modules/<module>/<module>.scss` for module styles.
2. Add that path to the appropriate context bundle in `bdlconfig.json` (after the variable files).
3. Create `js/intranet/modules/<module>/<module>.js` for module logic.
4. Add that path to the corresponding JS bundle in `bdlconfig.json`.
## Build Pipeline
The Gulp 4 pipeline (`Fuchs/gulpfile.js`) reads `bdlconfig.json` and `copyconfig.json`.
### Available Tasks
| Command | Description |
|---------|-------------|
| `gulp min:scss` | Compile and minify all SCSS bundles |
| `gulp min:js` | Concatenate and minify all JS bundles |
| `gulp min:html` | Minify HTML bundles |
| `gulp min` | Run `min:js`, `min:scss`, and `min:html` in parallel |
| `gulp copy` | Run file copy tasks from `copyconfig.json` |
| `gulp all` | Run `min` and `copy` in parallel (full build) |
| `gulp clean` | Delete all bundle output files |
| `gulp watch` | Watch source files and rebuild on change |
Run from the `Fuchs/` directory:
```powershell
cd Fuchs
npx gulp all
```
### How SCSS Compilation Works
- All `.scss` files listed in a bundle's `inputFiles` are concatenated into a single stream, then compiled with `node-sass` via `gulp-sass`.
- The output is then minified with `gulp-cssmin` and written to `web/<bundle>.min.css`.
- `.less` files in the same bundle are compiled separately and merged with the SCSS output before minification.
### How JS Bundling Works
- All JS files listed in `inputFiles` are concatenated with `gulp-concat`.
- Files listed in `inputFiles_tominify` are individually minified with `gulp-terser` before concatenation.
- When `minify.enabled` is `true` (default), the full bundle is also minified via `gulp-terser`.
## npm Dependencies
Runtime packages available via npm (do not copy these into source manually):
| Package | Usage |
|---------|-------|
| `jquery` | DOM/AJAX |
| `js-cookie` | Cookie access |
| `fg-loadcss` | Async CSS loading (`loadCSS.js`, `onloadCSS_array.js`) |
| `tinymce` | Rich text editor |
These are copied to `web/` or `wwwroot/lib/` via `copyconfig.json` entries and the `gulp copy` task.
| Package | Destination (gulp copy) | Served at |
|---------|------------------------|-----------|
| `jquery` + `js-cookie` | concatenated into `Fuchs/web/tools.js` | `~/web/tools.js` |
| `tinymce` | `wwwroot/lib/tinymce/` | `~/lib/tinymce/tinymce.min.js` |
## Layout Asset Wiring (`Views/Shared/_Layout.cshtml`)
Asset loading follows a strict order and is split by authentication state.
### Always loaded (before auth check)
```razor
<script src="~/web/tools.js" asp-append-version="true"></script>
```
`tools.js` contains jQuery and js-cookie. It must be first so all subsequent scripts can use `$` and `Cookies`.
### Auth-conditional bundles
```razor
@if (isAuth)
{
<script src="~/lib/tinymce/tinymce.min.js"></script>
<link rel="stylesheet" href="~/web/fis.min.css" asp-append-version="true" />
<script src="~/web/fis.min.js" asp-append-version="true"></script>
}
else
{
<link rel="stylesheet" href="~/web/fisb.min.css" asp-append-version="true" />
<script src="~/web/fisb.min.js" asp-append-version="true"></script>
}
```
- **Authenticated** (`fis.*`): full intranet styles + scripts + TinyMCE rich-text editor.
- **Unauthenticated** (`fisb.*`): login-page-only styles + scripts. TinyMCE is **not** loaded.
- TinyMCE is always loaded from `~/lib/tinymce/tinymce.min.js` (copied by `gulp copy` from `node_modules/tinymce`). Never use `~/Scripts/tinymce/` — that path does not exist.
### `$ocms.auth` injection (always)
A `<script>` block is always rendered (authenticated or not) that writes the `$ocms.auth` object with the current user's id, email, and authorization level. This lets client-side code read auth state without additional requests.
### Per-page module bundles (`@section CustomHeader`)
Module bundles (`fis.inv`, `fis.req`, `fis.rep`, `fis.bam`) are **not** loaded globally. Each view that needs them renders a `@section CustomHeader` block:
```razor
@section CustomHeader {
<link rel="stylesheet" href="~/web/fis.inv.min.css" asp-append-version="true" />
<script src="~/web/fis.inv.de.js" asp-append-version="true"></script>
}
```
`_Layout.cshtml` renders `@RenderSection("CustomHeader", required: false)` inside `<head>`, so only the relevant module assets are fetched for each page.
@@ -0,0 +1,75 @@
# JavaScript Instructions
## General Rules
- **Never edit files inside `wwwroot/` directly.** All JS output files are generated by the Gulp build pipeline.
- After adding or modifying JS source files, ensure they are listed in `Fuchs/bdlconfig.json` under the correct bundle and context.
## Source File Locations
| Type | Path |
|------|------|
| Core intranet JS | `Fuchs/js/intranet/` |
| Feature module JS | `Fuchs/js/intranet/modules/<module>/` |
## Bundle Contexts
Each bundle in `bdlconfig.json` has a `context` field that determines which page(s) load it:
| Context | Loaded on |
|---------|-----------|
| `intranet` | All intranet pages |
| `intranet:inv` | Invoices module only |
| `intranet:req` | Requests module only |
| `intranet:rep` | Reports module only |
| `intranet:bam` | BAM module only |
## Adding a JS File to a Bundle
Locate the correct bundle entry in `Fuchs/bdlconfig.json` by matching the `context` and `outputFileName`, then add the source path to `inputFiles`:
```json
{
"context": "intranet:inv",
"outputFileName": "web/fis.inv.de.js",
"inputFiles": [
"js/intranet/modules/invoices/invoices.js",
"js/intranet/modules/invoices/your-new-file.js"
],
"minify": { "enabled": true }
}
```
Files listed in `inputFiles_tominify` are minified individually before concatenation (use for third-party scripts that should be minified separately).
## Adding a New Module
1. Create `Fuchs/js/intranet/modules/<module>/<module>.js`.
2. If the module needs styles, create `Fuchs/js/intranet/modules/<module>/<module>.scss`.
3. Add both files to the appropriate bundle entries in `bdlconfig.json`.
4. Run `npx gulp all` from the `Fuchs/` directory to rebuild.
## npm Packages
Use packages already declared in `Fuchs/package.json` where possible. Available runtime packages:
| Package | Global / Usage |
|---------|----------------|
| `jquery` | DOM, AJAX |
| `js-cookie` | Cookie read/write |
| `fg-loadcss` | Async CSS loading |
| `tinymce` | Rich text editor |
Do not import npm packages directly in source files — they are copied to `web/` by `gulp copy` and referenced via bundle entries in `bdlconfig.json`.
## Build Commands
Run from the `Fuchs/` directory:
```powershell
cd Fuchs
npx gulp min:js # rebuild JS bundles only
npx gulp all # full rebuild (JS + CSS + copy)
```
@@ -0,0 +1,63 @@
---
applyTo: "Fuchs/**,Fuchs_DataService/**,MFR_RESTClient/**,MT940Parser/**"
---
# Logging Instructions
## Overview
All logging in this solution uses `Microsoft.Extensions.Logging.ILogger<T>`.
**Never use** `System.Diagnostics.Debug.Print`, `Debug.WriteLine`, `Console.WriteLine`, or `OCMS.ocms_debug.debug_log` in business or library code.
## Providers
### Fuchs_DataService — `FdsLoggerProvider`
- Location: `Fuchs_DataService/Logging/FdsLoggerProvider.cs`
- Always active: **Debug output** (`System.Diagnostics.Debug.WriteLine`) + **file** (`tmp/DebugLog.txt`, `tmp/ErrorLog.txt`)
- Database logging: prepared via `WriteToDatabase()` but **disabled by default**. Activate with `FdsLoggerProvider.DatabaseLoggingEnabled = true`.
- Registered in `FdsMain.cs` via `LoggerFactory.Create(b => b.SetMinimumLevel(LogLevel.Debug).AddFdsLogging())`.
### Fuchs — `FuchsLoggerProvider`
- Location: `Fuchs/Logging/FuchsLoggerProvider.cs`
- Always active: **Debug output** + **file** (`logs/AppLog.txt`, `logs/ErrorLog.txt`)
- Database logging: prepared but **disabled by default**. Activate with `FuchsLoggerProvider.DatabaseLoggingEnabled = true`.
- Registered in `Program.cs` via `builder.Logging.SetMinimumLevel(LogLevel.Debug).AddFuchsLogging()`.
### MFR_RESTClient — Library (no provider)
- No logging provider — purely a library.
- `MFRClient` accepts `ILogger<MFRClient>?` as an optional constructor parameter.
- Defaults to `NullLogger<MFRClient>.Instance` when no logger is provided.
- Callers supply a logger from the host's `ILoggerFactory`.
### MT940Parser — Library (no provider)
- Location: `../../WebProjectComponents/MT940Parser/` (shared external component)
- No logging provider — purely a library.
- **Public entry point only**: `Parser` accepts `ILogger<Parser>?` as an optional constructor parameter and defaults to `NullLogger<Parser>.Instance`.
- **Internal sub-parsers** (`StatementParser`, `BalanceParser`, `StatementLineParser`, `AdditionalInfoParser`) are pure algorithmic classes. They **do not hold logger fields**. Parse errors are thrown as exceptions (`InvalidDataException`, `FormatException`) and caught by `Parser.Parse()`, which logs them at `LogWarning`.
- Callers supply a logger when constructing `Parser`; the sub-parsers never need one directly.
## Rules
- **DI classes** (singletons, scoped): receive `ILogger<T>` via constructor injection.
- **Library classes** (`MFRClient`, `Parser`): accept `ILogger<T>?` as an optional constructor parameter, default to `NullLogger<T>.Instance`.
- **Internal library helpers** (`StatementParser`, `BalanceParser`, `StatementLineParser`, `AdditionalInfoParser`): pure algorithmic, no logger field. Throw exceptions; the public entry-point catches and logs.
- **Static helpers** (`FdsShared`, `Banking`): accept `ILogger?` as an optional method parameter, or delegate to `FdsDebug.DebugLog` for infrastructure-level errors.
- `FdsDebug` remains the **fallback for static infrastructure code only** (stream helpers, etc.). Do not use it in new class-level code.
- Use structured logging parameters: `_logger.LogError(ex, "Message {Param}", value)` — never string interpolation in the message template.
## Log Levels
| Level | Use for |
|-------|---------|
| `LogDebug` | Timing, row counts, progress traces |
| `LogInformation` | Service start/stop, significant state changes |
| `LogWarning` | Recoverable issues, skipped items |
| `LogError` | Caught exceptions, data sync failures |
| `LogCritical` | Unrecoverable failures that stop a job |
## Enabling Database Logging
```csharp
// Fuchs_DataService
FdsLoggerProvider.DatabaseLoggingEnabled = true;
// Fuchs web
FuchsLoggerProvider.DatabaseLoggingEnabled = true;
```
Uncomment the body of `WriteToDatabase()` in the respective provider and adjust the stored procedure name.
@@ -0,0 +1,53 @@
---
applyTo: "Fuchs_DataService/**"
---
# PeriodicHostedService Instructions
## Overview
`PeriodicHostedService` (`Fuchs_DataService/PeriodicHostedService.cs`) is a `BackgroundService` that runs multiple independent jobs, each on its own `PeriodicTimer`. It is started and stopped by the Topshelf `FdsService` (see `topshelf.instructions.md`).
## Job Definition
Each job is a `PeriodicJobDefinition` record:
```csharp
public sealed record PeriodicJobDefinition(
string Name,
TimeSpan Interval,
Func<CancellationToken, Task> Execute);
```
## Registering Jobs
Jobs are created in `FdsService` constructor and passed to `PeriodicHostedService`:
```csharp
var interval = TimeSpan.FromMinutes(FdsConfig.ExecutionFrequency_Minutes);
var jobs = new[]
{
new PeriodicJobDefinition("MfrSync", interval, async ct =>
{
await mfr.UpdateIfNecessary_async(debug);
await mfr.UpdateRequested_async(debug);
await mfr.GetInvoiceFiles_async(debug);
}),
// Add more jobs with different schedules here:
// new PeriodicJobDefinition("HourlyJob", TimeSpan.FromHours(1), async ct => { ... })
};
```
## Schedules
- Each job runs on its **own independent `PeriodicTimer`** — schedules do not interfere.
- Frequencies are configured in `appsettings.json` under the `Fds` section (e.g. `Fds:ExecutionFrequency_Minutes`).
- Add new config keys for additional job intervals as needed.
## Error Handling
- Each job's `Execute` delegate is wrapped in a `try/catch` inside `RunJobAsync`.
- `OperationCanceledException` propagates normally (signals shutdown).
- All other exceptions are caught, logged via `ILogger`, and the job continues on its next tick.
## Cancellation
- `PeriodicHostedService` respects the `CancellationToken` passed to `StartAsync`.
- `FdsService.Stop()` cancels the token, then awaits `StopAsync(CancellationToken.None)`.
## Adding a New Job
1. Optionally add a new frequency key to `appsettings.json` and read it via `FdsConfig`.
2. Add a new `PeriodicJobDefinition` to the `jobs` array in `FdsService`.
3. No changes to `PeriodicHostedService` itself are needed.
@@ -0,0 +1,52 @@
---
applyTo: "Fuchs_DataService/**"
---
# Topshelf Instructions
## Overview
`Fuchs_DataService` uses **Topshelf 4.x** as the Windows Service host. The hosted jobs run inside a `PeriodicHostedService` (see `periodic_service.instructions.md`), which is managed by the Topshelf `FdsService` class.
## Structure
```
FdsMainModule.Main()
└── HostFactory.Run()
└── FdsService : ServiceControl
├── Start() → _hostedService.StartAsync(_cts.Token)
└── Stop() → _cts.Cancel() + _hostedService.StopAsync()
```
## FdsService Rules
- `FdsService` owns a `CancellationTokenSource _cts` for cooperative cancellation.
- `FdsService` builds the `ILoggerFactory` (via `LoggerFactory.Create(b => b.AddFdsLogging())`) and passes it to all job objects.
- `FdsService` constructs `FdsMfr` directly: `new FdsMfr(loggerFactory.CreateLogger<FdsMfr>(), loggerFactory)`.
- `FdsService` constructs `PeriodicHostedService` with jobs and passes `loggerFactory.CreateLogger<PeriodicHostedService>()`.
- Never use the generic `Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder` pattern here — Topshelf manages the host lifetime.
## Topshelf Configuration
```csharp
HostFactory.Run(x =>
{
x.Service<FdsService>(s =>
{
s.ConstructUsing(name => new FdsService());
s.WhenStarted((tc, host) => tc.Start(host));
s.WhenStopped((tc, host) => tc.Stop(host));
s.WhenPaused((tc, host) => tc.Stop(host));
s.WhenContinued((tc, host) => tc.Start(host));
});
x.EnablePauseAndContinue();
x.StartAutomatically();
x.RunAsLocalSystem();
x.SetDescription("MFR Data Sync");
x.SetDisplayName("MFR Data Sync");
x.SetServiceName("MFR Data Sync");
});
```
## Dev-Machine Shortcut
On machines named `digital-pc` or `digital-dpc` the service runs in-process (console) instead of installing as a Windows Service.
## Adding a New Job
Add a new `PeriodicJobDefinition` in `FdsService` constructor — see `periodic_service.instructions.md`.