Codebasis als Dateien ins Arbeitsrepo statt als Gitlink

QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
This commit is contained in:
Christoph Schwörer
2026-08-26 07:43:51 +02:00
parent 18edae75b6
commit f045b99a25
24664 changed files with 5846716 additions and 1 deletions
@@ -0,0 +1,65 @@
# **How to add a new right**
Creating a new right is quite simple:
First you should open `UserRightsConst.cs`, since it'll give you the infos you need. You can also open the rightsmodule in c-entron.NET, select a right, then click "I3D kopieren" in the ribbon to find the `I3D` of a specific right.
You are creating a [script](How-to-create-scripts.md) that adds a new entry to the `Sichrech` table:
- the `I3D` will later become the value of the right. The next `I3D` is on top of `UserRightsConst.cs`
- `Nummer` is not important for centron, it can be 0.
- `FomName` is important for Delphi, but not for Centron, so it can remain null.
- the same for `FomCont`.
- With `Text` the name of the right is entered as it is displayed in the rights management.
- In `OwnerRecht` the I3D of the right comes in, which is required for your new right.
- If, on the other hand, another right (or rights) requires your new right(s), `NumChildren` will show the number of them.
- at `Beschreibung`, as the name suggests, a short description comes in, which makes the right.
###ScriptHelpers.AddRightIfNotExists(..)
__It is highly advised to use `ScriptHelpers.AddRightIfNotExists(..)` and not write your own script.__
The call should look something like this.
If you need more examples check `ScriptMethod11250.cs`.
```c#
yield return ScriptHelpers.AddRightIfNotExists(
UserRightsConst.Sales.Customer.Helpdesk.SHOW_HOURLYSURCHARGERATES,
UserRightsConst.Sales.Customer.Helpdesk.ID,
"Aufschläge Stundensätze anzeigen",
"Dieses Recht gibt an, ob ein Nutzer Aufschläge Stundensätze anzeigen kann.");
```
###Raw sql script
If you have to, your script could look like this:
```xml
<Script Name="AddNewMitarbeiterauslastungRight" ScriptNumber="10870">
<UpdateQuery>
<![CDATA[
IF NOT EXISTS (SELECT * FROM Sichrech WHERE I3D = 20800042) BEGIN
INSERT INTO Sichrech
(I3D,Nummer, Text, OwnerRecht, NumChildren, Beschreibung)
VALUES
(20800042, 0, 'Mitarbeiterauslastung - nur eigene Filiale', 10930, 0, 'Die Gruppen denen dieses Recht zugewiesen wurde, können nur die eigene Filiale in der Mitarbeiterauslastung sehen.');
UPDATE Sichrech
SET NumChildren = NumChildren + 1
WHERE I3D = 10930;
END;
]]>
</UpdateQuery>
</Script>
```
At the end you write a constant for your right in `UserRightsConst` et voilà you can use your right.
@@ -0,0 +1,45 @@
# How to check user rights
Rights and groups can be managed in the `Rechteverwaltung` module.
The IDs for each user right can be found in `UserRightsConst.cs`. Always use these constants instead of the id itself.
## In viewmodels (Centron.WPF.UI)
In viewmodels it's quite easy to check for user rights:
``` csharp
CentronCache.Instance.CurrentUserAppRights.Any(f => f.I3D == UserRightsConst.T);
```
## For modules
Checking if an user has access to a module in `ModuleRegistration.cs` can be done via the first overload on:
``` csharp
ModuleRegistrationItem.For<TController>(() => Helper.HasRights(UserRightsConst.T))
```
## In BL (Centron.BL)
To check out user rights in BLs simply call `AppRightsBL.CheckRightsFromUser(currentUserI3D, your rightids)` then check if the result contains the necessary right.
``` csharp
var checkRightsResult = new AppRightsBL(Session).CheckRightsFromUser(currentUserI3D, new List<int>
{
UserRightsConst.Sales.Customer.CustomerCommon.CREATE_CUSTOMER,
UserRightsConst.Sales.Customer.CustomerCommon.EDIT_CUSTOMER,
UserRightsConst.Sales.Customer.CustomerCommon.DELETE_CUSTOMER,
UserRightsConst.Sales.Customer.CustomerCommon.SEARCH_CUSTOMER,
UserRightsConst.Sales.Customer.CustomerCommon.UNLOCK_CUSTOMER
});
```
Then check via:
``` csharp
if (checkRightsResult.Contains(UserRightsConst.Sales.Customer.CustomerCommon.CREATE_CUSTOMER) == false)
{
return Result.AsError("Fehlende Rechte um Accounts zu erstellen");
}
```
(from `AccountBL.cs`)
@@ -0,0 +1,134 @@
# How to create Mail Template
Since we are migrating all mail templates to new structure, there are few things that changed.
## New Mail Template structure
From now on, all mail templates are supposed to be in the `MailVorlagen` table, where we are saving every mail template with its own classification.
In order to accomplish that, in the class `MailTemplateReference`, you can see what defines an actual mail template (`ObjectKind`, `ObjectI3D`, `SubObjectKind`, `TemplatePrio`).
Why are these properties important? – Every type of mail-template can be uniquely identified by the combination of these values. For example Offer-Mails have ObjectKind=Offer, and all other values are NULL. If there is a MailTemplate in the database with ObjectKind=Offer, and all other values NULL, we know it has to be of type “Offer-Mail”.
ObjectI3D can be used to attach this mail-template to some other database row (for example a mail-template with ObjectKind=HelpdeskType, and ObjectI3D is that specific Helpdesk-Type it is used for).
SubObjectKind can be used as another unique identifier, if you want to group similar mail-templates together. For example, ObjectKind=Escalation has multiple mail-templates for different cases. They don’t depend on other database rows tho, so we can’t use the ObjectI3D to distinguish them. That’s where we use the SubObjectKind then.
How do we do that? – We have created a class named `MailTemplateType` which will contain the mail template skeleton definition. Here is an example how it can be added.
```csharp
public static class AccountActivities
{
public static MailTemplateReference CrmActivity => MailTemplateReference.Create(
CentronObjectKindNumeric.AccountActivity, (int)AccountActivityKind.CRM, null, defaultSubject: string.Empty, defaultBody: string.Empty);
public static MailTemplateReference Note => MailTemplateReference.Create(
CentronObjectKindNumeric.AccountActivity, (int)AccountActivityKind.Note, null, defaultSubject: string.Empty, defaultBody: string.Empty);
public static MailTemplateReference PhoneNote => MailTemplateReference.Create(
CentronObjectKindNumeric.AccountActivity, (int)AccountActivityKind.PhoneNote, null, defaultSubject: string.Empty, defaultBody: string.Empty);
}
```
Please make sure you also add default text for Subject and Body. This can be done in the same file, in class named MailTemplateDefaultText. The Body text can be added as plain text, because we are going to make sure that it’s converted to RTF in the central method.
```csharp
public class MailTemplateDefaultText
{
public static string WebOfferDefaultSubject = @"Default subject";
public static string WebOfferDefaultBody = @"Default body";
}
```
Now after this step is done, you should make sure that Mail Templates Module uses the correct Mail Template Type for the corresponding Mail Template.
This step is quite easy, but if there are questions just ask me and I will gladly help.
In the `MailTemplatsViewModel` that is in Controls, in the method `CreateMailTemplateTreeViewItemChildren` where all children are created, go to the method that is creating mail templates with the corresponding `CentronObjectKindNumeric` value.
There you must replace the parameters that are sent to `CreateMailTemplate` method with the `MailTemplateType` values for the needed parameters.
> Tip: For example, you can check how it’s done in the `CreateAccountActivitiesMailTemplates` method.
> Note: This approach will be much more simplified after we are done with the migration part, but for now we stick with it.
## Using new methods
Now that we have covered that part, it’s time to replace the way we get those mail templates and replace each call with the new method that is returning correct new mail template.
This step is also easy and very simple, but it is a bit different if we are using the logic in c-entron.NET or one of our other applications.
In `MailTemplateBL.cs` there are 2 methods. One is for the internal (in the Web-Service) usage
```csharp
public Result<MailTemplate> GetMailTemplate(MailTemplateReference mailTemplateReference, int? branchI3D)
```
and the other one is for external (SBO, Outlook AddIn, …) usage.
```csharp
public Result<MailTemplate> GetMailTemplate(CentronObjectKindNumeric objectKind, int? subObjectKind, int? objectId, int? branchI3D)
```
You probably ask yourself what the difference is and why do we have two of them. - Well, there’s no difference. The method for internal usage is just calling the method
for external usage, just because of the method parameters. Like I have already mentioned, we have this `MailTemplateType` class which is accessible only in c-entron.NET, so external
apps must pass each of the parameters, that are defining each mail template skeleton.
## Migration/Creation of the Mail Template
Now one more thing that we need to cover is migration of the existing mail templates that are saved elsewhere and creation of new mail templates.
No matter if you are creating a new template, or migrating an existing one, the process is the same.
In order to make this step easier and to avoid mistakes, we have created a new method in `ScriptHelper`. You just have to make sure to pass the scripts for getting the
correct subject and body and the other of the parameters, the rest will be handeled by the method itself.
This method will also make sure that the body is inserted as RTF.
```csharp
internal static void InsertMailTemplate(DAOSession session, string subjectScript, string bodyScript, CentronObjectKindNumeric objectKind, int? subObjectKind, int? objectI3D, int? templatePrio = null)
```
Here is one example of how the migration can be done.
```csharp
public override Result ExecuteScript(DAOSession session)
{
ScriptHelpers.InsertMailTemplate(session,
GenerateAppSettingsSQL(AppSettingsConst.HelpdeskForwardingInternalEmailSubject),
GenerateAppSettingsSQL(AppSettingsConst.HelpdeskForwardingInternalEmailBody),
CentronObjectKindNumeric.HelpdeskClass,
1,
null);
ScriptHelpers.InsertMailTemplate(session,
GenerateAppSettingsSQL(AppSettingsConst.HelpdeskForwardingExternalEmailSubject),
GenerateAppSettingsSQL(AppSettingsConst.HelpdeskForwardingExternalEmailBody),
CentronObjectKindNumeric.HelpdeskClass,
2,
null);
}
private string GenerateAppSettingsSQL(AppSettingsConst settingsConst)
{
return $@"SELECT WertMemo FROM Stammdat WHERE I3D = {(int)settingsConst}";
}
private string GenerateTextModuleSQL(TextModuleType type)
{
return $@"SELECT Text FROM Textbaustein WHERE Art = {(int)type} AND SichbenuI3D = 0 AND KundenI3D = 0";
}
```
> :exclamation: Please, make sure that this step is required only if the mail template is not in the MailVorlagen table.
> Tip: In the `MailTemplateBL.cs` there is still a method `GetAllDelphiMailTemplates`, which can help you to get the needed parameters info faster as well.
@@ -0,0 +1,339 @@
# How to EndToEnd test
## General
EndToEnd tests are supposed to test everything from the `WebServiceBL` layer and downwards (see the diagram [here](GeneralStructure.md)).
In other words, with EndToEnd tests we are testing the `WebServiceBL`, the `BL` and the `database`.
The project can be found at `/tests/Centron.Tests.EndToEnd` and all the tests are supposed to be in the `Tests` namespace.
For example, there are tests for the c-entron.NET `Receipt Area` are at `/tests/Centron.Tests.EndToEnd/Tests/Receipts/ReceiptTest.cs`.
The tests are executed as part of every Pull-Request.
If one of the tests fails, the PR cannot be merged.
> Caution!
> Some tests are a bit flaky sometimes. That means they fail even tho they should not.
> In these cases you can just let the PR get built again, and the tests should run successful.
We should try to make all tests NOT flaky.
## Different kinds of tests
We can roughly categorize all tests into 2 categories:
1. [General purpose tests](#general-purpose-tests)
2. [Specialized tests](#specialized-tests)
They are only different in what they do, and it's not a hard categorization either.
You still create both kinds of tests the same way, and they will look the same.
They both inherit from `EndToEndTest.cs` and only differ in **what** and **how much** they do.
You can also inherit from `CentronTest.cs`, if you don't need database access for your test.
And also, there is a `PerformanceTest.cs`, that measures the performance of some code, and fails if the performance-goal is not met.
We can use this kind of test to make sure, that we don't have too harsh performance-regressions when we change some things.
For example, we have a test that makes sure big receipts can be saved in less than 5 seconds.
If we add something to the SaveReceipt method, or change some other things around, and the receipt suddenly takes 8 seconds to save, then we made a mistake and have to correct it.
### General purpose tests
These tests are general purpose, doing every day things that the user does too.
For example `creating a new customer`, `adding addresses` and `contact-persons` to it, `renaming contact-persons`, `removing addresses` and so on.
The general purpose tests are supposed to cover **really much code**.
We want them to fail if we break a workflow that the user is doing every day.
### Specialized tests
The specialized tests are way smaller than the general purpose tests and are usually created as a response to a ticket that was fixed, or a new feature that was created.
For example `make sure that a setting works as expected`, or `make sure that a bug was fixed`.
These specialized tests are supposed to cover **really small amounts of code**, just enough to make sure the specific thing works as expected.
We want them to fail if we break a specialized setting that some users might be using.
## How does it work?
1. Before each test is executed a database backup (`/Infrastructure/DatabaseBackup.zip`) is restored
2. The `DAOFactory` is configured to use that new database
3. Any new scripts (`IScriptMethod`) are executed
4. The `Test.Execute` method is executed
5. The database backup is removed
This workflow means that every test always has the same starting configuration.
This fact is extremely useful because it allows us to execute these tests today, or in a month, or in a year, and we will still get the same result in the end.
If you create new entries in a table for example, they will get the same I3D every time.
Actually, that's the most important thing about these tests, and why they even work at all: **The tests are repeatable!**
If they were not repeatable, they would fail without any real breaking changes, and then they are not useful to find the breaking changes.
## How to run the tests on your machine
Because we restore the database backup before each test, and the backup is on your local machine, the **SQL Server also needs to be on your local machine**.
So if I work on my own machine `CS-ULM-DANHAE`, and I want to run some tests, I absolutely need a SQL Server installed on my own machine (`CS-ULM-DANHAE\SQL2017` in my case).
**You cannot use a SQL Server on a different machine.**
> You need at least an SQL Server 2017.
To **configure the connection to the SQL Server** you have to **change some small things** in the `/Infrastructure/Database.cs` class.
You will see three properties at the top: `Server`, `Username` and `Password`. They all look similar to this:
``` csharp
public static string Server
{
get
{
var server = GetEnvironmentVariable("CENTRON_TESTS_DATABASE_SERVER");
if (string.IsNullOrWhiteSpace(server) == false)
return server;
throw new Exception("You have to configure the SQL server for EndToEndTests");
return "YOUR-MACHINE\\SQL2017"; //Enter your local SQL server here
}
}
```
You can see, it at first tries to get the configured value from an **environment variable** - that is exactly how the code gets the connection when it's run by the **build server**.
The build-server will set the environment variables to a SQL Server installed on the build-server itself (so it's locally to where the tests will be executed).
But if the environment variable is not set, it will **throw an Exception**.
If you want to configure the EndToEnd tests to use your own local SQL Server, you just have to **comment out the exception**, and **change the return value of the property**.
```csharp
public static string Server
{
get
{
var server = GetEnvironmentVariable("CENTRON_TESTS_DATABASE_SERVER");
if (string.IsNullOrWhiteSpace(server) == false)
return server;
//throw new Exception("You have to configure the SQL server for EndToEndTests");
return "CS-ULM-DANHAE\\SQL2017"; //Enter your local SQL server here
}
}
```
If you do the same thing for the `Username` and `Password` properties, you are done and can execute the tests locally on your machine.
> :exclamation: Make sure to not commit these changes! :exclamation:
## How data is validated
When executing the test, you will probably create some data or some objects.
For example, you will create a new customer, a new ticket, or a new receipt.
The test has to validate, that these objects look exactly how expected.
This is done with a class called `CentronVerifier`. It has a `Verify` method that takes the data you want to verify, and a name for it.
```csharp
this.Verifier.Verify("CreatedOffer", offer);
```
This will create a file called `CreatedOffer.actual.txt` that is basically the `offer` json serialized.
It will then compare this file, to a `CreatedOffer.expected.txt` file.
If they **differ**, **something has changed**, and the **test fails**.
If they are **the same**, **everything still works** as expected, and the **test continues**.
The first time you execute a test, of course there will be no `*.expected.txt` file, and thus the test will fail.
You're supposed to go ahead, and rename the `*.actual.txt` one to `*.expected.txt`.
So when you execute the test again, a new `*.actual.txt` file will be created, and compared to the `*.expected.txt`.
This also means, the `*.expected.txt` should be added to source-control, but the `*.actual.txt` files should **NOT** be added to source-control.
You can also verify data from a table by using the `VerifyTable` method.
```csharp
this.Verifier.VerifyTable("TheCustomer", "dbo.Kunden", where: "I3D = 12345");
```
With that you have to provide a name again, the table that you want to verify, and a optional where filter.
A `SELECT * FROM [Table] WHERE [Filter]` statement will be executed, and the result will be used as data for the `Verify` method.
So everything with the `*.expected.txt` and `*.actual.txt` files works exactly the same as with the `Verify` method.
## Tips and other useful things
### Run any sql statement in your test
The base class for all tests `EndToEndTest.cs` provides a useful method `Sql` to run any sql statement in your test.
You can use it for example if you easily wanna change a setting in the database before running your test. Or you want to change a configuration property on a customer or something else.
```csharp
this.Sql("UPDATE Stammdat SET Wert = 1 WHERE I3D = 1234");
```
### Debugging a test
You can also very easily debug a test by setting breakpoints in your code, and then `Right-Click` -> `Debug` the test in the `Test Explorer`.
![Debug a test](./.attachments/How-to-end-to-end-test/DebugTest.png)
### Diff view for *.expected.txt and *.actual.txt
If a test fails because something has changed between a `*.expected.txt` and a `*.actual.txt` file, you can see a diff between those two files.
This allows you to more easily spot the differences.
It is pretty easy to do. If a test fails because of a `this.Verifier.Verify` call, you just have to [debug the test](#debugging-a-test).
Now if the `Verify` call fails the test will fail as usual, but at the same time a Diff-View will open in Visual Studio with both the `*.expected.txt` and the `*.actual.txt` in it.
You can then more easily decide whether the changes are fine, or whether you broke something.
## How to create a test
Lets say we want to write a [general purpose test](#general-purpose-tests) for some `Helpdesk` stuff - creating a new ticket for a customer, setting some values and saving it.
> The real general purpose HelpdeskTest is of course supposed to cover even more of the helpdesk code.
> For example forwarding it, adding documents and history entries, and closing the ticket.
### Step 1
Make sure to [configure the database connection](#How-to-run-the-tests-on-your-machine) so you can run them on your computer.
### Step 2
Create a new namespace/folder under the `/Tests` namespace: `WikiHelpdesk`.
### Step 3
In that namespace, create a new class `WikiHelpdeskTests.cs`.
### Step 4
Make that class inherit from `EndToEndTest`.
### Step 5
Implement the constructor for the class, and override the `Execute` method.
Your class should look like this now:
```csharp
using Centron.Tests.EndToEnd.Infrastructure;
using Xunit.Abstractions;
namespace Centron.Tests.EndToEnd.Tests.WikiHelpdesks
{
public class WikiHelpdeskTests : EndToEndTest
{
public WikiHelpdeskTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
public override void Execute()
{
}
}
}
```
> This is the basic setup you always have to do for a test.
> Everything that follows now is actually implementing the test and [validating data](#how-data-is-validated).
### Step 6
Create a new method `CreateTicket`
```csharp
private HelpdeskDTO CreateTicket()
{
using (var session = new BLSession())
{
//Get a LoggedInUser for the current user using the base class method
var loggedInUser = this.GetLoggedInUser(11); // Default is 11 if no parameter is provided
//Create a new ticket
var ticket = session.GetBL<HelpdeskWebServiceBL>().GetNewTicketFromCustomer(loggedInUser.User, 10022);
//Set some values
ticket.ShortDescription = "Mein super wichtiges Ticket!";
ticket.Description = "Mit einer nicht sehr ausführlichen Beschreibung.";
//Save the helpdesk
var saveHelpdeskResult = session.GetBL<HelpdeskWebServiceBL>().SaveHelpdesk(ticket, loggedInUser);
//Verify the saveHelpdeskResult
this.Verifier.Verify("SaveHelpdeskResult", saveHelpdeskResult);
//Load the complete helpdesk object
var savedTicket = session.GetBL<HelpdeskWebServiceBL>().GetHelpdeskByI3D(saveHelpdeskResult.Data, loggedInUser);
//Verify the savedTicket
this.Verifier.Verify("SavedTicket", savedTicket);
return savedTicket.Data;
}
}
```
I wanna especially highlight the two lines that use the `this.Verifier`:
```csharp
this.Verifier.Verify("SaveHelpdeskResult", saveHelpdeskResult);
```
and
```csharp
this.Verifier.Verify("SavedTicket", savedTicket);
```
These calls make sure that the [data is actually validated](#how-data-is-validated).
### Step 7
Call the new `CreateTicket` method in the `Execute` method.
```csharp
public override void Execute()
{
var ticket = this.CreateTicket();
}
```
You created your first test! :tada:
### Step 8
As you know from the chapter on [how data is actually validated](#how-data-is-validated) the test will fail right now.
There is no `SaveHelpdeskResult.expected.txt` and no `SavedTicket.expected.txt`.
So lets run the test by opening the `Test Explorer` in Visual Studio and `Right-Click` -> `Run` the test.
![Run the test](./.attachments/How-to-end-to-end-test/RunTest.png)
It will take a little while, but after like 40 seconds you should get a failed test result.
![Failed test run](./.attachments/How-to-end-to-end-test/FailedTest.png)
### Step 9
If you activate `Show All Files` in the solution explorer for the `Centron.Tests.EndToEnd` project, you should see the `SaveHelpdeskResult.actual.txt` file.
![SaveHelpdeskResult.actual.txt file in solution explorer](./.attachments/How-to-end-to-end-test/SaveHelpdeskResult-actual-txt.png)
Go ahead and rename it to `SaveHelpdeskResult.expected.txt`.
It automatically gets picked up by the csproj file too.
![SaveHelpdeskResult.expected.txt file in solution explorer](./.attachments/How-to-end-to-end-test/SaveHelpdeskResult-expected-txt.png)
### Step 10
Run the test again!
...
As expected, it still fails because of the second `Verify` call.
But you now know what to do.
### Step 11
A new file `SavedTicket.actual.txt` was created.
Feel free to open it, and look at the data that is verified.
Every single line in that JSON document has to stay exactly the same.
Every property in that file is not allowed to change.
![SavedTicket.actual.txt file in solution explorer](./.attachments/How-to-end-to-end-test/SavedTicket-actual-txt.png)
Rename it to `SavedTicket.expected.txt`.
![SavedTicket.expected.txt file in solution explorer](./.attachments/How-to-end-to-end-test/SavedTicket-expected-txt.png)
### Step 12
Run the test once again!
...
IT WORKS! :tada:
![Successful test](./.attachments/How-to-end-to-end-test/SuccessfulTest.png)
The test is finished!
You can now add even more logic to the `Execute` method.
@@ -0,0 +1,15 @@
# Fixed Memory leaks
A collection of memory leaks we fixed and where they were.
# 1. DependencyPropertyDescriptor.FromProperty
Previously found in "ContentControlContentChangedBehavior".
Switching to PropertyChangeNotifier which just creates a normal Binding fixes the issue.
# 2. DispatcherTimer
Previously found in "TicketDetailView.xaml.cs"
A DispatcherTimer started with '.Start()' need a '.Stop()' call.
If you use '+=' u likewise need a '-='.
@@ -0,0 +1,183 @@
# Settings Management in c-entron.NET
This guide explains how application settings are managed in the c-entron.NET project, covering both legacy and current approaches, and providing best practices for working with settings.
## Overview
The c-entron.NET application uses two separate database tables for storing application settings:
1. **Stammdat** - Legacy settings table with historical settings
2. **ApplicationSettings** - Current table for new settings
This dual-table approach exists for historical reasons, and all new settings should be added to the `ApplicationSettings` table.
## Settings Tables
### Legacy: Stammdat Table
The Stammdat table contains many historical settings that are accessed through the `AppSettingsConst` enum.
- **Enum File**: `src/backend/Centron.BL/Administration/Settings/AppSettingsConst.cs`
- **Access**: Settings are accessed through the `AppSettingsBL.GetSettings(AppSettingsConst)` method
- **Updates**: Although we maintain these settings, no new settings should be added to this table
### Current: ApplicationSettings Table
The ApplicationSettings table is the current standard for all new application settings.
- **Enum File**: `src/backend/Centron.Interfaces/Administration/Settings/ApplicationSettingID.cs`
- **Next Free ID**: Tracked in a comment at line 15 of `ApplicationSettingID.cs`
- **Access**: Settings are accessed through the `AppSettingsBL.GetSettings(ApplicationSettingID)` method
- **Settings Descriptions**: Defined in `src/backend/Centron.Interfaces/Administration/Settings/ApplicationSettingDefinitions.cs`
## ID Management
When adding new settings, you must:
1. Check the next available ID from the comment in `ApplicationSettingID.cs`:
```csharp
// Next Centron Settings ID : 10370
// Current Riverbird Settings ID : 50035
```
2. Use the next ID in sequence (in this case, 10370)
3. Update the comment with the next available ID after adding your setting
The Riverbird setting IDs (starting with 50xxx) are not used by c-entron.NET.
## Setting Definitions
For each new setting in `ApplicationSettingID.cs`, you must add a corresponding description in the `ApplicationSettingDefinitions.cs` file:
```csharp
case ApplicationSettingID.YourNewSetting:
return "Description of what this setting does and how it's used.";
```
The description should clearly explain:
- The purpose of the setting
- What data it stores
- Any relevant format or validation constraints
## Accessing Settings
### Group Setting Classes
The client never accesses settings tables directly. Instead, we use "group setting classes" to manage related settings. These classes:
1. Load settings from the database
2. Provide a strongly-typed interface for accessing settings
3. Manage updating settings back to the database
### Example: Loading Settings
```csharp
// Example from ReceiptWebServiceBL.GetReceiptInvoiceSettings()
var appSettings = this._appSettingsBL.GetSettings
(
ApplicationSettingID.InvoiceArchiveActive,
ApplicationSettingID.IsZugferdInvoiceActive,
// additional settings...
);
var settings = new ReceiptInvoiceSettingsDTO
{
IsInvoiceArchiveActive = appSettings.GetBool(ApplicationSettingID.InvoiceArchiveActive, false),
IsZugferdInvoiceActive = appSettings.GetBool(ApplicationSettingID.IsZugferdInvoiceActive, false),
// map other settings...
};
return Result<ReceiptInvoiceSettingsDTO>.AsSuccess(settings);
```
### Example: Saving Settings
```csharp
// Example from ReceiptWebServiceBL.SaveReceiptInvoiceSettings()
var updateSettings = this._appSettingsBL.GetSettingsForUpdate
(
ApplicationSettingID.InvoiceArchiveActive,
ApplicationSettingID.IsZugferdInvoiceActive,
// additional settings...
);
// Update values
updateSettings.UpdateBool(ApplicationSettingID.InvoiceArchiveActive, settings.IsInvoiceArchiveActive.Value);
updateSettings.UpdateBool(ApplicationSettingID.IsZugferdInvoiceActive, settings.IsZugferdInvoiceActive.Value);
// update other settings...
// Save all changes
updateSettings.SaveSettings();
return Result<bool>.AsSuccess(true);
```
## API Integration
Settings are exposed through API methods, allowing client applications to retrieve and update settings.
### API Patterns
1. All setting API methods must use HTTP POST
2. Get methods return a DTO containing the settings
3. Save methods accept a DTO with the settings to update
### Example API Methods
```csharp
// In ICentronRestService.cs
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
Result<ReceiptInvoiceSettingsDTO> GetReceiptInvoiceSettings();
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
Result<bool> SaveReceiptInvoiceSettings(ReceiptInvoiceSettingsDTO settings);
```
## Best Practices
### Adding New Settings
1. Use the next available ID from the comment in `ApplicationSettingID.cs`
2. Add your setting to the `ApplicationSettingID` enum
3. Update the "Next Centron Settings ID" comment
4. Add a detailed description in `ApplicationSettingDefinitions.cs`
5. Create or update group setting classes to access your setting
### Setting Types
Application settings support multiple data types:
- **Boolean**: Use `GetBool()` / `UpdateBool()` methods
- **Integer**: Use `GetInt()` / `UpdateInt()` methods
- **String**: Use `GetString()` / `UpdateString()` methods
- **Large String**: Use `GetLargeString()` / `UpdateLargeString()` methods
- **Enum**: Use `GetEnum<T>()` / `UpdateEnum<T>()` methods
- **Decimal**: Use `GetDecimal()` / `UpdateDecimal()` methods
### Default Values
When retrieving settings, always provide a default value in case the setting doesn't exist:
```csharp
// Example with default value
bool isActive = appSettings.GetBool(ApplicationSettingID.SomeSetting, false);
```
## Common Scenarios
### Creating a New Group Settings Class
1. Define a DTO class to hold the settings
2. Create Get method that loads settings from AppSettingsBL
3. Create Save method that updates settings with AppSettingsBL
4. Add corresponding API methods
### Migrating Legacy Settings
When migrating settings from Stammdat to ApplicationSettings:
1. Add the new setting to ApplicationSettingID
2. Add its description to ApplicationSettingDefinitions
3. Update code to read from both sources during transition
4. Eventually remove the old setting access after migration
@@ -0,0 +1,15 @@
#A couple of Links useful for XRechnung
Offical Documentation PDF is embedded in the source code (ZUGFeRD-2.1.1 - Spezifikation_TA (1)).
Here the link for this document in the official Version archive: https://www.ferd-net.de/standards/zugferd-version-archive/zugferd-2.1.html
##Opensource Implementation
https://github.com/stephanstapel/ZUGFeRD-csharp
Nice place to check out some Stuff, has a couple of Demo files.
Would be nice to switch to this in the future instead of creating our own implementation.
##KOSIT (Koordinierungsstelle für IT-Standards)
https://github.com/itplr-kosit
Different helpful repositories for XRechnung.
For example a cli tool to verify the xml.