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,154 @@
# On DTOs and Entities
In our c-entron<span>.NET solution we use three different types of objects that hold actual data:
type | layer | usage
---|---|---
Entity | BL/WebServiceBL | interaction with the database via NHibernate
DTO | WebService/Logics |transfer from entity to viewmodel via webservice
ViewModels | ViewModel/UI|display data to the user and allow user to edit it
*Because ViewModels are pretty straight forward and quite boring we're just going to ignore them here.*
We're going to use the `Thingy` class for examples here again. For a real implementation check out MyDayWorkItem and associated classes.
## Entities
On the most basic level entities are just a row in a database table managed by our ORM [NHibernate](https://nhibernate.info/). If we have a `Thingy` there is a corresponding row in the `Thingies` table, where the **primary key equals** the **I3D**. Entities can never leave the BL-Layer as they cannot be used in the webservice and need a connection to NHibernate and the DB itself. CRUD-Operations are done via `Session.GetGenericDAO\<T>().SaveOrUpdate/Get/Delete` and can only be executed with a valid entity.`
As an entity is simply for holding data it should never include any logic, no overrides and not even a ctor. To avoid confusion all properties present in an entity should also be mapped to a column in the database.
An entitiy needs 2 classes:
The actual entity that holds all data and is used for all operations. It must inherit the `BaseEntity` abstract base class that adds the unique identifier (**I3D**) for us. The correct project for entities is `Centron.Entities` and then the `Entities` directory.
All properties must be declared **virtual** and have to have both a **setter** and a **getter**. Both things are required for the interaction with NHibernate.
``` csharp
public class Thingy : BaseEntity
{
public virtual string SomeProperty { get; set; }
public virtual int SomeOtherProperty { get; set; }
}
```
Aside from the actual entity we also need a mapping class from [Fluent NHibernate](https://github.com/FluentNHibernate/fluent-nhibernate). This class maps the properties to the database column and must inherit `ClassMap<T>`. Even tough NHibernate can figure a lot of the properties out itself, you should always set the table, the id and ALL properties. Mapped properties need to be described with `.Not`, `.Nullable()`, `.Lenght()` etc. as closely as possible. Check out ['Fluent NHibernate in a Nutshell'](https://github.com/FluentNHibernate/fluent-nhibernate/wiki/Getting-started) for more infos. The correct project for entities is `Centron.DAO` and then the `Mappings` directory.
``` csharp
public class ThingyMaps : ClassMap<Thingy>
{
public ThingyMaps
{
this.Table("Thingies");
this.Id(f => f.I3D);
this.Map(f => f.SomeProperty).Nullable();
this.Map(f => f.SomeOtherProperty).Not.Nullable();
}
}
```
## DTOs
>In the field of programming a data transfer object (DTO) is an object that carries data between processes. (https://en.wikipedia.org/wiki/Data_transfer_object)
For us DTOs do exactly that. They transfer the data from the entity on the bl-layer to the viewmodel in the UI over WebService or direct database connection. Our webservice exclusivly uses DTOs, so all other applications and the c-entron<span>.NET ILogics use them. The project for DTOs is `Centron.WebServices.Core` and then the wrongly named `Entities` directory.
Like `entities DTOs` should never hold any kind of logic, no overrides and no ctors. It can include properties not present in the entity but this should be only be done in very specific circumstances.
Constructing DTOs is very easy: just create a class `<yourclass>DTO` (`ThingyDTO` for us), inherit `BaseDTO` and copy all properties from the entity. Then add the `[DataContract]` attribute to your class and the `[DataMember]` attribute to each property.
There are certain things that need to be avoided.
Properties of type DateTime can be dangerous. The default value of DateTime in .NET is 01.01.0001 which cannot be parsed to json and *will* throw an exception. Especially if the DateTime property gets filled by the database, is used in multiple locations or by other applications. Using a nullable `DateTime?` is the easiest way around that.
List properties **must** use `List<T>`. Using `IList<T>` or other types of list (`IEnumerable<T>`, `ICollection<T>`) can lead to problems with serializing it into json. As this is not widely done in our DTOs please consider updating existing DTOs.
### KnownTypes
If your DTO inherits from a different class (NOT `BaseDTO`) you must add the type to `GetKnownTypes()` in `KnownTypes.cs`. Additionally each webservice method that uses that DTO also needs the `[ServiceKnownType(nameof(KnownTypes.GetKnownTypes), typeof(KnownTypes))]` attribute in `ICentronRestService`. Otherwise the webservice *will* throw an exception while parsing your DTO to json.
## Converting DTOs to Entities and vice versa
### DTO -> Entity
Converting from a DTO to an entity can be somewhat annoying and complicated.
**NEVER use the `ObjectMapper` for this.** *(Yes, I know, there are a lot of places that do)*
What we need to do is as follows:
1. check if the I3D of the `ThingyDTO` is 0.
\> yes: `new Thingy()`;
\> no: load the entity from the database (via the appopriate BL method)
2. take over all properties from the DTO to the entity by hand.
This is done to ensure that all entities that are already saved to the DB (I3D != 0) are unique and properly managed by NHibernate.
``` csharp
public Thingy ConvertThingyDTOToEntity(ThingyDTO dto)
{
Guard.NotNull(dto, nameof(dto));
Thingy entity;
if(dto.I3D != 0)
entity = new ThingyBL(this.Session).LoadThingy(dto.I3D);
else
entity = new Thingy();
entity.I3D = dto.I3D;
entity.SomeProperty = dto.SomeProperty;
[..]
return entity;
}
```
### Entity -> DTO
Converting an entity to a DTO is very simple as we can just use the `ObjectMapper.Map<TEntity, TDTO>()`.
To use the Objectmapper you should create a configuration file.
These files go under `Centron.Bl` -> `Webservice` -> `ObjectMapperConfiguration`.
If there is a fitting class already just add `this.CreateMap<Thingy, ThingyDTO>();`, else we need to create a new file.
``` csharp
public class ThingyConfiguration : Profile
{
protected ovveride void Configure()
{
this.CreateMap<Thingy, ThingyDTO>();
}
}
```
### Generating mapping and entities
The following SQL statements generate your mappings and entities for you.
**There could be errors inside, you need to manually verify them.**
``` SQL
SELECT 'Map(m => m.' + c.name + ').Column("' + c.name + '")' +
CASE WHEN ty.name = 'text' THEN '.Length(int.MaxValue)' ELSE '' END +
CASE WHEN ty.name = 'varchar' THEN '.Length(' + CONVERT(varchar(100), c.max_length) + ')' ELSE '' END +
CASE WHEN ty.name = 'nvarchar' THEN '.Length(' + CONVERT(varchar(100),c.max_length / 2) + ')' ELSE '' END +
CASE WHEN c.is_nullable = 1 THEN '.Nullable()' ELSE '' END + ';', c.max_length
FROM sys.all_columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = 'TableName' AND ty.name <> 'sysname' AND c.Name <> 'I3D'
SELECT 'public virutal ' +
CASE WHEN ty.name IN ('text', 'nvarchar', 'varchar') THEN 'string' ELSE '' END +
CASE WHEN ty.name = 'bit' THEN 'bool' ELSE '' END +
CASE WHEN ty.name = 'datetime' THEN 'DateTime' ELSE '' END +
CASE WHEN c.is_nullable = 1 AND ty.name = 'datetime' THEN 'DateTime' ELSE '' END +
CASE WHEN ty.name = 'float' THEN 'double' ELSE '' END +
CASE WHEN ty.name = 'int' THEN 'int' ELSE '' END +
CASE WHEN ty.name = 'uniqueidentifier' THEN 'Guid' ELSE '' END +
CASE WHEN ty.name = 'char' THEN 'char' ELSE '' END +
CASE WHEN c.is_nullable = 1 AND ty.name NOT IN ('text', 'nvarchar', 'varchar', 'datetime') THEN '?' ELSE '' END +
' ' + c.name + ' { get; set; }'
FROM sys.all_columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = 'TableName' AND ty.name <> 'sysname' AND c.Name <> 'I3D'
```
**In the mapping Length(0) should be .Length(int.MaxValue)**
**For the entities ? should be DateTime?**
@@ -0,0 +1,3 @@
# MVVM in centron
I don't know, but I would like to - please tell me.
@@ -0,0 +1,224 @@
# Results and Responses in c-entron.NET
This document explains the `Result` and `Response` classes in c-entron.NET, their purpose, and how they interact across the system layers. These classes form a critical part of the error handling and communication pattern throughout the application.
## Overview
In c-entron.NET, we use a standardized approach for operation results and API responses:
1. **`Result`** - Internal class used within the business logic layer to indicate operation success/failure
2. **`Response`** - Web service API class that translates `Result` objects to client-friendly responses
This pattern provides:
- Consistent error handling across all application layers
- Type-safe return values with status information
- Clean separation between internal logic and API responses
- Standardized way to include error messages and codes
## Result Class
### Location
`src/backend/Centron.Interfaces/Results/Result.cs`
### Purpose
The `Result` class represents the outcome of an operation in the business logic layer. It includes not just the success/failure status, but also contextual information like error messages and exception details.
### Structure
```csharp
public class Result
{
public string Message { get; protected set; }
public int? MessageCode { get; protected set; }
public Exception Error { get; protected set; }
public ResultStatus Status { get; protected set; }
// Factory methods and constructors...
}
```
### Status Values
The `Result` object can have one of the following statuses:
- **Success** - The operation completed successfully
- **Error** - The operation failed due to an error
- **Warning** - The operation completed but with warnings
### Factory Methods
The `Result` class uses factory methods (instead of constructors) for creating result objects:
```csharp
// Success results
Result.AsSuccess();
Result.AsSuccess("Operation completed successfully");
// Error results
Result.AsError("The operation failed", messageCode: 100);
Result.AsError("Invalid input", error: exception);
// Warning results
Result.AsWarning("Some fields could not be processed");
// From exceptions
Result.FromException("Failed to process request", exception);
Result.FromException(exception);
```
### Generic Version
There is also a generic version `Result<T>` that carries data along with the status:
```csharp
// Success with data
Result<Customer> customerResult = Result<Customer>.AsSuccess(customer);
// Error with no data
Result<Customer> errorResult = Result<Customer>.AsError("Customer not found");
```
## Response Class
### Location
`src/webservice/Centron.WebServices.Core/Messages/Response.cs`
### Purpose
The `Response` class serves as the API response format returned by web services to clients. It translates the internal `Result` objects into standardized API responses.
### Structure
```csharp
[DataContract]
public class Response
{
[DataMember]
public StatusCode Status { get; set; }
[DataMember]
public string Message { get; set; }
[DataMember]
public int MessageCode { get; set; }
// Factory methods...
}
[DataContract]
public class Response<T> : Response
{
[DataMember]
public List<T> Result { get; set; }
// Additional factory methods...
}
```
### Status Values
The `Response` object can have one of the following statuses:
- **Success** - The operation completed successfully
- **Failed** - The operation failed due to an error
Note that unlike `Result`, `Response` maps both `ResultStatus.Success` and `ResultStatus.Warning` to `StatusCode.Success`.
## Interaction Between Result and Response
The key interaction occurs through the `FromBLResult` methods in the `Response` class:
```csharp
// Converting a Result to Response
Response response = Response.FromResult(result);
// Converting a Result<T> to Response<T>
Response<Customer> response = Response<Customer>.FromBLResult(customerResult);
```
### Mapping Logic
1. `Result.Status` is mapped to `Response.Status`:
- `ResultStatus.Success` → `StatusCode.Success`
- `ResultStatus.Warning` → `StatusCode.Success` (warnings are treated as success at API level)
- `ResultStatus.Error` → `StatusCode.Failed`
2. `Result.Message` is directly copied to `Response.Message`
3. `Result.MessageCode` is copied to `Response.MessageCode` (with a default if not provided)
4. For `Result<T>`, the data is added to the `Response<T>.Result` collection
## Usage Examples
### Business Logic Layer (BL)
```csharp
public Result<CustomerDTO> GetCustomerById(int customerId)
{
try
{
var customer = this._repository.GetCustomerById(customerId);
if (customer == null)
return Result<CustomerDTO>.AsError("Customer not found");
var dto = this.ConvertToDTO(customer);
return Result<CustomerDTO>.AsSuccess(dto);
}
catch (Exception ex)
{
return Result<CustomerDTO>.FromException(ex);
}
}
```
### Web Service Layer
```csharp
public Response<CustomerDTO> GetCustomerById(int customerId)
{
var result = this._customerBL.GetCustomerById(customerId);
return Response<CustomerDTO>.FromBLResult(result);
}
```
## Best Practices
1. **Always use factory methods** instead of constructors for creating `Result` objects
```csharp
// Good
return Result.AsSuccess("Operation succeeded");
// Avoid
return new Result(ResultStatus.Success, "Operation succeeded");
```
2. **Include meaningful error messages** to help diagnose issues
```csharp
// Good
return Result.AsError($"Customer with ID {id} not found");
// Avoid
return Result.AsError("Not found");
```
3. **Propagate exceptions appropriately** using `FromException`
```csharp
try
{
// Operation code
}
catch (Exception ex)
{
return Result.FromException("Failed to complete operation", ex);
}
```
4. **Use `ThrowIfError` extension method** when chaining operations
```csharp
var result = await someOperation().ThrowIfError();
```
5. **Convert between `Result` and `Response` only at service boundaries** to maintain clean separation of concerns
## Conclusion
The `Result` and `Response` classes provide a robust pattern for error handling and operation results throughout the c-entron.NET application. Understanding how they work together is essential for maintaining consistency in API responses and proper error handling across all application layers.
@@ -0,0 +1,10 @@
# Where can i find Stani's secret API-Documentation
<details>
<summary>Here</summary>
<summary>
P:\Entwicklung C#\c-entron 2.0 Web-Services\Dokumentationen\c-entron Web-Service Dokumentation.pdf
</summary>
</details>
@@ -0,0 +1,36 @@
#We use TraySoft AddTAPI.NET component on all our products with TAPI intigration
https://www.traysoft.com/add-tapi-telephony-library
Currently c-entron.NET, c-entron Outlook Addin and ServiceBoard have TAPI-Modules.
The dlls can be found on our network drives under `P:\Entwicklung C#\Components\AddTapi.NET Professional 18.11.2021`
**OUR Traysoft.AddTAPI.dll has been modified to work with .NET 5/6**
If you update this .dll you need to check if this needs to be modified again.
##How to update
First you need a decompiled version of TraySoft.AddTapi.dll. The easiest way to do this is to use dotPeek, decompile the dll and then export as csproj.
Then navigate to the following location:
TapiLine.cs > ProcessIncomingCall
In this method the BeginInvoke call is no longer supported in our new .NET runtimes.
We can simply replace this call with the following code that *should* result in the same behavior.
```
Task.Factory.StartNew(() =>
{
tapiCallHandler.Invoke(call);
});
```
##How to debug
There are a few ways to debug tapi related things:
1. a simple TAPI-Test app under `P:\Entwicklung C#\Components\tapi_test_app` which allows a very low level testing.
2. the product itself of course, there should be vast amounts of logging everywhere (check PhoneManager.cs & TapiPhoneConnectionManager.cs in c-entron.NET)
3. if your product uses the UI in centron.Controls there's a testtool in Centron.Controls.Preview
If you need very low level logging of the AddTapi.dll itself, you can use VS and check the output tab when debugging as the dll writes its log there.
@@ -0,0 +1,159 @@
# Centron Database Script Rules
These conventions **must** be followed for all database scripts in the `Centron.BusinessLogic.Administration.Scripts.ScriptMethods.Scripts` namespace. Scripts are used to update the SQL Server database by creating/altering tables, creating indexes, functions, triggers, and updating data when necessary.
---
## 1. Script Organization & Naming
1. **Placement**
- Place all scripts in the directory: `src/backend/Centron.BL/Administration/Scripts/ScriptMethods/Scripts/`
2. **Naming Convention**
- Name scripts using the pattern: `ScriptMethod{NUMBER}.cs`
- The script number is managed in an external Excel file accessible through Teams
- When creating a new script, use a placeholder for the number and update it with the next available number from the Excel file
3. **Class Structure**
- Each script class must inherit from `BaseScriptMethod`
- The class name must match the filename
```csharp
internal class ScriptMethod11699 : BaseScriptMethod
```
## 2. Script Implementation
1. **Required Method**
- Override the `GetSqlQueries()` method from the base class
- Return type should be `IEnumerable<string>`
- Use `yield return` statements to return SQL statements
```csharp
public override IEnumerable<string> GetSqlQueries()
{
yield return ScriptHelpers.AddColumnIfNotExists("dbo", "TableName", "ColumnName", "datatype");
}
```
2. **Using ScriptHelpers**
- Always use the `ScriptHelpers` class methods to generate SQL statements
- This ensures consistency and safety in database operations
- Common helper methods:
- `AddColumnIfNotExists`
- `DropColumnIfExists`
- `ChangeColumnTypeIfExists`
- `AddTableIfNotExists`
- `AddIndexIfNotExists`
- `CreateSpecialObjectAlterIfExists`
- And many more in `ScriptHelpers.cs`
### Script Helper Note
When using `ScriptHelpers.AddTableIfNotExists()` method in database scripts, the primary key column `I3D` is automatically created and does not need to be specified in the column list. The method internally handles the creation of:
- The `I3D` [int] IDENTITY(1,1) NOT NULL column
- The primary key constraint with appropriate clustering
### Standard Audit Columns
- Add `CreatedByI3D`, `CreatedDate`, `IsDeleted`, `DeletedByI3D`, and `DeletedDate` to new NHibernate-managed domain tables according to the standard entity conventions.
- Add `ChangedByI3D` and `ChangedDate` only when rows are modified after creation.
- Write-once/read-only history tables do not need `Changed*` columns. Examples: chat messages, tool call history, immutable communication logs.
## 3. Common Script Scenarios
1. **Adding or Changing Columns**
```csharp
// Adding a column
yield return ScriptHelpers.AddColumnIfNotExists("dbo", "TableName", "ColumnName", "datatype", nullable: true/false);
// Changing a column type
yield return ScriptHelpers.ChangeColumnTypeIfExists("dbo", "TableName", "ColumnName", "new_datatype");
```
2. **Creating Tables**
```csharp
// Basic usage with dbo schema implied
yield return ScriptHelpers.AddTableIfNotExists("TableName",
("Column1", "int", false, null),
("Column2", "nvarchar(100)", false, null),
("Column3", "datetime2(0)", true, null));
// Explicit schema usage
yield return ScriptHelpers.AddTableIfNotExists(
schema: "dbo",
table: "TableName",
("Column1", "int", false, null),
("Column2", "nvarchar(100)", false, null),
("IsActive", "bit", false, null));
```
**Parameter explanation:**
- Column format: (name, datatype, nullable, defaultValue)
- For new NHibernate-managed tables, use `null` or an empty value for `defaultValue`
- NHibernate writes all mapped columns on `INSERT`, so SQL defaults on new tables do not apply in normal application writes and only create unused SQL objects
- Only consider SQL defaults when extending existing tables with new `NOT NULL` columns, where existing rows or non-NHibernate writers need a valid value
- If `defaultValue` is empty, no default constraint will be created
- The primary key column `I3D` is automatically created
- Examples of justified default values:
- For integers: "0", "1", etc.
- For strings: "''" (empty string with single quotes)
- For dates: "GETUTCDATE()"
- For bits (boolean): "1" (true) or "0" (false)
3. **Creating Indexes**
```csharp
yield return ScriptHelpers.AddIndexIfNotExists(
table: "TableName",
indexName: "IX_TableName_Column1_Column2",
columns: new List<(string column, OrderDirection? orderDirection)>
{
("Column1", OrderDirection.ASC),
("Column2", OrderDirection.ASC),
});
```
4. **Executing SQL Statements Directly**
```csharp
yield return @"
UPDATE TableName
SET Column1 = 'value'
WHERE Condition = 1;";
```
5. **Creating/Altering Views, Functions, Triggers**
```csharp
yield return ScriptHelpers.CreateSpecialObjectAlterIfExists("ViewName", "VIEW", @"
CREATE VIEW dbo.ViewName
AS
SELECT * FROM TableName
WHERE Condition = 1;");
```
## 4. Best Practices
1. **Script Independence**
- Each script should be independent and idempotent
- Use conditional checks like `IF EXISTS` and `IF NOT EXISTS`
2. **Script Safety**
- Always use `ScriptHelpers` methods when available
- When direct SQL is needed, ensure proper schema references and SQL injection protection
3. **Performance Considerations**
- For large data operations, consider transaction management and batching
- When modifying indexed columns, use `AlterColumnTypeIndexSafe` to preserve indexes
4. **Documentation**
- Add comments to clarify complex operations
- For significant schema changes, document the purpose in a comment
5. **Testing**
- Test scripts in a development environment before applying to production
- Verify the script achieves the intended changes without side effects
## 5. Examples
Refer to these existing scripts for common patterns:
- `ScriptMethod11699.cs` - Adding columns and changing column types
- `ScriptMethod11698.cs` - Executing direct SQL update statements
- `ScriptMethod11696.cs` - Creating new tables
- `ScriptMethod11677.cs` - Creating indexes
- `ScriptMethod11670.cs` - Creating and altering views (same approach for triggers, functions)
@@ -0,0 +1,351 @@
# EDI Architecture Documentation
This document provides a comprehensive overview of the Electronic Data Interchange (EDI) architecture in the c-entron.NET system, focusing on the supplier EDI integration patterns and implementation.
## Table of Contents
- [Overview](#overview)
- [Core Architecture](#core-architecture)
- [EDI Data Flow](#edi-data-flow)
- [Supplier Integration Patterns](#supplier-integration-patterns)
- [Partial Class Architecture](#partial-class-architecture)
- [EDI Data Types](#edi-data-types)
- [Document Processing Workflow](#document-processing-workflow)
- [Error Handling and Logging](#error-handling-and-logging)
- [Configuration Management](#configuration-management)
- [Extension Points](#extension-points)
## Overview
The EDI system in c-entron.NET facilitates automated business document exchange between c-entron and various suppliers. The architecture supports multiple EDI standards and supplier-specific formats, processing orders, order responses, deliveries, and invoices through a unified interface.
### Key Components
- **SupplierEdiBL**: Main business logic class handling EDI operations
- **EDICommonBL**: Shared utilities for data parsing and formatting
- **EDILogBL**: Logging and audit trail management
- **ClientConnectBL**: Connection management for external EDI services
- **Partial Classes**: Supplier-specific implementations for different EDI formats
## Core Architecture
### Class Hierarchy
```
SupplierEdiBL (main class)
├── SupplierEdiBL.AlsoCH.cs - ALSO Switzerland specific implementation
├── SupplierEdiBL.Also.cs - ALSO generic implementation
├── SupplierEdiBL.Alltron.cs - Alltron supplier integration
├── SupplierEdiBL.Herweck.cs - Herweck supplier integration
├── SupplierEdiBL.Komsa.cs - Komsa supplier integration
└── SupplierEdiBL.Opentrans.cs - OpenTrans 2.1 standard implementation
```
### Dependencies
The EDI system relies on several core components:
- **Gateway Libraries**: Supplier-specific EDI parsing libraries
- `Centron.Gateway.EDI_Also`
- `Centron.Gateway.EDI_AlsoCH`
- `Centron.Gateway.EDI_Alltron`
- `Centron.Gateway.EDI_Herweck`
- `Centron.Gateway.OpenTrans`
- **External APIs**:
- ITScope API for product data synchronization
- EGIS integration for electronic invoicing
- ZUGFeRD for structured invoice data
## EDI Data Flow
### High-Level Process Flow
```
1. Configuration Setup
├── Supplier EDI configurations defined
├── Connection parameters configured
└── Data type mappings established
2. Document Download
├── FTP/SFTP file retrieval
├── File decompression (ZIP support)
└── Document validation
3. Data Processing
├── Format detection (EdiDataType)
├── Supplier-specific parsing
└── c-entron object creation
4. Integration
├── Order matching and validation
├── Business rule application
└── Database persistence
5. Logging and Audit
├── Processing status tracking
├── Error logging
└── User notification
```
### Core Processing Method: `ApplyDistriToCentron`
The `ApplyDistriToCentron` method serves as the central dispatch mechanism for processing EDI files:
```csharp
public async Task<bool> ApplyDistriToCentron(
List<EDIDistriFile> xmlData,
SupplierEdiConfigurations config,
OrderInfo deal)
```
**Key Responsibilities:**
- Routes processing based on `EdiDataType` and `ObjectKind`
- Delegates to supplier-specific parsing methods
- Handles file cleanup after successful processing
- Returns success/failure status for downstream processing
## Supplier Integration Patterns
### Document Types Supported
Each supplier integration supports different combinations of document types:
| Supplier | Order Response | Delivery | Invoice | Notes |
|----------|---------------|----------|---------|--------|
| OpenTrans 2.1 | ✓ | ✓ | ✓ | Industry standard |
| ALSO | ✓ | ✓ | ✓ | Generic ALSO format |
| ALSO CH | ✓ | ✓ | ✓ | Switzerland-specific |
| Herweck | ✓ | ✓ | ✓ | Dual structure support |
| Komsa | ✓ | ✓ | ✗ | No invoice integration |
| Alltron | ✓ | ✓ | ✓ | Full document support |
| ZUGFeRD | ✗ | ✗ | ✓ | Invoice-only standard |
## Partial Class Architecture
### Design Pattern
The EDI system uses partial classes to organize supplier-specific logic while maintaining a unified interface. Each partial class handles:
1. **Document Parsing**: XML deserialization using supplier-specific schemas
2. **Data Mapping**: Conversion from supplier format to c-entron entities
3. **Business Logic**: Supplier-specific validation and processing rules
4. **Error Handling**: Format-specific error recovery and logging
### Example: ALSO CH Implementation
```csharp
public partial class SupplierEdiBL
{
public bool ReadAlsoCHResponse(List<EDIDistriFile> distriFiles, SupplierEdiConfigurations config)
{
// Parse ALSO CH specific XML format
var serializer = new XmlSerializer(typeof(orderresponse));
var response = (orderresponse)serializer.Deserialize(distriFile.XmlDatei);
// Map to c-entron entities
EDIOrderResponseHead head = new EDIOrderResponseHead();
// ... mapping logic
// Process and validate
return ApplyEDIReceiptToCentronOrder(lstHead, lstItems, lstData, objectKind);
}
}
```
### Supplier-Specific Features
#### ALSO CH (AlsoCH.cs)
- **Special Handling**: Swiss banking integration (ESR codes)
- **Additional Costs**: Handling of various Swiss fees (G1-G8 codes)
- **Localization**: Swiss address format and currency handling
#### Herweck (Herweck.cs)
- **Dual Structure**: Supports both legacy and new XML structures
- **Fallback Logic**: Automatic retry with alternative parsing method
- **Advanced Mapping**: Complex article code resolution
#### Alltron (Alltron.cs)
- **Serial Number Tracking**: Enhanced barcode and serial number management
- **Delivery Integration**: Detailed delivery note processing
- **Swiss Market**: Optimized for Swiss IT distribution
## EDI Data Types
### Supported Formats
```csharp
public enum EdiDataType
{
OpenTrans21 = 1, // Industry standard OpenTrans 2.1
Also = 2, // ALSO generic format
AlsoCH = 3, // ALSO Switzerland
Herweck = 4, // Herweck proprietary format
Komsa = 5, // Komsa format
Alltron = 6, // Alltron format
Zugferd = 7 // ZUGFeRD standard
}
```
### Object Kinds
```csharp
public enum EDIConnectionObjectKind
{
Order = 1, // Purchase orders (outbound)
OrderResponse = 2, // Order confirmations (inbound)
Delivery = 3, // Delivery notifications (inbound)
Invoice = 4 // Electronic invoices (inbound)
}
```
## Document Processing Workflow
### Order Response Processing
1. **Document Reception**: Download from supplier FTP/SFTP
2. **Format Detection**: Identify EDI format and supplier
3. **Header Processing**:
- Extract order reference information
- Validate buyer/supplier party IDs
- Map delivery addresses
4. **Line Item Processing**:
- Parse product codes (supplier, manufacturer, EAN)
- Extract quantities and pricing
- Handle delivery dates and availability
5. **Integration**:
- Match with existing c-entron orders
- Update order status and quantities
- Generate user notifications for discrepancies
### Delivery Note Processing
1. **Shipment Information**: Extract tracking and delivery details
2. **Serial Number Handling**: Process individual item serial numbers
3. **Quantity Validation**: Verify shipped quantities against orders
4. **Barcode Processing**: Handle product identification codes
5. **Receipt Generation**: Create delivery receipts in c-entron
### Invoice Processing
1. **Financial Data**: Extract pricing, VAT, and currency information
2. **Reference Matching**: Link invoices to deliveries and orders
3. **Tax Calculation**: Validate VAT amounts and rates
4. **Payment Terms**: Process payment conditions and bank details
5. **Accounting Integration**: Create accounting entries
## Error Handling and Logging
### Logging Framework
The EDI system uses structured logging through `EDILogBL`:
```csharp
public enum EDILogState
{
DownloadOK, // Successful processing
DownloadError, // Processing errors
DownloadTest, // Test mode operations
Exception, // System exceptions
TestException // Test mode exceptions
}
```
### Error Recovery Strategies
1. **Format Fallbacks**: Multiple parsing attempts for flexible formats
2. **Partial Processing**: Continue processing valid records despite individual failures
3. **User Notifications**: Alert users to validation issues requiring manual intervention
4. **Retry Logic**: Automatic retry for transient connection issues
### Audit Trail
- Complete processing history for each document
- User actions and validations tracked
- Error details and resolution steps logged
- Performance metrics and processing times recorded
## Configuration Management
### Supplier Configuration
Each supplier integration requires configuration through `SupplierEdiConfigurations`:
```csharp
public class SupplierEdiConfigurations
{
public int SupplierI3D { get; set; } // Supplier identifier
public string SupplierCustomerNumber { get; set; } // Customer number at supplier
public int EdiDataType { get; set; } // Format type
public int ObjectKind { get; set; } // Document type
public string ConnectionString { get; set; } // FTP/API connection details
// ... additional configuration properties
}
```
### Branch-Specific Handling
- Multi-branch deployments supported
- Branch-specific supplier configurations
- Centralized vs. distributed processing options
## Extension Points
### Adding New Suppliers
To integrate a new supplier:
1. **Create Partial Class**: `SupplierEdiBL.NewSupplier.cs`
2. **Implement Reading Methods**:
```csharp
private bool ReadNewSupplierResponse(List<EDIDistriFile> files, SupplierEdiConfigurations config)
private bool ReadNewSupplierDelivery(List<EDIDistriFile> files, SupplierEdiConfigurations config)
private bool ReadNewSupplierInvoice(List<EDIDistriFile> files, SupplierEdiConfigurations config)
```
3. **Update ApplyDistriToCentron**: Add new case for supplier format
4. **Add Gateway Library**: Create parsing library if needed
5. **Configure Mappings**: Set up data type and configuration entries
### Custom Business Logic
- Override validation rules in supplier-specific partials
- Implement custom data transformations
- Add supplier-specific error handling
- Extend logging and audit capabilities
### API Integration
- ITScope API integration for real-time product data
- EGIS electronic invoicing support
- ZUGFeRD structured invoice processing
- Custom API endpoints for supplier-specific requirements
## Best Practices
### Performance Considerations
- Batch processing for large document volumes
- Async processing for I/O operations
- Memory-efficient XML parsing for large files
- Connection pooling for FTP/API operations
### Security
- Secure credential storage for supplier connections
- Encrypted data transmission (SFTP/HTTPS)
- Audit logging for compliance requirements
- Access control for EDI operations
### Maintainability
- Consistent error handling patterns across suppliers
- Comprehensive unit testing for each supplier integration
- Clear separation of concerns between parsing and business logic
- Documentation of supplier-specific requirements and limitations
---
**Related Documentation:**
- [Database Schema Reference](../database/README.md)
- [Security Architecture](../security/README.md)
- [Receipt Processing Guide](../receipts/README.md)
@@ -0,0 +1,244 @@
# EDI-Service Import Process
This document outlines the technical specifications and process flow for how the c-entron.NET EDI-Service downloads EDI documents from suppliers and imports them into the database.
## 1. System Architecture
### 1.1 Components
- **EdiDownloadService**: ASP.NET Core BackgroundService for scheduled downloads
- **SupplierEdiWebServiceBL**: Business logic layer for EDI web services
- **SupplierEdiBL**: Core business logic for supplier EDI operations
- **EDIConnectBL**: Connection handling for FTP/SFTP/FTPS
### 1.2 Execution Frequency
- Runs every 30 minutes (configurable)
- Initial 1 minute delay after system startup
- Log cleanup for entries older than 185 days occurs between 00:00-02:00
## 2. Document Processing Flow
### 2.1 Initialization & Configuration
```
EdiDownloadService.ExecuteAsync
└── SupplierEdiWebServiceBL.EDIDownloadStartAsync
└── SupplierEdiBL.DownloadStartAsync
├── GetSupplierEdiConfigurations
└── ProcessIndividualConfigurations
```
### 2.2 Download Process
For each supplier configuration:
1. **Connection Selection**:
- FTP/FTPS: Uses `Ftp_DownloadAsync()`
- SFTP: Uses `sFtp_DownloadAsync()`
2. **File Filtering**:
```csharp
// Get list of already processed files
var usedFiles = UsedFiles(config);
// Filter available files
foreach (FtpListItem file in serverFiles.Data.Where(f => f.Type == FtpObjectType.File))
{
// Skip if specific file expected but doesn't match
if (!string.IsNullOrEmpty(expectedFile) && expectedFile != file.Name) continue;
// Skip blacklisted files (those that failed multiple times before)
if (badFiles.Any(f => f.Value.Contains(file.Name))) continue;
// Skip already processed files
if (usedFiles.IndexOf(file.Name) > -1) continue;
// Skip files not matching mask pattern
if (!FitMask(file.Name, config.Mask)) continue;
// Process file
var distriFiles = await DownloadFtpFile(config, file.Name);
if (distriFiles != null)
if (await ApplyDistriToCentron(distriFiles, config, null)) ++nSaved;
}
```
### 2.3 ZIP Handling
Files are processed differently based on extension:
```csharp
if (Path.GetExtension(fileName).ToLower() == ".zip")
{
// Add ZIP file to list
distriFiles.Add(new EDIDistriFile() { DistriName = fileName });
// Extract contents
ZipExtract(result.Data, distriFiles);
}
else
{
// Handle non-ZIP file
MemoryStream dataStream = new MemoryStream();
result.Data.CopyTo(dataStream);
distriFiles.Add(new EDIDistriFile() {
UnpackName = fileName,
DistriName = fileName,
XmlDatei = dataStream
});
}
```
### 2.4 Database Import
Import logic varies by document and supplier type:
```csharp
switch (config.EdiDataType)
{
case (int)EdiDataType.OpenTrans21:
if (config.ObjectKind == (int)EDIConnectionObjectKind.OrderResponse)
isOk = await ReadOT21Response(xmlData, config, deal);
if (config.ObjectKind == (int)EDIConnectionObjectKind.Delivery)
isOk = await ReadOT21Delivery(xmlData, config, deal);
if (config.ObjectKind == (int)EDIConnectionObjectKind.Invoice)
isOk = await ReadOT21InvoiceAsync(xmlData, config, deal);
break;
// Additional formats (Also, AlsoCH, Herweck, etc.)
...
}
```
## 3. Database Schema
### 3.1 Primary EDI Tables
| Table | Description | Key Columns |
|-------|-------------|------------|
| `[dbo].[EDIInvoiceHead]` | Stores EDI invoice headers | `I3D`, `SupplierI3D`, `OrigFileName` |
| `[dbo].[EDIInvoicePositions]` | Stores EDI invoice line items | `I3D`, `InvoiceHeadI3D` |
| `[dbo].[EDIDeliveryHead]` | Stores EDI delivery headers | `I3D`, `SupplierI3D`, `OrigFileName` |
| `[dbo].[EDIDeliveryPositions]` | Stores EDI delivery line items | `I3D`, `DeliveryHeadI3D` |
### 3.2 File Tracking
The system prevents duplicate imports by checking the `OrigFileName` column:
```csharp
// For invoices
if (config.ObjectKind == (int)EDIConnectionObjectKind.Invoice)
{
var used = this.Session.GetGenericDAO<EDIInvoiceHead>().GetEntityList(f => f.SupplierI3D == config.SupplierI3D);
return used.Select(f => f.OrigFileName).ToList();
}
// For delivery notes
if (config.ObjectKind == (int)EDIConnectionObjectKind.Delivery)
{
var used = this.Session.GetGenericDAO<EDIDeliveryHead>().GetEntityList(f => f.SupplierI3D == config.SupplierI3D);
return used.Select(f => f.OrigFileName).ToList();
}
```
## 4. Special Case Handling
### 4.1 Distributor-Specific Processing
- **ITScope**: Uses `LoadITScopeReceiptAsync()` for specialized receipt handling
- **EGIS**: Uses `CheckEgisAsync()` for EGIS-specific downloads
- **Supplier-Specific Format Handlers**:
- OpenTrans 2.1 (`ReadOT21*` methods)
- Also (`ReadAlso*` methods)
- AlsoCH (`ReadAlsoCH*` methods)
- Herweck (`ReadHerweck*` methods)
- Komsa (`ReadKomsa*` methods)
- Alltron (`ReadAlltron*` methods)
- Zugferd (`ReadZugferd*` methods)
### 4.2 Error Handling & File Blacklist
- Failed downloads are tracked in a separate list
- The system can be configured to retry previously failed downloads
- Errors are logged with detailed exception information
- A file blacklist mechanism prevents repeated processing of problematic files
#### 4.2.1 File Blacklist Implementation
The system maintains a blacklist of files that have repeatedly failed processing:
```csharp
// In DownloadStartAsync method:
badFiles = GetDownloadWithError(config.SupplierI3D, config.ObjectKind);
// Files with more than 3 recorded exceptions are blacklisted
await Ftp_DownloadAsync(config, badFiles.Where(f => f.ID > 3).ToList(), expectedFile);
```
The blacklist is populated from the `EDIManagementLog` table using SQL:
```csharp
private List<IntStringList> GetDownloadWithError(int distributorI3D, int objectKind)
{
ReceiptLogKind logKind;
// Map objectKind to appropriate log kind...
string sSql = $@"select COUNT(*) ID, l.FileName Value from EDIManagementLog l
Where l.State = {(int)EDILogState.Exception}
and l.EDIReceiptLogKind = {(int)logKind}
and l.DistributorI3D = {distributorI3D.ToString()}
Group By l.FileName ";
return Session.Advanced.RawSqlAccess.ExecuteQuery<IntStringList>(sSql, null).ToList();
}
```
During download processing, blacklisted files are skipped:
```csharp
// In both Ftp_DownloadAsync and sFtp_DownloadAsync methods
if (badFiles.Any(f => f.Value.Contains(file.Name))) continue;
```
This prevents the system from repeatedly trying to process files that have caused multiple exceptions, reducing system load and avoiding potential endless error loops.
## 5. Logging System
The EDI process uses NLog for comprehensive logging:
```csharp
// Log start of EDI process
Logger.Info($"EDI Download starts.");
// Log errors with full exception details
Logger.Error(exception, "EDI Download ERROR");
// Detailed operation logs via _eDILogBL
_eDILogBL.WriteEdiDownloadLog(config, EDILogState.DownloadTest, fileName: expectedFile, comment: $"File: {expectedFile} has already been exported.");
```
## 6. Testing & Debugging
### 6.1 Test Mode
A test mode is available (`isTest` parameter in `DownloadStartAsync`):
- Files are not deleted from remote server
- More detailed logs are generated
- Can target specific files via `expectedFile` parameter
### 6.2 System User
The system uses a designated system user account:
```csharp
var user = this.Session.GetGenericDAO<AppUser>().GetById(
new AppSettingsBL(Session).GetSettings(ApplicationSettingID.CentronSystemUser)
.GetInt(ApplicationSettingID.CentronSystemUser, null));
```
## 7. Security Considerations
- Connection credentials are securely stored in supplier EDI configuration
- Supports secure protocols: FTPS (FTP with SSL/TLS) and SFTP
- Files are processed in memory to minimize disk exposure
- System user permissions control database operations
@@ -0,0 +1,123 @@
# RMM-Article Logic in Contract Billing
## Overview
The Remote Monitoring Management (RMM) Article functionality in c-entron.NET allows for automatic billing of usage-based services that are measured by an external RMM system. This document outlines the rules, workflow, and technical implementation of the RMM Article billing process.
## Key Concepts
### RMM System Integration
- The c-entron.NET application integrates with external RMM systems (e.g., "Riverbird") to retrieve usage statistics
- Usage data is collected for specified periods and used to calculate billing amounts
- Communication happens via the `RiverConnectionBL` class which connects to the RMM service
### Contract Article References
- Each billable RMM item is defined as a `ContractArticleReferenzes` entity
- These references link articles in c-entron.NET to specific metrics in the RMM system
- Article references contain configuration for billing calculation rules
## Workflow
### 1. Contract Configuration
1. A contract is configured to use RMM billing (`WhetherRMM` returns true)
2. Contract article references are configured, specifying:
- Article type (e.g., server, workstation)
- Article reference (linking to the inventory item)
- Pricing rules
### 2. Invoice Generation Process
1. During the `CreateInvoiceToContractComplete` process, `CheckRMMArticle` is called
2. The system checks if the contract has RMM enabled
3. The system looks for a placeholder tag `@@RMMArtikel@@` in the invoice template
4. Contract article references are retrieved for the specific contract
5. The system queries the external RMM service for usage data in the billing period
6. For each article reference with available usage data:
- Usage amount is calculated using `CalculateContractBillingAmount`
- An invoice line item is created with the calculated amount
- Descriptive text is added explaining the service type
- The item is inserted at the position marked by `@@RMMArtikel@@` or near the end of the invoice
### 3. Placeholder Handling
- If the invoice template contains a text element with `@@RMMArtikel@@`, it serves as a position marker
- This placeholder is removed and replaced with the actual RMM article items
- If no placeholder is found, RMM items are inserted near the end of the invoice (count - 2 position)
## Error Handling
### External Service Unavailability
- If the RMM service is unavailable during invoice generation, and the contract requires RMM data:
- An `RMMServiceUnavailableException` is thrown
- The invoice creation process is aborted
- An error message is logged with details about the failure
- This prevents invoices from being created with incomplete usage data, ensuring customers are billed correctly
### Data Integrity Rules
- When a parent entity is deleted (State = 0), related child entities should also be marked as deleted
- This ensures data consistency when RMM configurations change
## Technical Implementation Details
### RMM Article Detection
```csharp
var rmmItem = invoice.Items.FirstOrDefault(f =>
(f.RichText != null && f.RichText.IndexOf("@@RMMArtikel@@", StringComparison.InvariantCulture) > -1) ||
(f.Text != null && f.Text.IndexOf("@@RMMArtikel@@", StringComparison.InvariantCulture) > -1));
```
### Usage Data Retrieval
```csharp
var riverbirdStatisticsResult = new RiverConnectionBL(this.Session).GetContractBillingAmounts(
billingParam.InvoiceFrom.Value,
billingParam.InvoiceTo.Value.AddDays(1),
invoice.CustomerI3D,
rmmArticleReferences);
```
### Error Handling for Service Unavailability
```csharp
if (riverbirdStatisticsResult.Status is ResultStatus.Error)
{
// Only throw exception when RMM articles are expected
if (rmmItem != null || rmmArticleReferences.Any())
{
string errorMsg = $"Die Rechnung kann nicht erstellt werden, da der RMM-Service nicht erreichbar ist. " +
$"Fehlermeldung: {riverbirdStatisticsResult.Message}";
_logger.Error(errorMsg);
throw new RMMServiceUnavailableException(errorMsg);
}
return;
}
```
## Best Practices
1. **Service Configuration**
- Ensure the RMM service URL is properly configured in application settings
- Verify authentication tickets are valid for the RMM service
2. **Contract Setup**
- Associate correct article references with appropriate RMM metrics
- Set proper calculation rules for each article type
3. **Invoice Templates**
- Include the `@@RMMArtikel@@` placeholder in invoice templates where RMM items should appear
- Ensure proper formatting and positioning for RMM article items
4. **Monitoring**
- Monitor logs for RMM service connectivity issues
- Periodically verify that usage data is being correctly retrieved and calculated
## Troubleshooting
| Problem | Possible Cause | Solution |
|---------|---------------|----------|
| No RMM items in invoice | RMM not enabled for contract | Check contract configuration |
| No RMM items in invoice | No usage data in RMM system | Verify usage data in RMM system |
| Invoice creation fails | RMM service unavailable | Check network connectivity and service status |
| Incorrect billing amounts | Calculation rules misconfigured | Review article reference configuration |
## Related Components
- `AutomaticFacturaWebServiceBL` - Main billing logic
- `RiverConnectionBL` - Handles communication with RMM service
- `ContractArticleReferenzes` - Defines article references for RMM billing
@@ -0,0 +1,398 @@
# ActionPrice System Documentation
## Overview
The ActionPrice (Aktionspreis) system in c-entron manages time-limited promotional pricing from distributors and manufacturers. It integrates seamlessly with the price matrix (Preismatrix) to provide users with current action prices alongside other pricing sources.
## Table of Contents
- [Database Structure](#database-structure)
- [Architecture & Components](#architecture--components)
- [Data Flow](#data-flow)
- [Data Sources](#data-sources)
- [UI Access](#ui-access)
- [Integration with Price Matrix](#integration-with-price-matrix)
- [API Reference](#api-reference)
- [Business Rules](#business-rules)
## Database Structure
### Table: `HerstellerArtikAktionspreis`
**Location**: SQL Server database
**Mapped by**: `ActionPriceMaps.cs`
| Column | Data Type | Description |
|--------|-----------|-------------|
| `I3D` | int IDENTITY(1,1) | Primary key |
| `ArtikelI3D` | int | Foreign key to Article table |
| `Artikelcode` | nvarchar(60) | Article code |
| `Preis` | decimal | Action price |
| `Distributor` | nvarchar(100) | Distributor name |
| `GueltigAb` | datetime2(2) | Effective from date |
| `GueltigBis` | datetime2(2) | Effective until date |
| `Text` | nvarchar(500) | Description/notes |
| `Hersteller` | nvarchar(60) | Manufacturer |
| `BearbeiterI3D` | int | Editor user ID |
| `EDI_I3D` | int | EDI integration ID (reserved) |
| `Verfuegbarkeit` | nvarchar(50) | Availability |
| `VK` | decimal | Selling price |
| `Kreditorcode` | nvarchar(50) | Creditor code |
| `Status` | int | Status flag |
| `DistID` | nvarchar(50) | Distributor product ID |
## Architecture & Components
### Core Components
#### 1. Entity Layer
- **File**: `Centron.Entities/Warehousing/ActionPrice.cs`
- **Purpose**: Domain entity representing action price data
- **Properties**: Maps 1:1 with database columns
#### 2. Data Access Layer (DAO)
- **File**: `Centron.DAO/Mappings/Warehousing/ActionPriceMaps.cs`
- **Purpose**: NHibernate mapping for ActionPrice entity
- **Technology**: FluentNHibernate
#### 3. Business Logic Layer (BL)
- **File**: `Centron.BL/Warehousing/ActionPriceBL.cs`
- **Methods**:
- `GetActionPrice(int actionPriceI3D)`
- `GetActionPricesByArticleI3D(int articleI3D)`
- `SaveOrUpdateActionPrice(ActionPrice actionPrice)`
- `DeleteActionPrice(ActionPrice actionPrice)`
#### 4. Web Service Layer
- **File**: `Centron.BL/WebServices/Warehousing/ActionPriceWebServiceBL.cs`
- **Purpose**: DTO conversion and web service operations
- **Features**: Entity ↔ DTO mapping using ObjectMapper
#### 5. REST API
- **File**: `CentronRestService.cs`
- **Endpoints**:
- `POST /GetActionPrice`
- `POST /GetActionPricesByArticleI3D`
- `POST /SaveOrUpdateActionPrice`
- `POST /DeleteActionPrice`
### Dual Implementation Pattern
Following c-entron's standard pattern, ActionPrice supports both connection types:
#### BL Logic (Direct Database)
- **File**: `BLActionPriceLogic.cs`
- **Connection**: `CentronConnectionType.SqlServer`
- **Access**: Direct database via NHibernate
#### WS Logic (Web Service)
- **File**: `WSActionPriceLogic.cs`
- **Connection**: `CentronConnectionType.CentronWebServices`
- **Access**: REST API calls
## Data Flow
### Reading ActionPrices
```
1. Price Matrix Request
↓
2. Article Lookup (by ManufacturerCode or EAN)
↓
3. IActionPriceLogic.GetActionPricesByArticleI3D()
↓
4. Filter by Date Range (current valid prices only)
↓
5. Convert to PriceItemViewModel
↓
6. Display in Price Matrix Grid
```
### Creating ActionPrices
```
1. User Right-clicks Price Matrix Grid
↓
2. Select "Aktionspreis hinzufügen"
↓
3. AddActionPriceViewModel Dialog Opens
↓
4. User Enters Data (Distributor, Price, Dates)
↓
5. Validation (Distributor required, valid date range)
↓
6. IActionPriceLogic.SaveOrUpdateActionPrice()
↓
7. Data Saved to Database
↓
8. Price Matrix Refreshed
```
## Data Sources
### Current Active Sources
#### 1. Manual Entry (Primary)
- **Location**: Article Management → Additional Info → Preisspiegel Tab
- **Method**: Right-click context menu → "Aktionspreis hinzufügen"
- **Validation**:
- Distributor name required
- EffectiveFrom ≤ EffectiveUntil
- **User Tracking**: EditorI3D field tracks creator
### Potential Sources (Infrastructure Exists)
#### 1. EDI Integration
- **Evidence**: `EDI_I3D` field in database
- **Status**: Infrastructure exists but no active implementation found
- **Purpose**: Automated import from supplier EDI systems
#### 2. Bulk Import
- **Evidence**: Standard c-entron import patterns
- **Status**: No specific ActionPrice import modules identified
- **Potential**: Could be implemented for supplier data feeds
## UI Access
### Step-by-Step Navigation
1. **Open Article Management**
- Navigate: Warehousing → Article Management
2. **Select Article**
- Search for and open an existing article
3. **Access Additional Info**
- Navigate to "Zusatzinfo" (Additional Info) section
4. **Open Preisspiegel Tab**
- Click on "Preisspiegel" tab
- This displays the price matrix grid
5. **Access ActionPrice Functions**
- **Right-click** on the price matrix grid
- Context menu appears with options:
- "Preisspiegel aktualisieren" (Refresh)
- "Aktionspreis hinzufügen" (Add ActionPrice)
- "Aktionspreis bearbeiten" (Edit ActionPrice)
- "Aktionspreis löschen" (Delete ActionPrice)
### UI Components
- **View**: `ArticleAdditionalInfoView.xaml`
- **ViewModel**: `ArticleAdditionalInfoViewModel.cs`
- **Grid**: `PriceWatchGridControl` (line 68)
- **Tab**: "Preisspiegel" (line 64)
- **Context Menu**: Lines 123-141
## Integration with Price Matrix
### Price Matrix Sources
ActionPrice is one of 7 parallel price sources in the matrix:
1. **ITscope** - External API
2. **Article Import** - Imported price data
3. **COP** - External API
4. **NEOS** - External API
5. **TradersGuide** - External API
6. **EGIS** - External API
7. **Aktionspreise** - Internal action prices ←
### Display Logic
- **File**: `PriceMatrixViewModel.cs`
- **Method**: `GetPriceItemsFromArticleActionPrices()` (lines 416-459)
- **Filtering**: Only shows prices where current date is within EffectiveFrom/EffectiveUntil range
- **Service Label**: Displays as "Aktionspreise" in Service column
- **Description Format**: "Aktionspreis vom {EffectiveFrom:d} bis {EffectiveUntil:d}. {Text}"
### Price Item Properties
```csharp
// ActionPrice in Price Matrix
Service = "Aktionspreise"
Supplier = actionPrice.Distributor
PurchasePrice = actionPrice.Price
RawPurchasePrice = actionPrice.Price
Date = actionPrice.EffectiveFrom
Stock = null // Always visible
ArticleDescription = "Aktionspreis vom ... bis ... {Text}"
```
## API Reference
### REST Endpoints
#### Get Single ActionPrice
```http
POST /GetActionPrice
Content-Type: application/json
{
"Data": 123 // ActionPrice I3D
}
```
#### Get ActionPrices by Article
```http
POST /GetActionPricesByArticleI3D
Content-Type: application/json
{
"Data": 456 // Article I3D
}
```
#### Save or Update ActionPrice
```http
POST /SaveOrUpdateActionPrice
Content-Type: application/json
{
"Data": {
"I3D": 0, // 0 for new, >0 for update
"ArticleI3D": 456,
"ArticleCode": "ART001",
"Price": 99.99,
"Distributor": "Supplier Name",
"EffectiveFrom": "2024-01-01T00:00:00",
"EffectiveUntil": "2024-12-31T23:59:59",
"Text": "Special promotion",
"Manufacturer": "Brand Name"
}
}
```
#### Delete ActionPrice
```http
POST /DeleteActionPrice
Content-Type: application/json
{
"Data": {
"I3D": 123,
// ... other properties
}
}
```
### Code Usage
#### Get ActionPrices for Article
```csharp
var actionPrices = await ClassContainer.Instance
.WithInstance((IActionPriceLogic logic) =>
logic.GetActionPricesByArticleI3D(articleI3D))
.ThrowIfError();
```
#### Save New ActionPrice
```csharp
var actionPriceDTO = new ActionPriceDTO
{
ArticleI3D = articleI3D,
Distributor = "Supplier Name",
Price = 99.99,
EffectiveFrom = DateTime.Now,
EffectiveUntil = DateTime.Now.AddMonths(3),
Text = "Special promotion"
};
await ClassContainer.Instance
.WithInstance((IActionPriceLogic logic) =>
logic.SaveOrUpdateActionPrice(actionPriceDTO))
.ThrowIfError();
```
## Business Rules
### Validation Rules
1. **Required Fields**
- `Distributor` - Must not be empty or whitespace
2. **Date Validation**
- `EffectiveFrom` must be ≤ `EffectiveUntil`
- Both dates are required
3. **Display Rules**
- Only ActionPrices with current date within effective range show in Price Matrix
- Filter: `EffectiveFrom.StartOfDay() <= DateTime.Now && EffectiveUntil >= DateTime.Now`
### Data Integrity
1. **Article Linking**
- ActionPrices are linked to articles via `ArticleI3D`
- Article must exist in system
2. **User Tracking**
- `EditorI3D` tracks who created/modified the record
- Automatically set during save operations
3. **Status Management**
- `Status` field available for workflow management
- Currently not actively used in UI
### Price Matrix Integration
1. **Loading Priority**
- ActionPrices loaded in parallel with other price sources
- No specific priority ordering
2. **Cache Behavior**
- Price matrix results are cached by ManufacturerCode + EANCode
- Cache invalidated when ActionPrices are modified
3. **Display Formatting**
- ActionPrices always show Stock as null (always visible)
- Service column shows "Aktionspreise"
- Description includes date range and text
## Troubleshooting
### Common Issues
1. **ActionPrice Not Visible in Price Matrix**
- Check if current date is within EffectiveFrom/EffectiveUntil range
- Verify article linking via ArticleI3D
- Ensure Price Matrix cache is refreshed
2. **Context Menu Not Appearing**
- Ensure right-clicking directly on the Price Matrix grid
- Check if article is properly selected
- Verify user is in "Preisspiegel" tab
3. **Save Validation Errors**
- Verify Distributor field is not empty
- Check date range: EffectiveFrom ≤ EffectiveUntil
- Ensure all required fields are populated
### Debug Information
- **Price Matrix Loading**: Check `PriceMatrixViewModel.GetPriceItemsFromArticleActionPrices()`
- **Article Lookup**: Verify article found by ManufacturerCode or EANCode
- **Date Filtering**: Current ActionPrice validation logic
- **UI Binding**: Check `ArticleAdditionalInfoViewModel.ActionPrices` collection
## Development Notes
### Future Enhancements
1. **EDI Integration**
- `EDI_I3D` field suggests planned EDI integration
- Could automate ActionPrice imports from suppliers
2. **Bulk Import**
- Standard c-entron import patterns could be applied
- Excel/CSV import functionality possible
3. **Workflow Management**
- `Status` field could support approval workflows
- Multi-step ActionPrice approval process
4. **Advanced Filtering**
- Additional filter options in Price Matrix
- ActionPrice-specific search capabilities
### Code Maintenance
- **Entity Changes**: Update both `ActionPrice` entity and `ActionPriceDTO`
- **Database Changes**: Update `ActionPriceMaps` NHibernate mapping
- **API Changes**: Update both BL and WS logic implementations
- **UI Changes**: Update both View and ViewModel files
---
*This documentation covers the complete ActionPrice system as implemented in c-entron. For questions or updates, refer to the source code files referenced throughout this document.*
@@ -0,0 +1,435 @@
# Contracts Backend Architecture
This document describes the specific backend implementation for contracts within the c-entron.NET receipts system. Contracts extend the generic receipt architecture with specialized functionality for recurring billing, service agreements, and customer asset management.
## Overview
Contracts in c-entron.NET are specialized receipts that handle ongoing service agreements, maintenance contracts, and recurring billing scenarios. They extend the base receipt functionality with contract-specific features like billing intervals, contingent management, device tracking, and automated invoice generation.
## Entity Architecture
### ReceiptContract Entity
**Location:** `src/backend/Centron.Entities/Entities/Sales/Receipts/ContractLists/ReceiptContract.cs`
The `ReceiptContract` class extends `ReceiptBase` and implements `IReceiptContract`. It represents the main contract entity with comprehensive contract-specific properties.
#### Core Contract Properties
**Customer and Project Information:**
- `CustomerI3D`: Primary customer reference
- `ProjectNumber`: Project identifier for the contract
- `PurchaseOrderNumber`: Customer's purchase order reference
- `AdditionalText`: Supplementary contract description
**Personnel Assignment:**
- `SalesRepresentativeI3D`: Assigned sales representative
- `OfficeStaffI3D`: Internal staff responsible for contract management
**Delivery and Billing Addresses:**
- `DeliveryAddress`, `DeliveryAddressCustomerI3D`: Service delivery location
- `InvoiceAddress`, `InvoiceAddressCustomerI3D`: Billing address information
- `LicenseeAddress`, `LicenseeAddressCustomerI3D`: Software licensing address
**Contract Lifecycle:**
- `DeliveryDate`: Contract start or service delivery date
- `ContractEnd`: Contract termination date
- `ContractTermination`: Actual termination date
- `FirstPaidDate`: Date of first payment received
- `ReminderDate`: Follow-up reminder date
- `PreparationDate`: Contract preparation date
- `FinishDate`: Contract completion date
#### Billing Configuration
**Billing Intervals:**
- `BillingIntervalKind`: Type of billing cycle (Daily, Monthly, Quarterly, Yearly)
- `BillingIntervalDuration`: Number of intervals (e.g., 3 for quarterly when kind is Monthly)
- `BillingKind`: Billing methodology (enum BillingKinds)
- `AutomatedBilling`: Boolean flag for automatic invoice generation
**Contract Calculation:**
- `CalculationKind`: How contract values are calculated (enum ContractCalculationKind)
- `CalcNeedKind`: Calculation requirements (enum ContractNeedCalcKind)
- `IsNormalize`: Whether to normalize billing amounts
- `IsFullNormalizeAmount`: Full normalization flag
**Payment and Collection:**
- `PaymentConditionI3D`: Reference to payment terms
- `PaymentConditionText`: Custom payment terms description
- `CollectInvoice`: Collection settings
- `MandatI3D`: SEPA mandate reference
#### Advanced Contract Features
**Contingent Management:**
- `ContingentUsedHours`: Hours consumed from contract contingent
- `ContingentUsedAmount`: Monetary amount consumed
- `ContingentBalanceUsedHours`: Balance hours utilized
- `ContingentBalanceUsedAmount`: Balance amount utilized
- `ContingentBalanceArticleI3D`: Article used for contingent balancing
- `UseContingentBalanceArticle`: Flag to enable balance article usage
- `ContingentResidualValueStart`: Starting residual value
- `ContingentResidualValueStartDate`: Start date for residual calculation
**Contingent Limits and Monitoring:**
- `IsContingentLimitBilling`: Enable contingent limit billing
- `ContingentLimitValue`: Limit threshold value
- `ContingentLimitKind`: Type of limit (enum ContingentLimitKinds)
- `IsMonitoring`: Enable contract monitoring
- `MonitoringValue`: Monitoring threshold
**Device and Asset Management:**
- Contract-specific device relationships through master data lists
- Serial number tracking and device lifecycle management
- Click counter management for printer/copier contracts
#### Contract Automation
**Prolongation and Renewals:**
- `AutomatedProlongation`: Automatic contract renewal flag
- `LastSubsequentBillingDate`: Date of last follow-up billing
**Web Integration:**
- `IsDisplayedOnWeb`: Web portal visibility flag
- `WebReportI3D`: Associated web report
### Database Schema
#### Contract Database Architecture
Contracts follow the dual-layer database architecture used throughout the receipts system, consisting of legacy German-named tables and modern English-named views.
#### Legacy Contract Tables
##### VertragKopf Table (Contract Headers)
**Physical Table:** `dbo.VertragKopf`
**Entity Class:** `Centron.DAO.TemporaryEntities.VertragKopf`
**Mapping:** `Centron.DAO.Mappings.TemporaryEntities.VertragKopfMaps`
The `VertragKopf` table inherits from `ReceiptTable` and contains contract header information.
**Key Columns:**
- `I3D` - Primary key (identity)
- `Nummer` - Contract number
- `KundenI3D` - Customer reference
- `Datum` - Contract date
- `Status` - Contract state
- `Berechnungsart` - Calculation method
- `AutoVerlaengerung` - Auto-renewal flag
- `AbrechnungsIntervallArt` - Billing interval type
- `AbrechnungsIntervallDauer` - Billing interval duration
- `VertragsBeginn` - Contract start date
- `VertragsEnde` - Contract end date
- `KuendigungsDatum` - Termination date
- `ErsteBezahlung` - First payment date
- `KontingentWert` - Contingent value
- `KontingentArt` - Contingent type
- `Automatische Abrechnung` - Automated billing flag
##### VertragPos Table (Contract Items)
**Physical Table:** `dbo.VertragPos`
**Entity Class:** `Centron.DAO.TemporaryEntities.VertragPos`
**Mapping:** `Centron.DAO.Mappings.TemporaryEntities.VertragPosMaps`
The `VertragPos` table contains contract line items and positions.
**Key Columns:**
- `I3D` - Primary key (identity)
- `VertragKopfI3D` - Foreign key to contract header
- `Pos` - Position number for ordering
- `ArtikelI3D` - Article/product reference
- `Text` - Item description
- `Stk` - Quantity
- `VKKalk` - Sales price calculation
- `EK` - Purchase price
- `MwstI3D` - VAT rate reference
- `VertragI3D` - Contract reference for recurring items
- `Lieferdatum` - Delivery date
- `Benachrichtigungsdatum` - Notification date
##### Contract Version Tables
**Version History Tables:**
- `VertragKopfVersions` - Contract header version history
- `VertragPosVersions` - Contract items version history
**Critical Architecture Detail:** Version tables are **exact 1:1 copies** of their corresponding original tables (`VertragKopf` and `VertragPos`). This means:
- Every column that exists in `VertragKopf` must also exist in `VertragKopfVersions` with identical data types
- Every column that exists in `VertragPos` must also exist in `VertragPosVersions` with identical data types
- The only exceptions are system columns (`I3D`, `OriginalI3D`) which are handled specially
- Additional versioning columns are added: `OriginalI3D` (references original record) and `KopfVersionsI3D` (for position tables)
**Contract Version Creation Process:**
When a contract version is saved (using `AssetHeadDAO.SaveAssetVersion` mechanism):
```sql
-- Save contract header version
INSERT INTO VertragKopfVersions (all_columns_except_I3D, OriginalI3D)
SELECT all_columns_except_I3D, I3D AS OriginalI3D
FROM VertragKopf
WHERE I3D = @contractId
-- Save contract items version
INSERT INTO VertragPosVersions (all_columns_except_I3D, OriginalI3D, KopfVersionsI3D)
SELECT all_columns_except_I3D, I3D AS OriginalI3D, @headerVersionId AS KopfVersionsI3D
FROM VertragPos
WHERE VertragKopfI3D = @contractId
```
This enables complete audit trails of all contract modifications, rollback capability, and change tracking functionality.
**⚠️ Schema Maintenance Warning:** When adding new columns to `VertragKopf` or `VertragPos`, the identical columns must also be added to their corresponding version tables (`VertragKopfVersions` and `VertragPosVersions`). Failure to maintain this 1:1 correspondence will cause runtime errors during version saving operations.
#### Modern Contract Views
##### Contracts View (Contract Headers)
**Database View:** `dbo.Contracts`
**Purpose:** Clean, English-named view of VertragKopf for C# application use
**Benefits:**
- Consistent English naming convention
- Cleaner column structure
- Type safety improvements
- Better integration with ORM mapping
##### ContractItems View (Contract Items)
**Database View:** `dbo.ContractItems`
**Purpose:** Clean, English-named view of VertragPos for C# application use
##### Contract Version Views
**Version Views:**
- `ContractVersions` - Clean view of VertragKopfVersions
- `ContractItemVersions` - Clean view of VertragPosVersions
#### Contract Logging Integration
##### AnlageLog Integration
Contracts integrate with the centralized `AnlageLog` table for audit logging:
**Contract Log Entries:**
- `AnlageArt = 22` (Contract identifier)
- `AnlageI3D` - References contract I3D from VertragKopf
- Log entries for contract creation, modifications, billing events, renewals, and terminations
**Typical Log Events:**
- Contract creation and approval
- Billing interval changes
- Contingent modifications
- Device associations/removals
- Automated billing execution
- Contract renewals and terminations
## Business Logic Architecture
### ReceiptContractBL
**Location:** `src/backend/Centron.BL/Sales/Receipts/ContractLists/ReceiptContractBL.cs`
The `ReceiptContractBL` class provides contract-specific business logic extending the base receipt functionality.
#### Key Responsibilities
**Contract Invoice Management:**
- `DeactivateContractInvoice()`: Deactivate invoices generated from contracts
- `GetContractInfosFromInvoices()`: Extract contract information from invoices
- `ExistsInvoiceForContract()`: Check if invoices exist for a contract
**Device and Counter Management:**
- `ResetDeviceClickCounter()`: Reset click counters for printer/copier contracts
- `UpdateDeviceToContract()`: Associate devices with contracts
- `CheckCounterHistory()`: Validate counter readings history
**Contingent and Billing:**
- `ContractContingentBalanceCalculation()`: Calculate contingent balances
- `CalculateContingentWithRecalculationArticle()`: Handle contingent recalculations
- `UpdateContractContingentBalanceCalculationForReceiptChange()`: Adjust balances when receipts change
- `UpdateTakeRestAndOverBooking()`: Handle remainder and overbooking scenarios
**Master Data List Management:**
- `AddMasteDateListsToContract()`: Associate master data lists with contracts
- `CreateMasterDataListsForNewMspArticles()`: Create lists for new MSP articles
- `RemoveMasterDataList()`: Remove master data list associations
- `CheckRemovedMasterDataList()`: Validate removed associations
**Contract-Specific Item Processing:**
- `GetContractRelevantItems()`: Retrieve items relevant for contract billing
- `UpdatePriceForContractRelevantItem()`: Update pricing for contract items
- `SaveContractPositionCounter()`: Persist counter readings
- `SaveContractFreeCopies()`: Handle free copy allowances
### ContractSpecificLogic
**Location:** `src/backend/Centron.BL/Sales/Receipts/ContractLists/ContractSpecificLogic.cs`
This class implements contract-specific operations that are called by the main `ReceiptBL` through the `SpecificLogics` pattern.
### Related Business Logic Classes
#### ContractBL
**Location:** `src/backend/Centron.BL/Sales/CustomerAssets/Contracts/ContractBL.cs`
Handles broader contract asset management beyond the receipt functionality:
- Contract lifecycle management
- Device associations and management
- Contract analysis and reporting
- Integration with customer asset management
#### AutomaticFacturaBL.Contracts
**Location:** `src/backend/Centron.BL/Sales/CustomerAssets/AutomaticFactura/AutomaticFacturaBL.Contracts.cs`
Manages automated billing for contracts:
- Automatic invoice generation based on billing intervals
- RMM (Remote Monitoring and Management) integration
- Billing parameter calculation
- Multi-interval billing support
## Contract Workflows
### Contract Creation Process
1. **Initialize Contract**
```csharp
var contract = new ReceiptContract
{
CustomerI3D = customerId,
Date = DateTime.Now,
BillingIntervalKind = BillingIntervalKinds.Monthly,
BillingIntervalDuration = 1,
AutomatedBilling = true
};
```
2. **Configure Billing Parameters**
- Set billing intervals and calculation methods
- Define payment conditions and terms
- Configure contingent limits if applicable
3. **Add Contract Items**
- Products and services to be provided
- Pricing and quantity information
- Device associations for maintenance contracts
4. **Set Up Automation**
- Enable automated billing if required
- Configure renewal settings
- Set up monitoring and alerts
### Automated Billing Process
The automated billing system (`AutomaticFacturaBL`) processes contracts based on their billing intervals:
1. **Contract Evaluation**
- Check contracts due for billing
- Validate billing parameters and dates
- Verify customer and contract status
2. **Invoice Generation**
- Create invoice based on contract items
- Apply pricing rules and calculations
- Handle contingent deductions
3. **Post-Processing**
- Update contract billing dates
- Generate documents and notifications
- Update contingent balances
### Device Management Integration
For maintenance and service contracts:
1. **Device Association**
- Link devices to contracts through master data lists
- Track serial numbers and device information
- Monitor device status and warranty information
2. **Counter Reading Management**
- Collect meter readings for copiers/printers
- Calculate usage-based billing amounts
- Handle free copy allowances and overages
3. **Service Integration**
- Connect with RMM systems for automated data collection
- Process device monitoring data
- Generate alerts for maintenance requirements
## Configuration Options
### Billing Interval Configuration
**BillingIntervalKind Options:**
- `Daily`: Daily billing cycles
- `Monthly`: Monthly billing cycles
- `Quarterly`: Quarterly billing cycles
- `Yearly`: Annual billing cycles
**BillingIntervalDuration:**
- Number of intervals (e.g., 3 months for quarterly when kind is Monthly)
- Supports flexible billing periods
### Calculation Methods
**ContractCalculationKind Options:**
- Standard calculation methods for different contract types
- Custom calculation logic for specialized scenarios
**ContractNeedCalcKind:**
- Defines calculation requirements and triggers
- Controls when recalculations are needed
## Integration Points
### Customer Asset Management
- Integration with device and asset tracking systems
- Warranty and maintenance schedule management
- Service history and documentation
### Accounting System
- Automated journal entry generation for contract billing
- Revenue recognition for service contracts
- Contingent liability tracking
### External Systems
- **RMM Integration**: Remote monitoring and management systems
- **Device APIs**: Direct device communication for counter readings
- **Customer Portals**: Web-based contract management interfaces
## Performance Considerations
### Billing Performance
- **Batch Processing**: Large contract sets processed in batches
- **Parallel Processing**: Multiple contracts processed simultaneously where possible
- **Caching**: Frequently accessed contract data cached for performance
### Database Optimization
- **Indexed Relationships**: Foreign keys properly indexed
- **Partitioning**: Large contract tables partitioned by date ranges
- **Archive Strategy**: Old contract data archived for performance
## Security and Compliance
### Access Control
- **Role-Based Permissions**: Different access levels for contract operations
- **Branch Isolation**: Contracts accessible only to authorized branches
- **Customer Isolation**: Cross-customer data protection
### Audit Requirements
- **Change Tracking**: Complete audit trail for all contract modifications
- **Billing History**: Detailed logging of all billing operations
- **Compliance Reporting**: Support for regulatory reporting requirements
## Best Practices
### Contract Design
- **Clear Billing Intervals**: Use consistent and predictable billing cycles
- **Contingent Management**: Monitor contingent usage to prevent overruns
- **Device Integration**: Properly associate devices for accurate billing
### Development Guidelines
- **Use ContractBL**: Leverage existing contract business logic
- **Handle Contingents**: Always consider contingent impacts in calculations
- **Validate Intervals**: Ensure billing interval consistency
- **Test Automation**: Thoroughly test automated billing scenarios
### Troubleshooting
- **Billing Issues**: Check interval configuration and calculation settings
- **Device Problems**: Verify master data list associations
- **Performance Issues**: Review indexing and query optimization
- **Integration Failures**: Validate external system connections and data formats
@@ -0,0 +1,356 @@
# Receipt Search Architecture
This document explains how the receipt search system works in the c-entron.NET backend, including filter application, shared logic across receipt types, and how to add new searchable properties.
## Overview
The receipt search system provides a unified search interface across all receipt types (offers, orders, delivery lists, invoices, contracts, credit vouchers, pickup lists, and supplier receipts). It uses a configurable, extensible architecture that allows different receipt types to implement their own search logic while sharing common filtering capabilities.
## Architecture Components
### 1. REST API Layer
**Entry Point:** `CentronRestService.SearchReceiptsThroughPaging`
- **Location:** `src/webservice/Centron.Host/Services/CentronRestServiceParts/CentronRestService.Receipts.cs`
- **Method:** `SearchReceiptsThroughPaging(Request<SearchReceiptsThroughPagingRequest> request)`
```csharp
public Response<ReceiptSearchItemPagingDTO> SearchReceiptsThroughPaging(Request<SearchReceiptsThroughPagingRequest> request)
{
var result = this.Session.GetBL<ReceiptSearchWebServiceBL>().SearchReceipts(
this.GetLoggedInUserByTicket(request.Ticket),
request.Data.Filter,
request.Data.Page,
request.Data.EntriesPerPage);
return Response<ReceiptSearchItemPagingDTO>.FromBLResult(result);
}
```
### 2. Business Logic Layer
**Primary Class:** `ReceiptSearchWebServiceBL`
- **Location:** `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearchWebServiceBL.cs`
- **Responsibility:** Coordinates search operations, handles pagination, and manages user context
```csharp
public Result<ReceiptSearchItemPagingDTO> SearchReceipts(LoggedInUser user,
ReceiptSearchFilter filter, int page, int entriesPerPage)
{
var receipts = new ReceiptSearcher(this.Session).SearchReceipts(filter, user);
// Apply pagination and return results
ReceiptSearchItemPagingDTO pagingDTO = new ReceiptSearchItemPagingDTO()
{
Count = receipts.Count,
CurrentPage = page,
PageCount = (int)Math.Ceiling(receipts.Count/(decimal) entriesPerPage),
Result = receipts.OrderByDescending(o => o.Date).Skip((page - 1) * entriesPerPage).Take(entriesPerPage).ToList()
};
return Result<ReceiptSearchItemPagingDTO>.AsSuccess(pagingDTO);
}
```
### 3. Core Search Engine
**Primary Class:** `ReceiptSearcher`
- **Location:** `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearcher.cs`
- **Responsibility:** Executes searches across all receipt types using configuration-driven SQL generation
#### Key Features:
- **Multi-Receipt Type Support:** Searches across multiple receipt types simultaneously
- **Configuration-Driven:** Uses `ReceiptSearchConfiguration` classes for each receipt type
- **Raw SQL Execution:** Generates and executes optimized SQL queries for performance
- **User Context Handling:** Applies user-specific filters (web accounts, branches, permissions)
#### Search Process:
1. **Filter Preparation:** Adjusts filter based on user context (web accounts, permissions)
2. **Configuration Iteration:** Loops through all receipt type configurations
3. **SQL Generation:** Creates receipt-type-specific SQL queries with parameters
4. **Query Execution:** Executes raw SQL with 5-minute timeout
5. **Result Aggregation:** Combines results from all receipt types
6. **Result Sorting:** Orders by ObjectKind, then by Number descending
```csharp
public IList<ReceiptSearchItemDTO> SearchReceipts(ReceiptSearchFilter filter, LoggedInUser user)
{
var result = new List<ReceiptSearchItemDTO>();
this.PrepareFilterForWebAccounts(filter, user);
foreach (var configuration in this._receiptSearchConfigurations)
{
if (filter.ReceiptKinds == null || filter.ReceiptKinds.Count == 0 || filter.ReceiptKinds.Contains(configuration.ReceiptKind))
{
var query = this.CreateSqlStatementAndParameters(configuration, filter, user);
if (query == null) continue; // Receipt type doesn't support this filter
var sqlStatement = query.Item1;
var parameters = query.Item2;
var receipts = this._rawSqlAccessDAO.ExecuteQuery<ReceiptSearchItemDTO>(sqlStatement, parameters, timeout: TimeSpan.FromMinutes(5));
result.AddRange(receipts);
}
}
return result.OrderBy(f => f.ObjectKind).ThenByDescending(f => f.Number).ToList();
}
```
### 4. Filter Definition
**Class:** `ReceiptSearchFilter`
- **Location:** `src/backend/Centron.Interfaces/Sales/Receipts/ReceiptSearch/ReceiptSearchFilter.cs`
- **Responsibility:** Defines all available search criteria
#### Available Filter Properties:
- **Basic Search:** `SearchText`, `ReceiptNumber`, `ReceiptNumbers`
- **Date Range:** `DateFrom`, `DateTo`, `ChangedAfterDate`
- **Account/Customer:** `AccountI3D`, `AccountNumbers`, `AccountName`
- **Receipt Types:** `ReceiptKinds` (controls which receipt types to search)
- **Status:** `IncludeClosedReceipts`, `OnlyOwn`, `OnlyOwnBranch`
- **Financial:** `GrossPriceFrom`, `GrossPriceTo`, `PaymentConditionI3D`, `DeliveryConditionI3D`
- **Specialized:** `HourlySurchargeRateI3Ds`, `ContractKindI3Ds`, `ArticleI3Ds`, `CampaignI3D`
- **Items:** `SearchInReceiptItemText`, `ReceiptItemI3D`
- **Advanced:** `IsCart`, `OnlyNonCarts`, `IsDownPaymentInvoice`, `IsReceiptTemplate`
### 5. Configuration System
**Base Class:** `ReceiptSearchConfiguration`
- **Location:** `src/backend/Centron.BL/WebServices/Sales/Receipts/ReceiptSearch/ReceiptSearchConfiguration.cs`
- **Responsibility:** Defines the contract for receipt-type-specific search configurations
#### Configuration Properties:
- **Basic Properties:**
- `ReceiptKind`: Identifies the receipt type (CentronObjectKindNumeric)
- `GetBaseSelectStatement()`: Returns the base SELECT query for this receipt type
- `OnlyActiveWhereStatement`: SQL for filtering active receipts
- **Filter WHERE Statements:** Each filter property has a corresponding WHERE clause property:
- `AccountI3DWhereStatement`: SQL for filtering by account
- `ReceiptNumberWhereStatement`: SQL for filtering by receipt number
- `DateFromWhereStatement`, `DateToWhereStatement`: Date range filtering
- `SearchTextWhereStatement`: Full-text search implementation
- And many more...
- **Permission Integration:**
- `ShowRight`: Required right to view receipts of this type
- `OnlyOwnRight`: Right that restricts to user's own receipts
- `OnlyOwnBranchRight`: Right that restricts to user's branch
#### Receipt Type Configurations:
- `OfferReceiptSearchConfiguration` - Offers (AngKopf)
- `OrderReceiptSearchConfiguration` - Orders (AufKopf)
- `DeliveryListReceiptSearchConfiguration` - Delivery Lists (LiefKopf)
- `InvoiceReceiptSearchConfiguration` - Invoices (RechKopf)
- `ContractReceiptSearchConfiguration` - Contracts (VertragKopf)
- `CreditVoucherReceiptSearchConfiguration` - Credit Vouchers (GutKopf)
- `PickupListReceiptSearchConfiguration` - Pickup Lists (AbholKopf)
- Plus supplier variants for each type
## SQL Generation Process
The `ReceiptSearcher.CreateSqlStatementAndParameters` method builds SQL queries dynamically based on the provided filter and receipt type configuration:
### 1. Base Query Construction
```csharp
var baseSelect = configuration.GetBaseSelectStatement(filter);
var builder = new StringBuilder(baseSelect);
```
### 2. Filter Application
For each filter property that has a non-null/non-empty value:
```csharp
if (filter.AccountI3D != null && filter.AccountI3D > 0)
{
var accountWhereStatement = configuration.AccountI3DWhereStatement;
if (string.IsNullOrWhiteSpace(accountWhereStatement))
return null; // This receipt type doesn't support this filter
builder.AppendLine(accountWhereStatement);
parameters.Add(new NamedQueryParameter("AccountI3D", filter.AccountI3D, NHibernateUtil.Int32));
}
```
### 3. Permission Checks
```csharp
if (configuration.ShowRight.HasValue && !this._appRightsBL.HasRight(user.AppUser, configuration.ShowRight.Value))
{
return null; // User doesn't have permission to search this receipt type
}
```
### 4. Active Receipts Filter
```csharp
if (!filter.IncludeClosedReceipts)
{
builder.AppendLine(configuration.OnlyActiveWhereStatement);
}
```
## Adding New Filter Properties
To add a new searchable property (e.g., `IsHiddenInHelpdesk`), follow these steps:
### Step 1: Add to ReceiptSearchFilter
Add the new property to `ReceiptSearchFilter.cs`:
```csharp
[DataMember]
public bool? IsHiddenInHelpdesk { get; set; }
```
### Step 2: Add to Base Configuration
Add the corresponding WHERE statement property to `ReceiptSearchConfiguration.cs`:
```csharp
public virtual string IsHiddenInHelpdeskWhereStatement { get; } = null;
```
### Step 3: Update Receipt Type Configurations
For each receipt type that supports the new filter, implement the WHERE statement:
**Example for ContractReceiptSearchConfiguration:**
```csharp
public override string IsHiddenInHelpdeskWhereStatement => "AND AK.IsHiddenInHelpdesk = :IsHiddenInHelpdesk";
```
### Step 4: Update SQL Generation
Add the filter logic to `ReceiptSearcher.CreateSqlStatementAndParameters`:
```csharp
if (filter.IsHiddenInHelpdesk != null)
{
var isHiddenInHelpdeskWhere = configuration.IsHiddenInHelpdeskWhereStatement;
if (string.IsNullOrWhiteSpace(isHiddenInHelpdeskWhere))
return null; // This receipt type doesn't support this filter
builder.AppendLine(isHiddenInHelpdeskWhere);
parameters.Add(new NamedQueryParameter("IsHiddenInHelpdesk", filter.IsHiddenInHelpdesk.Value, NHibernateUtil.Boolean));
}
```
### Step 5: Database Schema Requirements
Ensure the underlying database tables and views include the new column:
- **Tables:** Add to base tables (e.g., `VertragKopf`) and version tables (e.g., `VertragKopfVersions`)
- **Views:** Update views (e.g., `Contracts`, `ContractVersions`) to include the new column
## Shared Logic Across Receipt Types
The system achieves code reuse through several mechanisms:
### 1. Configuration-Driven Architecture
- Common filter logic is implemented once in `ReceiptSearcher`
- Receipt-type-specific behavior is encapsulated in configuration classes
- New receipt types can be added by implementing a new configuration class
### 2. Base SELECT Queries
Each configuration provides a standardized SELECT query that returns `ReceiptSearchItemDTO` properties:
```csharp
public override string GetBaseSelectStatement(ReceiptSearchFilter filter)
{
return @"
SELECT
I3D = AK.I3D,
ObjectKind = 22,
Number = AK.Nummer,
Version = AK.Version,
Caption = AK.Zusatztext,
Date = AK.Datum,
Receiver = AK.Empfaenger,
AccountI3D = AK.KundenID,
-- ... more fields
FROM VertragKopf AK
-- ... joins
WHERE 1=1"; // Base WHERE clause for dynamic filter appending
}
```
### 3. Parameter Handling
- All configurations use named parameters (`:ParameterName`)
- Parameter types are consistently defined using NHibernate types
- Array parameters support IN clauses for multiple values
### 4. Permission Integration
- Rights checking is standardized across all receipt types
- Each configuration can define specific rights for viewing, own-only, and branch-only access
- Permission failures result in null queries (no results for that receipt type)
## Performance Considerations
### 1. Raw SQL Execution
- Uses raw SQL instead of LINQ/HQL for optimal performance
- Timeout set to 5 minutes for complex searches
- Transaction isolation levels configured for consistency
### 2. Pagination
- Results are paginated at the business logic level
- Sorting is applied after aggregation (may impact performance for large result sets)
- Consider implementing database-level pagination for very large datasets
### 3. Index Requirements
- Ensure all filterable columns are properly indexed
- Foreign key columns should have indexes
- Date range queries benefit from composite indexes
### 4. Query Optimization
- Each receipt type can optimize its base query independently
- Complex joins are handled in the base SELECT statement
- WHERE clauses are appended dynamically to avoid query plan issues
## Security and Permissions
### 1. User Context Handling
- Web account users are automatically filtered to their associated customer
- Employee users can be restricted by branch or ownership
- Permission checking prevents unauthorized access to receipt types
### 2. SQL Injection Prevention
- All user input is parameterized
- No dynamic SQL concatenation with user values
- Named parameters ensure type safety
### 3. Branch Isolation
- Branch-specific filtering can be enforced per receipt type
- User's branch context is automatically applied where configured
## Testing Strategies
### 1. Unit Testing
- Test individual configuration classes in isolation
- Mock filter scenarios for comprehensive coverage
- Verify SQL generation for all filter combinations
### 2. Integration Testing
- Test complete search workflows with real data
- Verify permission enforcement
- Test pagination and sorting behavior
### 3. Performance Testing
- Measure query execution times for large datasets
- Test timeout behavior under load
- Validate index effectiveness
## Future Enhancements
### 1. Elasticsearch Integration
- Consider moving to Elasticsearch for full-text search capabilities
- Maintain SQL for structured filtering
- Hybrid approach for optimal performance
### 2. Real-time Filtering
- Implement WebSocket-based real-time updates
- Consider caching frequently accessed search results
### 3. Advanced Search Features
- Saved search queries
- Search history
- Search result highlighting
## Conclusion
The receipt search system provides a robust, extensible architecture for searching across all receipt types in the c-entron.NET system. By following the established patterns for adding new filter properties, developers can easily extend search capabilities while maintaining consistency and performance across the entire system.
The configuration-driven approach ensures that new receipt types can be added with minimal impact on existing code, while the shared search logic provides consistency and maintainability across all receipt types.
@@ -0,0 +1,371 @@
# Receipts Backend Architecture
This document describes the generic architecture and components of the receipts system in the c-entron.NET backend, which provides a unified foundation for all receipt types including offers, orders, delivery lists, invoices, contracts, and credit vouchers.
## Overview
The receipts system follows a layered architecture pattern with a shared base implementation that is extended by specific receipt types. All receipt types inherit from common base classes and share fundamental operations while providing specialized functionality through their own business logic classes.
## Core Components
### Entity Layer
#### ReceiptBase Abstract Class
**Location:** `src/backend/Centron.Entities/Entities/Sales/Receipts/ReceiptBase.cs`
The `ReceiptBase` abstract class serves as the foundation for all receipt entities in the system. It inherits from `BaseEntity` and implements the `IReceiptBase` interface.
**Key Properties:**
- **Receipt Header Information:** Number, Date, Version, State, Editor
- **Branch Information:** BranchI3D, BranchOrigin
- **Currency Information:** CurrencyI3D, CurrencyFactor, CurrencyString, ExclusiveOfVAT
- **Contact Information:** Receiver, Phone, Fax, Email
- **Address Information:** AddressI3D, ContactPersonI3D, Street, PostOfficeBox, Zip, City, ContactName, CountryI3D
- **Audit Fields:** CreatedByI3D, CreatedAt, ChangedByI3D, ChangedAt, Application Version tracking
- **System Fields:** ConcurrencyControlGuid, CustomUpdateArticlePricesAndTexts
**Abstract Methods:**
- `ReceiptKind`: Returns the specific receipt type (CentronObjectKindNumeric)
- `GetReceiptItems()`: Returns all receipt items
- `SetReceiptItems()`: Sets receipt items collection
- `AddItem()`: Adds a new item to the receipt
- `RemoveItem()`: Removes an item from the receipt
#### Receipt Types Hierarchy
All receipt types extend `ReceiptBase` and follow a consistent pattern of entity classes, database tables, and views:
| Receipt Type | Entity Class | Database Table | Database View | Items Table | Items View |
|--------------|--------------|----------------|---------------|-------------|------------|
| **Offers** | `ReceiptOffer` | `AngKopf` | `Offers` | `AngPos` | `OfferItems` |
| **Orders** | `ReceiptOrder` | `AufKopf` | `Orders` | `AufPos` | `OrderItems` |
| **Delivery Lists** | `ReceiptDeliveryList` | `LiefKopf` | `DeliveryLists` | `LiefPos` | `DeliveryListItems` |
| **Invoices** | `ReceiptInvoice` | `RechKopf` | `Invoices` | `RechPos` | `InvoiceItems` |
| **Contracts** | `ReceiptContract` | `VertragKopf` | `Contracts` | `VertragPos` | `ContractItems` |
| **Credit Vouchers** | `ReceiptCreditVoucher` | `GutKopf` | `CreditVouchers` | `GutPos` | `CreditVoucherItems` |
| **Pickup Lists** | `ReceiptPickupList` | `AbholKopf` | `PickupLists` | `AbholPos` | `PickupListItems` |
**Entity Locations:**
- `src/backend/Centron.Entities/Entities/Sales/Receipts/Offers/ReceiptOffer.cs`
- `src/backend/Centron.Entities/Entities/Sales/Receipts/Orders/ReceiptOrder.cs`
- `src/backend/Centron.Entities/Entities/Sales/Receipts/DeliveryLists/ReceiptDeliveryList.cs`
- `src/backend/Centron.Entities/Entities/Sales/Receipts/Invoices/ReceiptInvoice.cs`
- `src/backend/Centron.Entities/Entities/Sales/Receipts/ContractLists/ReceiptContract.cs`
- `src/backend/Centron.Entities/Entities/Sales/Receipts/CreditVouchers/ReceiptCreditVoucher.cs`
- `src/backend/Centron.Entities/Entities/Sales/Receipts/PickupLists/ReceiptPickupList.cs`
## Database Schema
### Dual Layer Architecture: Tables and Views
The receipts system uses a dual-layer database architecture consisting of legacy tables and modern views. This design maintains backward compatibility while providing cleaner interfaces for the C# application.
#### Legacy Tables (German Names)
The original database tables use German naming conventions and contain historical structure:
**Header Tables (*Kopf):**
- `AngKopf` - Offer headers
- `AufKopf` - Order headers
- `LiefKopf` - Delivery list headers
- `RechKopf` - Invoice headers
- `VertragKopf` - Contract headers
- `GutKopf` - Credit voucher headers
- `AbholKopf` - Pickup list headers
**Position Tables (*Pos):**
- `AngPos` - Offer items
- `AufPos` - Order items
- `LiefPos` - Delivery list items
- `RechPos` - Invoice items
- `VertragPos` - Contract items
- `GutPos` - Credit voucher items
- `AbholPos` - Pickup list items
**Version Tables (*Versions):**
Each receipt type maintains version history through dedicated version tables:
- `AngKopfVersions` / `AngPosVersions` - Offer version history
- `AufKopfVersions` / `AufPosVersions` - Order version history
- `LiefKopfVersions` / `LiefPosVersions` - Delivery list version history
- `RechKopfVersions` / `RechPosVersions` - Invoice version history
- `VertragKopfVersions` / `VertragPosVersions` - Contract version history
- `GutKopfVersions` / `GutPosVersions` - Credit voucher version history
- `AbholKopfVersions` / `AbholPosVersions` - Pickup list version history
#### Modern Views (English Names)
For C# application compatibility, cleaner views with English names are used:
**Header Views:**
- `Offers` - Clean view of AngKopf
- `Orders` - Clean view of AufKopf
- `DeliveryLists` - Clean view of LiefKopf
- `Invoices` - Clean view of RechKopf
- `Contracts` - Clean view of VertragKopf
- `CreditVouchers` - Clean view of GutKopf
- `PickupLists` - Clean view of AbholKopf
**Item Views:**
- `OfferItems` - Clean view of AngPos
- `OrderItems` - Clean view of AufPos
- `DeliveryListItems` - Clean view of LiefPos
- `InvoiceItems` - Clean view of RechPos
- `ContractItems` - Clean view of VertragPos
- `CreditVoucherItems` - Clean view of GutPos
- `PickupListItems` - Clean view of AbholPos
**Version Views:**
- `OfferVersions` / `OfferItemVersions` - Offer version views
- `OrderVersions` / `OrderItemVersions` - Order version views
- `DeliveryListVersions` / `DeliveryListItemVersions` - Delivery list version views
- `InvoiceVersions` / `InvoiceItemVersions` - Invoice version views
- `ContractVersions` / `ContractItemVersions` - Contract version views
- `CreditVoucherVersions` / `CreditVoucherItemVersions` - Credit voucher version views
- `PickupListVersions` / `PickupListItemVersions` - Pickup list version views
### Shared Logging Infrastructure
#### AnlageLog Table
**Purpose:** Centralized logging for all receipt types
**Structure:** Shared table with receipt type differentiation
**Key Columns:**
- `AnlageI3D` - References the specific receipt's I3D
- `AnlageArt` - Receipt type identifier (corresponds to ObjectKind)
- Log entry details and timestamps
**AnlageArt Values:**
- `1` = Offer
- `2` = Order
- `3` = Delivery List
- `4` = Invoice
- `5` = Pickup List
- `6` = Credit Voucher
- `22` = Contract
This pattern (`ObjectI3D` + `ObjectKind` / `AnlageI3D` + `AnlageArt`) is used throughout the system for shared references across different entity types.
### Schema Maintenance
#### Version Tables: 1:1 Copies of Original Tables
**Critical Requirement:** Version tables (`*KopfVersions`, `*PosVersions`) are exact 1:1 copies of their corresponding original tables. This means **every column that exists in the base table must also exist in the version table** with identical structure and data types.
**Version Table Structure:**
- Contains all columns from the original table
- Excludes certain system columns (I3D, OriginalI3D)
- Adds `OriginalI3D` column to reference the original record
- Adds `KopfVersionsI3D` column (for *Pos version tables) to reference the header version
**Versioning Implementation Example:**
The versioning mechanism (as seen in `AssetHeadDAO.SaveAssetVersion`) works by:
```sql
-- Copy header record to version table
INSERT INTO AngKopfVersions (all_columns_except_I3D, OriginalI3D)
SELECT all_columns_except_I3D, I3D AS OriginalI3D
FROM AngKopf
WHERE I3D = @receiptId
-- Copy all item records to version table
INSERT INTO AngPosVersions (all_columns_except_I3D, OriginalI3D, KopfVersionsI3D)
SELECT all_columns_except_I3D, I3D AS OriginalI3D, @headerVersionId AS KopfVersionsI3D
FROM AngPos
WHERE AngKopfI3D = @receiptId
```
#### Adding New Columns - Complete Checklist
When adding new columns to receipts, **all** of the following must be updated:
1. **Base Table:** Add column to the legacy table (e.g., `AngKopf`)
2. **Version Table:** Add the **identical column** to the version table (e.g., `AngKopfVersions`)
3. **Update Views:** Modify both the main view (e.g., `Offers`) and version view (e.g., `OfferVersions`) to include the new column
4. **Entity Classes:** Add the property to the C# entity class (e.g., `ReceiptOffer`) and version entity if applicable
5. **Mapping Classes:** Update NHibernate mapping classes for ORM functionality
6. **Temporary Legacy Entities:** Add the property to the matching `*Kopf` / `*Pos` temporary entity under `src/backend/Centron.Entities/Entities/DbEntities/`
7. **Temporary Entity Mappings:** Add the mapping to the matching class under `src/backend/Centron.DAO/Mappings/TemporaryEntities/`
8. **SaveReceipt Repository:** Copy the value in the receipt-type-specific `SaveReceipt*Repository` (`SynchronizeReceiptData` for header fields, `SynchronizeReceiptItemData` for item fields)
9. **DTOs and Interfaces:** Add the property to webservice DTOs and relevant receipt interfaces when the value crosses BL/WebService/UI boundaries
10. **Version Views:** Ensure version views (`OfferVersions`, `OfferItemVersions`) include the new column
**⚠️ Critical Warning:** Forgetting to add a column to the version table will cause runtime errors when the versioning system attempts to copy records. The `DoGetFieldList()` method dynamically generates field lists, so missing columns in version tables will break the INSERT statements.
**Critical Save Warning:** The normal NHibernate receipt entity mapping is not the only persistence path. Receipt saves go through legacy `SaveReceipt*Repository` classes, which synchronize the modern receipt entities into temporary legacy table entities (`RechKopf`, `RechPos`, `LiefKopf`, `LiefPos`, etc.) before writing to the database. If a new field is only added to the modern entity/view mapping but not to the temporary entity, temporary mapping, and `SaveReceipt*Repository`, the value may load correctly from the view but will not be persisted on save.
#### Version Table Maintenance Process
**For Header Tables (*Kopf → *KopfVersions):**
1. Add column to base table: `ALTER TABLE AngKopf ADD NewColumn datatype`
2. Add identical column to version table: `ALTER TABLE AngKopfVersions ADD NewColumn datatype`
3. Update corresponding views to include the new column
**For Item Tables (*Pos → *PosVersions):**
1. Add column to base table: `ALTER TABLE AngPos ADD NewColumn datatype`
2. Add identical column to version table: `ALTER TABLE AngPosVersions ADD NewColumn datatype`
3. Update corresponding views to include the new column
This strict 1:1 correspondence ensures that the automatic versioning system (`AssetHeadDAO.SaveAssetVersion` and similar methods) can create complete snapshots of receipt states for audit trails and change tracking.
### Business Logic Layer
#### ReceiptBL - Core Business Logic
**Location:** `src/backend/Centron.BL/Sales/Receipts/ReceiptBL.cs`
The `ReceiptBL` class provides the central business logic for all receipt operations. It contains over 10,000 lines of code handling comprehensive receipt management functionality.
**Key Responsibilities:**
- **CRUD Operations:** Generic methods for loading, searching, and saving receipts
- **Receipt Processing:** State management, workflow processing, validation
- **Item Management:** Adding, updating, removing receipt items
- **Price Calculations:** Tax calculations, discounts, currency conversions
- **Document Generation:** PDF generation, printing, email sending
- **Integration:** Connection with accounting systems, warehousing, customer management
- **Workflow Management:** Approval processes, state transitions
- **Reporting:** Export functionality (Excel, PDF)
**Core Methods:**
- `GetReceiptByI3D<T>(int receiptI3D)`: Generic receipt retrieval
- `GetReceipts<T>(IReceiptFilter filter)`: Search receipts with filtering
- `SaveReceipt<T>(T receipt)`: Generic receipt saving
- `DeleteReceipt(int receiptI3D, CentronObjectKindNumeric receiptKind)`: Receipt deletion
- `ExportReceiptToExcel()`: Excel export functionality
#### SpecificLogics Pattern
The `ReceiptBL` utilizes a `SpecificLogics` helper class that delegates specialized operations to receipt-type-specific business logic classes:
- **ContractSpecificLogic** for contracts
- **InvoiceSpecificLogic** for invoices
- **OrderSpecificLogic** for orders
- And similar classes for other receipt types
### Data Access Layer
#### Repository Pattern
Each receipt type has its own repository for data persistence:
- **SaveReceiptContractRepository** → `VertragKopf` & `VertragPos` tables
- **SaveReceiptInvoiceRepository** → `RechKopf` & `RechPos` tables
- **SaveReceiptOfferRepository** → `AngKopf` & `AngPos` tables
- **SaveReceiptOrderRepository** → `AufKopf` & `AufPos` tables
- **SaveReceiptDeliveryListRepository** → `LiefKopf` & `LiefPos` tables
- **SaveReceiptCreditVoucherRepository** → `GutKopf` & `GutPos` tables
- **SaveReceiptPickupListRepository** → `AbholKopf` & `AbholPos` tables
- And similar repositories for supplier receipt types
These repositories are a legacy persistence layer between the normal NHibernate receipt entities and the database. They create or update temporary table entities and explicitly assign many properties. When adding a persisted receipt header or item field, update the repository method that synchronizes that level:
- Header field: `SynchronizeReceiptData(...)`
- Item field: `SynchronizeReceiptItemData(...)`
Do not rely on AutoMapper or the modern NHibernate entity mapping for this save path. End-to-end tests are the preferred safety net for new receipt fields because they execute the database script, save through `ReceiptWebServiceBL.SaveReceipt(...)`, reload the receipt, and can assert the raw legacy table values.
#### Database Table Structure
All receipt types follow a consistent two-table pattern:
**Header Tables (*Kopf):**
- Contains receipt-level information (customer, dates, totals, etc.)
- Inherits from `ReceiptTable` base structure
- Primary key: `I3D` (identity column)
- Common audit fields: CreatedAt, ChangedAt, CreatedByI3D, ChangedByI3D
**Position Tables (*Pos):**
- Contains individual line items/positions
- Foreign key reference to header table (*KopfI3D)
- Article information, quantities, prices, and item-specific data
- Primary key: `I3D` (identity column)
- Position number: `Pos` (for ordering)
## Common Workflows
### Receipt Creation Process
1. **Initialize Receipt Entity** - Create new receipt instance with default values
2. **Set Header Information** - Customer, addresses, dates, currency
3. **Add Receipt Items** - Products/services with quantities and prices
4. **Calculate Totals** - Tax calculations, discounts, final amounts
5. **Validate Business Rules** - Check inventory, credit limits, etc.
6. **Save to Database** - Persist header and position records
7. **Generate Document** - Create PDF, send emails if required
### Receipt State Management
Receipts progress through defined states:
- **Draft** - Initial creation, can be freely modified
- **Released** - Approved for processing, limited modifications
- **Processed** - Finalized, minimal changes allowed
- **Cancelled** - Marked as cancelled, read-only
### Item Management
- **Dynamic Item Addition** - Items can be added at any time during draft state
- **Price Calculation** - Automatic recalculation of totals when items change
- **Inventory Integration** - Real-time stock checking and updates
- **Article Linking** - Connection to master article data
## Integration Points
### Customer Management
- Customer data integration for addresses and contact information
- Credit limit checking and payment term assignment
- Customer-specific pricing and discount structures
### Inventory System
- Real-time stock level checking
- Automatic inventory updates on receipt processing
- Serial number and barcode tracking
### Accounting System
- Automatic journal entry generation
- Tax calculation and reporting
- Integration with financial reporting systems
### Document Management
- PDF generation for all receipt types
- Email delivery capabilities
- Document archiving and retrieval
## Extensibility
### Adding New Receipt Types
To add a new receipt type:
1. **Create Entity Classes** - Header and position entities extending base classes
2. **Implement Business Logic** - Specific BL class with type-specific operations
3. **Create Database Tables** - Following the *Kopf/*Pos naming convention
4. **Add Repository Classes** - For data persistence operations
5. **Register with SpecificLogics** - Enable integration with core ReceiptBL
### Customization Points
- **Custom Fields** - Additional properties on receipt entities
- **Business Rules** - Custom validation and processing logic
- **Workflow Extensions** - Additional states and transitions
- **Integration Hooks** - Custom external system connections
## Performance Considerations
### Database Optimization
- **Indexed Foreign Keys** - All *KopfI3D references are indexed
- **Pagination Support** - Large result sets handled via paging
- **Query Optimization** - Efficient queries for common operations
### Memory Management
- **Lazy Loading** - Receipt items loaded on demand
- **Batch Operations** - Bulk processing for multiple receipts
- **Caching Strategy** - Frequently accessed data cached appropriately
## Security
### Access Control
- **User-based Permissions** - Role-based access to receipt functions
- **Branch Isolation** - Users can only access receipts from their branches
- **Audit Trail** - Complete tracking of all receipt changes
### Data Protection
- **Concurrency Control** - GUID-based optimistic locking
- **Data Validation** - Input validation and sanitization
- **Transaction Management** - ACID compliance for all operations
## Best Practices
### Development Guidelines
- **Use Generic Methods** - Leverage ReceiptBL generic operations where possible
- **Follow Inheritance Patterns** - Extend base classes rather than duplicating code
- **Implement Proper Error Handling** - Use try-catch blocks and meaningful error messages
- **Maintain Audit Trails** - Always populate CreatedBy/ChangedBy fields
### Testing Considerations
- **Unit Tests** - Test business logic methods in isolation
- **Integration Tests** - Test complete receipt workflows
- **Database Tests** - Verify data persistence and retrieval
- **Performance Tests** - Ensure acceptable response times under load
@@ -0,0 +1,403 @@
# Anmelden mit Microsoft — Technische Anleitung
## Überblick
Die Funktion "Anmelden mit Microsoft" nutzt **Microsoft Entra ID (Azure AD)** über **OpenID Connect** mit der **MSAL-Bibliothek**. Der Client holt ein ID-Token von Microsoft, schickt es an die c-entron API, die es validiert, den User per Entra Object ID (`oid`-Claim) nachschlägt und ein c-entron Session-Ticket zurückgibt.
---
## Kompletter Flow
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Client App │ │ c-entron API │ │ Microsoft │
│ │ │ (Web Service) │ │ Entra ID │
└────────┬────────┘ └────────┬─────────┘ └────────┬────────┘
│ │ │
1. GET /config/jwt ─────────>│ │
│<── { Authority, │ │
│ Audience, │ │
│ Enabled } │ │
│ │ │
2. MSAL: Token von Microsoft holen ──────────────────> │
│<──────────────────────────── AuthenticationResult│
│ (enthält IdToken) │ │
│ │ │
3. POST /jwt/login ─────────>│ │
│ Authorization: │ │
│ Bearer {IdToken} │ 4. JWT Middleware │
│ Body: { Application, │ validiert Token │
│ AppVersion, │ (Signatur, Issuer, │
│ Device } │ Audience, Lifetime)│
│ │ │
│ │ 5. oid-Claim extrahieren│
│ │ → User in DB suchen │
│ │ (OpenIdConnect- │
│ │ SubjectIdentifier) │
│ │ │
│ │ 6. Ticket erstellen │
│<── "ticket-string" │ │
│ │ │
7. Ticket für alle │ │
weiteren API-Calls nutzen │ │
```
---
## Schritt 1: JWT-Konfiguration abrufen
Anonymer Endpoint — kein Auth nötig.
```http
GET {baseUrl}/config/jwt
```
**Response:**
```json
{
"Authority": "https://login.microsoftonline.com/{tenant-id}/v2.0",
"Audience": "{azure-ad-client-id}",
"Enabled": true
}
```
| Feld | Bedeutung |
|---|---|
| `Authority` | OpenID Connect Authority URL (Entra ID Tenant) |
| `Audience` | Azure AD Application (Client) ID |
| `Enabled` | `true` wenn beide Werte konfiguriert sind |
Wenn `Enabled == false` → OIDC ist nicht konfiguriert, Abbruch.
---
## Schritt 2: ID-Token von Microsoft Entra ID holen (MSAL)
Mit den Werten aus Schritt 1 wird ein MSAL Public Client konfiguriert:
- **Client ID** = `Audience` aus der JWT-Konfiguration
- **Authority** = `Authority` aus der JWT-Konfiguration
- **Scopes** = `["openid", "profile"]`
- **Broker** = Windows WAM (optional, für SSO mit Windows-Anmeldung)
**Ergebnis:** Ein `AuthenticationResult` mit einem `IdToken` (JWT).
> **Wichtig:** Es wird das **ID-Token** verwendet, nicht das Access-Token. Die Scopes `openid` und `profile` reichen aus.
---
## Schritt 3: ID-Token gegen c-entron Ticket tauschen
```http
POST {baseUrl}/jwt/login
Authorization: Bearer {microsoft-id-token}
Content-Type: application/json
{
"Application": "{verschlüsselte-lizenz-GUID}",
"AppVersion": "2.1.2605.636",
"Device": "MACHINE-NAME"
}
```
| Feld | Typ | Bedeutung |
|---|---|---|
| `Application` | string | Verschlüsselte Lizenz-GUID der Anwendung |
| `AppVersion` | string | Version der Client-Anwendung |
| `Device` | string | Gerätename (`Environment.MachineName`) |
**Response (Erfolg):** `200 OK`
```
ticket-hash-string
```
Der Response-Body enthält direkt den Ticket-String (plain text, kein JSON).
**Response (Fehler):** `400 Bad Request` oder `401 Unauthorized`
---
## Schritt 4: Ticket für weitere API-Calls verwenden
Das erhaltene Ticket wird für alle weiteren c-entron API-Aufrufe als Authentifizierung verwendet.
---
## Was auf dem Server passiert
### JWT-Validierung (Middleware)
Die ASP.NET Core JWT Bearer Middleware:
1. Lädt das OpenID Connect Discovery Document von `{Authority}/.well-known/openid-configuration`
2. Holt die Signing Keys vom JWKS-Endpoint
3. Validiert: Signatur, Issuer, Audience, Lifetime
4. Befüllt `HttpContext.User` mit den Claims
### User-Lookup
```csharp
// oid-Claim = Microsoft Entra Object ID
var oid = identity.Claims.FirstOrDefault(c => c.Type == "oid")?.Value;
// User in der Datenbank suchen
var user = dao.GetEntity(where => where.OpenIdConnectSubjectIdentifier == oid);
```
Die Spalte `OpenIdConnectSubjectIdentifier` in der Tabelle `Sichbenu` (AppUser) enthält die Microsoft Entra Object ID des verknüpften Benutzers.
### Ticket-Erstellung
```csharp
var salt = CryptoUtils.CreateSalt(32);
var ticketId = CryptoUtils.CreatePasswordHash(deviceId, salt); // SHA-basierter Hash
var expireDate = DateTime.Now.AddMinutes(30); // 30 Min Gültigkeit
// INSERT INTO Ticket (TicketId, ExpiryDate, ApplicationID, LicenseGUID, UserI3D, DeviceId)
```
---
## Voraussetzungen
### Azure AD App Registration
| Einstellung | Wert |
|---|---|
| Application (Client) ID | → wird als `JwtAudience` in c-entron gespeichert |
| Authority URL | `https://login.microsoftonline.com/{tenant-id}/v2.0` → `JwtAuthority` |
| Redirect URI | MSAL Default für Public Client Apps |
| Token-Typ | ID-Token (nicht Access-Token) |
### c-entron Konfiguration
| Was | Wo | Setting ID |
|---|---|---|
| `JwtAuthority` | ApplicationSettings | 10351 |
| `JwtAudience` | ApplicationSettings | 10352 |
| `SystemAuthenticationMethod` | ApplicationSettings | 10360 (0=Any, 3=OpenIdConnect) |
| OpenIDConnect-Lizenz | Lizenztabelle | `AB4181F6-EF3B-4763-B29B-F5D0603311F7` |
### User-Verknüpfung
Jeder c-entron User braucht seine **Microsoft Entra Object ID** in der Spalte `OpenIdConnectSubjectIdentifier` (Tabelle `Sichbenu`). Verknüpfung über:
- **Self-Service:** `POST /jwt/connect_accounts`
- **Admin-Zuweisung:** WPF-UI unter "Persönliche Einstellungen"
---
## API-Endpoints im Überblick
| Endpoint | Methode | Auth | Zweck |
|---|---|---|---|
| `/config/jwt` | GET | Keine | JWT-Konfiguration abrufen |
| `/config/jwt` | PATCH | c-entron Ticket | JWT-Konfiguration ändern |
| `/jwt/login` | POST | Bearer (ID-Token) | **ID-Token → c-entron Ticket** |
| `/jwt/connect_accounts` | POST | Bearer (ID-Token) | Microsoft-Konto mit c-entron verknüpfen |
---
## Implementierungsbeispiel: OAuth-Token gegen c-entron Ticket tauschen
Minimales Beispiel für eine externe Applikation, die bereits ein Microsoft ID-Token hat und dieses gegen ein c-entron Ticket tauschen möchte.
### C# (.NET)
```csharp
using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.Identity.Client;
public class CentronOAuthClient
{
private readonly HttpClient _httpClient;
private readonly string _centronBaseUrl;
public CentronOAuthClient(string centronBaseUrl)
{
_centronBaseUrl = centronBaseUrl.TrimEnd('/');
_httpClient = new HttpClient { BaseAddress = new Uri(_centronBaseUrl) };
}
// ──────────────────────────────────────────────────────
// Schritt 1: JWT-Konfiguration vom c-entron Server holen
// ──────────────────────────────────────────────────────
public async Task<JwtConfiguration> GetJwtConfigurationAsync()
{
var response = await _httpClient.GetAsync("/config/jwt");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<JwtConfiguration>();
}
// ──────────────────────────────────────────────────────
// Schritt 2: Microsoft ID-Token über MSAL holen
// ──────────────────────────────────────────────────────
public async Task<string> AcquireMicrosoftIdTokenAsync(JwtConfiguration config)
{
var app = PublicClientApplicationBuilder
.Create(config.Audience) // Client ID aus c-entron Config
.WithAuthority(config.Authority) // Authority aus c-entron Config
.WithDefaultRedirectUri()
.Build();
string[] scopes = ["openid", "profile"];
AuthenticationResult result;
var accounts = await app.GetAccountsAsync();
var account = accounts.FirstOrDefault();
try
{
// Silent: aus Cache oder SSO
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Interaktiv: Microsoft Login-Dialog zeigen
result = await app.AcquireTokenInteractive(scopes)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
return result.IdToken; // WICHTIG: IdToken, nicht AccessToken!
}
// ──────────────────────────────────────────────────────
// Schritt 3: ID-Token gegen c-entron Ticket tauschen
// ──────────────────────────────────────────────────────
public async Task<string> ExchangeTokenForTicketAsync(
string microsoftIdToken,
string applicationGuid,
string appVersion)
{
var request = new HttpRequestMessage(HttpMethod.Post, "/jwt/login")
{
Content = JsonContent.Create(new
{
Application = applicationGuid,
AppVersion = appVersion,
Device = Environment.MachineName
})
};
request.Headers.Add("Authorization", $"Bearer {microsoftIdToken}");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Login fehlgeschlagen: {response.StatusCode} — {error}");
}
// Response ist der Ticket-String (plain text)
return await response.Content.ReadAsStringAsync();
}
// ──────────────────────────────────────────────────────
// Kompletter Flow: Alles zusammen
// ──────────────────────────────────────────────────────
public async Task<string> LoginWithMicrosoftAsync(
string applicationGuid,
string appVersion)
{
// 1. JWT-Konfiguration abrufen
var config = await GetJwtConfigurationAsync();
if (!config.Enabled)
throw new Exception("OpenID Connect ist auf diesem Server nicht aktiviert.");
// 2. Microsoft ID-Token holen
var idToken = await AcquireMicrosoftIdTokenAsync(config);
// 3. Token gegen c-entron Ticket tauschen
var ticket = await ExchangeTokenForTicketAsync(idToken, applicationGuid, appVersion);
return ticket;
}
}
// ──────────────────────────────────────────────────────
// DTOs
// ──────────────────────────────────────────────────────
public class JwtConfiguration
{
public string Authority { get; set; }
public string Audience { get; set; }
public bool Enabled { get; set; }
}
```
### Verwendung
```csharp
var client = new CentronOAuthClient("https://mein-centron-server.example.com");
// Kompletter Flow
var ticket = await client.LoginWithMicrosoftAsync(
applicationGuid: "{verschlüsselte-lizenz-guid}",
appVersion: "1.0.0.0"
);
Console.WriteLine($"c-entron Ticket: {ticket}");
// → Ticket für alle weiteren API-Calls verwenden
```
### Minimales Beispiel: Nur Token-Tausch (wenn ID-Token bereits vorhanden)
```csharp
// Wenn du bereits ein Microsoft ID-Token hast (z.B. aus einer anderen Auth-Bibliothek):
var client = new CentronOAuthClient("https://mein-centron-server.example.com");
var ticket = await client.ExchangeTokenForTicketAsync(
microsoftIdToken: "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
applicationGuid: "{verschlüsselte-lizenz-guid}",
appVersion: "1.0.0.0"
);
```
### cURL-Beispiel
```bash
# 1. JWT-Konfiguration abrufen
curl -s https://mein-centron-server.example.com/config/jwt
# 2. ID-Token gegen Ticket tauschen (ID-Token aus MSAL o.ä.)
curl -X POST https://mein-centron-server.example.com/jwt/login \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOi..." \
-H "Content-Type: application/json" \
-d '{
"Application": "{verschlüsselte-lizenz-guid}",
"AppVersion": "1.0.0.0",
"Device": "MEIN-PC"
}'
# Response: ticket-hash-string (plain text)
```
### NuGet-Pakete
```xml
<PackageReference Include="Microsoft.Identity.Client" Version="4.*" />
<!-- Optional, für Token-Cache-Persistierung: -->
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.*" />
<!-- Optional, für Windows WAM Broker (SSO): -->
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.*" />
```
---
## Relevante Quellcode-Dateien
| Schicht | Datei | Rolle |
|---|---|---|
| WPF View | `src/centron/Centron.WPF.UI/Modules/Administration/Connections/LoginDialogView.xaml` | Button "Anmelden mit Microsoft" |
| WPF ViewModel | `src/centron/Centron.WPF.UI/Modules/Administration/Connections/LoginDialogViewModel.cs` | Command-Handler |
| Client MSAL | `src/centron/Centron.WPF.UI/Services/WebServices/CentronWebServiceConnection.cs` | MSAL-Konfiguration, Token-Akquise, Token-Tausch |
| HTTP Client | `src/webservice/Centron.WebServices.Core/HttpClients/JwtAuthClient.cs` | `POST /jwt/login` mit Bearer-Header |
| Server Middleware | `src/webservice/Centron.Host/CentronHost.cs` | JWT Bearer Validierung |
| Server Controller | `src/webservice/Centron.Controllers/Controllers/Unversioned/JwtAuthController.cs` | `/jwt/login` Endpoint |
| Auth Factory | `src/backend/Centron.BL/Administration/Logins/Auth/AuthenticatorFactory.cs` | Routing zum OIDC-Authenticator |
| OIDC Authenticator | `src/backend/Centron.BL/Administration/Logins/Auth/OpenIdConnectAuthenticator.cs` | User-Lookup per `oid`-Claim |
| Ticket-Erstellung | `src/backend/Centron.BL/Administration/Logins/Auth/Authenticator.cs` + `TicketBL.cs` | Ticket generieren & speichern |
| Account Linking | `src/backend/Centron.BL/Administration/Logins/Auth/OpenIdConnectAccountConnector.cs` | Microsoft ↔ c-entron verknüpfen |
| JWT Config Model | `src/webservice/Centron.WebServices.Core/RestRequests/JwtConfiguration.cs` | Authority + Audience DTO |
| Login Request Model | `src/webservice/Centron.WebServices.Core/RestRequests/JwtLoginRequest.cs` | Application + AppVersion + Device |
| Settings IDs | `src/backend/Centron.Interfaces/Administration/Settings/ApplicationSettingID.cs` | JwtAuthority=10351, JwtAudience=10352 |
| Lizenz-GUIDs | `src/backend/Centron.Interfaces/Administration/Logins/LicenseGuids.cs` | OpenIDConnectAuthentication |
@@ -0,0 +1,25 @@
# Developer security
We have some security in place, to protect developers from accidentally doing something bad, for example sending mails to real customers.
These safeguards can't protect you from all accidents, so you still have to be careful when you're sending mails or calling external APIs.
But in most cases, you should be protected and safe.
All of these safeguards are configured in the `DeveloperSecurity.cs` file.
Check out the code in that file to get a detailed understanding of how it works and what it does.
## Sending emails
To protect us from accidentally sending emails to actual customer email addresses, there are some safeguards in place.
> :exclamation: These safeguards are only active in DEBUG-builds of the c-entron.NET :exclamation:
If you manually create a RELEASE-build of the c-entron.NET and send emails with it, it's up to you to as the developer to make sure that you don't send any emails to actual customer email addresses.
In DEBUG-builds all **external email addresses** will get replaced by `test@nexoware.com`.
The **internal email addresses** will not get modified at all.
> A email address is considered **internal** when it ends with `nexoware.com`.
> Every other email address is considered **external**.
If you want to disable this behavior (for example when you're trying to test your email sending code), you can manually edit the `AllowSendingEmailToExternalAddresses` property in the `DeveloperSecurity.cs` file.
@@ -0,0 +1,102 @@
# How does our licensing work?
## What is a license?
Our licenses are just simple GUIDs.
There is a GUID for `c-entron.NET`, another one for `Service-Board`, and again a different one for `Outlook Add-In`, etc.
But also **single features** can have their own GUID.
For example the `branch functionality`, or the `report server`, etc.
Those are all licenses a customer can potentially **have** or **NOT have**.
Additionally, each license can have a `count`, a `valid until date` and a `valid until version`, with either a `real value` or `unlimited`.
The license also has a name for display purposes only, technically the name is not relevant at all.
So, to summarize it again, for each license we have the following possible values:
* Does he have the license (`GUID`)?
* How many of them (`count`)?
* Until when is this license valid (`valid until date`)?
* Until which version is the license valid (`valid until version`)?
## How does the c-entron.NET and c-entron Web-Service work with those?
The c-entron.NET and c-entron Web-Service (also Riverbird Web-Service) generally differentiate between `Applications` and `Only Licenses`.
`Only Licenses` are the **single features** like the `branch functionality` or the `report server`.
They are all listed in the `LicenseGuids.cs` file.
Actually, every single license that we have is listed in the `LicenseGuids.cs` file, no matter if it's just a single license that we check for, or a `Application`.
`Applications` on the other hand are all licenses that are allowed to `Login` at the web-service.
They are all listed in the `ApplicationKind.cs` file.
Every entry in that file is allowed to `Login` at the web-service.
For all of those the `count`, `valid until date` and `valid until version` values are automatically checked and validated.
## Which licenses do we have?
The single source of truth for all our available licenses is the license-server.
You can use the `c-entron Office` tool to look at all the licenses, but usually that is not required.
We try to keep the `LicenseGuids.cs` file in sync with the license-server, to make it easier to check for licenses.
## I need a new license, what do I do?
At first, make sure we really have a `NEW THING` that needs to be separately licensed?
When you're sure, go to your development leader of your choice, and ask him to create this new license for you.
He will give you the `GUID` that represents this license.
Remember: Our licenses are just simple GUIDs.
You should add this new GUID to the `LicenseGuids.cs` file. And if it's required to `Login` at the web-service with it (in case for a new product), also add it to the `ApplicationKind.cs` file.
## Great, I got the GUID, how do I check for the license now?
If your license is a `Application` like we talked about above, then you might not need to do anything.
Just adding it the the `ApplicationKind.cs` is enough to allow you to login at the web-service, and have the `count`, `valid until date` and `valid until version` validated for you.
If you only have a simple boring license that you want to check, to show or hide a module in the c-entron.NET (like the `password manager` for example), or show some UI to the user, or enable extra functionality in any other way, you can use the `LicenseManager` to do that.
Let me just show you some code examples.
### Check if the customer has a license
Again, you can use this to hide or show UI, a module, or enable some features for a customer only.
```csharp
bool hasPasswordManager = LicenseManager.Instance.HasLicense(LicenseGuids.PasswordManager); // This is the important line
if (hasPasswordManager)
this.ShowPasswordManagerUI();
```
### Check the `count` of the license
This can for example be used, when we license something on a HOW MANY base.
Right now we do it for example for the `MyDay Import`.
This module can be used to import from external tools into c-entron for the `MyDay` module.
And we sell every import separately.
That means, a customer could buy 3 imports, and then would be allowed to configure 3 different imports.
On a more crazy, made up example, we could use this functionality to license how many articles the customer is allowed to create in the c-entron.
```csharp
Result<int?> myDayImportCountResult = LicenseManager.Instance.GetLicenseCount(LicenseGuids.MyDayImports); // This is the important line
if (myDayImportCountResult.Status == ResultStatus.Error)
{
// The customer does NOT have a license for LicenseGuids.MyDayImports
// Consider checking with LicenseManager.Instance.HasLicense first if the customer even has the license
}
else if (myDayImportCountResult.Status == ResultStatus.Success)
{
// The customer does have a license for LicenseGuids.MyDayImports, that's great!
// Lets now check how MANY of them he does have
// Again, this checks the COUNT of the license
int? licenseCount = myDayImportCountResult.Data;
if (licenseCount == null)
{
// The COUNT is UNLIMITED
}
else
{
// The COUNT is the number that is in licenseCount right now
// If the customer is allowed to use 3 MyDayImports, then licenseCount would be 3 here
}
}
```
@@ -0,0 +1,582 @@
# ZUGFeRD / XRechnung Feldzuordnung
Diese Dokumentation erklärt, welche Felder aus c-entron in die ZUGFeRD/XRechnung XML-Datei übernommen werden.
## Übersicht
c-entron erstellt beim Export von Rechnungen und Gutschriften automatisch eine ZUGFeRD/XRechnung-konforme XML-Datei. Diese Dokumentation zeigt Ihnen, aus welchen c-entron Feldern die einzelnen XML-Informationen stammen.
### Unterstützte Versionen
- ZUGFeRD 1.0 (Altversion)
- ZUGFeRD 2.0 / XRechnung 1.2
- ZUGFeRD 2.1 / XRechnung 2.0, 2.2, 2.3.1
- ZUGFeRD 2.1 / XRechnung 3.0.1 (aktuell)
---
## Dokumentkopf
### Grundlegende Rechnungsinformationen
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Rechnungsnummer | `ram:ID` | Rechnungsnummer | Die Belegnummer der Rechnung/Gutschrift |
| Rechnungstyp | `ram:TypeCode` | Belegart | "380" = Rechnung, "381" = Gutschrift |
| Rechnungsdatum | `ram:IssueDateTime/udt:DateTimeString` | Rechnungsdatum | Datum der Rechnungserstellung |
| Zahlungsbedingungen (Notiz) | `ram:IncludedNote` | Zahlungskonditionen Text | Freitext zu den Zahlungsbedingungen |
| Verkäufer Information (Notiz) | `ram:IncludedNote[@SubjectCode="REG"]` | Automatisch generiert | Name, Adresse, Geschäftsführer, Handelsregisternummer |
| Verwendungszweck (Notiz) | `ram:IncludedNote[@SubjectCode="ABT"]` | Bankverbindung Einstellungen | Verwendungszweck für Überweisung |
---
## Verkäufer (Eigene Firma)
Der Verkäufer repräsentiert Ihre eigene Firma (Mandant) oder Filiale.
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| **Identifikation** | | | |
| Lieferantennummer (beim Kunden) | `ram:SellerTradeParty/ram:ID` | Eigene Lieferantennummer | Ihre Lieferantennummer beim Kunden |
| Handelsregisternummer | `ram:SellerTradeParty/ram:SpecifiedLegalOrganization/ram:ID` | Mandant → Handelsregisternummer | Handelsregisternummer (HRB) |
| **Name und Kontakt** | | | |
| Firmenname | `ram:SellerTradeParty/ram:Name` | Filiale → Name oder Mandant → Name | Name der Filiale (oder Mandant, abhängig von Einstellung) |
| Geschäftsführer | `ram:SellerTradeParty/ram:DefinedTradeContact/ram:PersonName` | Mandant → Geschäftsführer | Name des Geschäftsführers |
| Abteilung | `ram:SellerTradeParty/ram:DefinedTradeContact/ram:DepartmentName` | Kontaktperson → Abteilung | Abteilungsname (falls vorhanden) |
| Telefon | `ram:SellerTradeParty/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber` | Filiale → Telefon oder Mandant → Telefon | Telefonnummer |
| E-Mail | `ram:SellerTradeParty/ram:DefinedTradeContact/ram:EmailURIUniversalCommunication/ram:URIID` | Filiale → E-Mail oder Mandant → E-Mail | E-Mail-Adresse |
| **Adresse** | | | |
| Postleitzahl | `ram:SellerTradeParty/ram:PostalTradeAddress/ram:PostcodeCode` | Filiale → PLZ oder Mandant → PLZ | Postleitzahl |
| Straße | `ram:SellerTradeParty/ram:PostalTradeAddress/ram:LineOne` | Filiale → Straße oder Mandant → Straße | Straße und Hausnummer |
| Stadt | `ram:SellerTradeParty/ram:PostalTradeAddress/ram:CityName` | Filiale → Stadt oder Mandant → Stadt | Ort |
| Land | `ram:SellerTradeParty/ram:PostalTradeAddress/ram:CountryID` | Land → Ländercode | Zweistelliger Ländercode (z.B. "DE") |
| **Steuer** | | | |
| Steuernummer | `ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]` | Mandant → Steuernummer | Steueridentifikationsnummer |
### Datenquelle
- Wenn eine Filiale auf der Rechnung hinterlegt ist, werden deren Daten verwendet
- Andernfalls werden die Daten des Mandanten verwendet
- Das Land wird aus der Filiale, dem Mandanten oder dem Standardland ermittelt
---
## Käufer (Kunde)
Der Käufer repräsentiert Ihren Kunden.
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| **Identifikation** | | | |
| Kundennummer | `ram:BuyerTradeParty/ram:ID` | Rechnung → Kundennummer | Kundennummer in c-entron |
| **Name und Kontakt** | | | |
| Firmenname | `ram:BuyerTradeParty/ram:Name` | Rechnungsempfänger → Firmenname | Name des Rechnungsempfängers |
| Kontaktperson | `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:PersonName` | Rechnungsempfänger → Kontaktname | Name der Kontaktperson |
| Abteilung (Kontakt) | `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:DepartmentName` | Rechnungsempfänger → Kontakt-Abteilung | Abteilung der Kontaktperson |
| Telefon | `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber` | Rechnung → Kontakt Telefon | Telefonnummer des Kontakts |
| E-Mail | `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:EmailURIUniversalCommunication/ram:URIID` | Rechnung → Kontakt E-Mail | E-Mail-Adresse des Kontakts |
| **Adresse** | | | |
| Postleitzahl | `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:PostcodeCode` | Rechnungsempfänger → PLZ | Postleitzahl |
| Straße (Zeile 1) | `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:LineOne` | Rechnungsempfänger → Strukturiert aufgebaut | Erste Adresszeile (siehe unten) |
| Adresszeile 2 | `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:LineTwo` | Rechnungsempfänger → Strukturiert aufgebaut | Zweite Adresszeile (siehe unten) |
| Adresszeile 3 | `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:LineThree` | Rechnungsempfänger → Strukturiert aufgebaut | Dritte Adresszeile (siehe unten) |
| Stadt | `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:CityName` | Rechnungsempfänger → Stadt | Ort |
| Land | `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:CountryID` | Rechnungsempfänger → Land | Zweistelliger Ländercode |
| **Steuer** | | | |
| USt-IdNr. | `ram:BuyerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]` | Rechnung → USt-IdNr. | Umsatzsteuer-Identifikationsnummer |
### Strukturierter Adressaufbau
c-entron baut die Empfängeradresse intelligent aus den Rechnungsempfänger-Daten auf (maximal 3 Zeilen):
1. **Firmenname** - Der Firmenname (erste Priorität)
2. **Adresszusatz** - Zusätzlicher Adresszusatz
3. **Abteilung / Kontakt-Abteilung** - Abteilungsinformationen (Reihenfolge konfigurierbar über Einstellung "Kontakt-Abteilung zuerst")
4. **Kontaktname** - Name der Kontaktperson
5. **Straße/Hausnummer oder Postfach** - Entweder Straßenadresse oder Postfach
Die erste Information wird als `Name` verwendet, die weiteren Informationen füllen `AddressLine1`, `AddressLine2` und `AddressLine3` (maximal 3 Zeilen).
### Besonderheiten
- **Postfach**: Wenn ein Postfach angegeben ist, wird dieses anstelle der Straßenadresse verwendet
- **Straßenformatierung**: Straße und Hausnummer werden automatisch kombiniert
- **Abteilungsreihenfolge**: Die Reihenfolge von "Abteilung" und "Kontakt-Abteilung" kann über die Einstellung "Empfänger Kontakt-Abteilung zuerst" (ApplicationSettingID 10370) konfiguriert werden
- **Abweichende Rechnungsadresse**: Bei abweichender Rechnungsadresse wird der Name aus den Kundenstammdaten verwendet
---
## Bestellinformationen
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Leitweg-ID / Bestellnummer | `ram:BuyerReference` | Rechnung → Externe Bestellnummer | Bestellnummer des Kunden (bei XRechnung: Leitweg-ID) |
### Hinweis
Bei XRechnung-Exporten wird die Leitweg-ID als Pflichtfeld verwendet. Bei normalen ZUGFeRD-Exporten ist die Bestellnummer optional.
---
## Lieferinformationen
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Lieferdatum | `ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime` | Rechnung → Lieferdatum | Datum der Leistungserbringung |
### Wichtig
Das Lieferdatum ist in XRechnung ein Pflichtfeld. Wenn kein Lieferdatum angegeben ist, wird automatisch das Rechnungsdatum verwendet.
---
## Zahlungsinformationen
### Bankverbindung und SEPA
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| **Eigene Bankverbindung (Lastschrift)** | | | |
| SEPA Gläubiger-ID | `ram:CreditorReferenceID` | Mandant → SEPA-Identifikationsnummer | SEPA Creditor Identifier |
| **Währung** | | | |
| Währungscode | `ram:InvoiceCurrencyCode` | Rechnung → Währung | Währungscode (z.B. "EUR") |
| **Zahlungsart** | | | |
| Zahlungsart-Code | `ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode` | Rechnung → UNTDID 4461 Code | UN/EDIFACT Zahlungsart-Code (Standard: "ZZZ") |
| Zahlungsinformation | `ram:SpecifiedTradeSettlementPaymentMeans/ram:Information` | Rechnung → Zahlungskonditionen Text | Freitext zur Zahlungsart |
| **Lastschrift (nur bei SEPA)** | | | |
| Schuldner IBAN | `ram:PayerPartyDebtorFinancialAccount/ram:IBANID` | Bankverbindung → IBAN | IBAN des Kunden (bei Lastschrift) |
| **Überweisung (nur bei Banküberweisung)** | | | |
| Empfänger IBAN | `ram:PayeePartyCreditorFinancialAccount/ram:IBANID` | Mandant → Bankverbindung (Bank 1-4) | Ihre IBAN für Überweisungen |
| Empfänger BIC | `ram:PayeeSpecifiedCreditorFinancialInstitution/ram:BICID` | Mandant → Bankverbindung (Bank 1-4) | Ihre BIC |
| Kontoinhaber | `ram:PayeePartyCreditorFinancialAccount/ram:AccountName` | Mandant → Bankverbindung (Bank 1-4) | Name des Kontoinhabers |
### Bankauswahl
Die verwendete Bankverbindung wird wie folgt bestimmt:
1. Einstellung "Mandantenbank für Rechnung verwenden" (Bank 1-4)
2. Bei Kundenrechnungen: Kundenstammdaten können die Bankauswahl überschreiben
3. Die Bankdaten werden aus den Mandantenstammdaten (Bank 1-4) geladen
---
## Steuerinformationen
### Steuerbeträge und Steuersätze
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Steuerbetrag | `ram:ApplicableTradeTax/ram:CalculatedAmount` | Berechnet aus Positionen | Steuerbetrag je Steuersatz |
| Steuerart | `ram:ApplicableTradeTax/ram:TypeCode` | Fest "VAT" | Mehrwertsteuer |
| Steuerbefreiungsgrund | `ram:ApplicableTradeTax/ram:ExemptionReason` | Abhängig vom Szenario | Textlicher Grund für Steuerbefreiung |
| Steuerbemessungsgrundlage | `ram:ApplicableTradeTax/ram:BasisAmount` | Berechnet aus Positionen | Nettobetrag für die Steuerberechnung |
| Steuerkategorie | `ram:ApplicableTradeTax/ram:CategoryCode` | Abhängig vom Szenario | Steuerkategorie-Code |
| Steuersatz | `ram:ApplicableTradeTax/ram:RateApplicablePercent` | Positionen → Steuersatz | Mehrwertsteuersatz in Prozent |
### Steuerkategorien
Die Steuerkategorie wird automatisch ermittelt:
- **S (Standard)**: Normaler Steuersatz
- **E (Befreit)**: 0% MwSt. bei steuerfreien Inlandsgeschäften
- **K (Innergemeinschaftlich)**: 0% MwSt. bei innergemeinschaftlichen Lieferungen
- **G (Export)**: 0% MwSt. bei Exporten außerhalb der EU
- **AE (Reverse Charge)**: Umkehrung der Steuerschuldnerschaft
### Steuerbefreiungsgründe
Je nach Steuersituation wird automatisch der passende Text eingefügt:
- **Reverse Charge**: "Steuerschuldnerschaft des Leistungsempfängers gem. §13B Abs 2 Nr. 10 UStG."
- **Steuerfrei Inland**: "Steuerfrei"
- **Innergemeinschaftlich**: "Kein Ausweis der Umsatzsteuer bei innergemeinschaftlichen Lieferungen"
- **Export**: "Steuer nicht erhoben aufgrund von Export außerhalb der EU"
---
## Abrechnungszeitraum
Für Vertragsabrechnungen werden die Abrechnungszeiträume automatisch ermittelt:
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Abrechnungszeitraum Von | `ram:BillingSpecifiedPeriod/ram:StartDateTime` | Vertragsabrechnung → Frühestes Startdatum | Beginn des Abrechnungszeitraums |
| Abrechnungszeitraum Bis | `ram:BillingSpecifiedPeriod/ram:EndDateTime` | Vertragsabrechnung → Spätestes Enddatum | Ende des Abrechnungszeitraums |
### Hinweis
Die Abrechnungszeiträume werden nur exportiert, wenn sie sich unterscheiden (Start ≠ Ende).
---
## Zahlungsbedingungen
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Beschreibung | `ram:SpecifiedTradePaymentTerms/ram:Description` | Rechnung → Zahlungsbedingungen + Skonto-Info | Vollständiger Text der Zahlungsbedingungen mit Skonto-Informationen |
| Fälligkeitsdatum | `ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime` | Rechnung → Fälligkeitsdatum | Datum, bis wann die Zahlung erfolgen muss |
| SEPA Mandatsreferenz | `ram:SpecifiedTradePaymentTerms/ram:DirectDebitMandateID` | Bankverbindung → Mandatsreferenz | SEPA-Mandatsreferenznummer (nur bei Lastschrift) |
### Skonto-Information
Wenn Skonto-Konditionen hinterlegt sind, werden diese automatisch im BR-DE-18 Format an die Beschreibung angehängt.
---
## Betragsübersicht
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Nettobetrag (Summe) | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:LineTotalAmount` | Rechnung → Nettobetrag gesamt | Summe aller Netto-Positionsbeträge |
| Zuschlagsbetrag | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:ChargeTotalAmount` | 0 (fest) | Gesamtbetrag der Zuschläge |
| Abschlagsbetrag | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:AllowanceTotalAmount` | 0 (fest) | Gesamtbetrag der Abschläge |
| Steuerbemessungsgrundlage | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxBasisTotalAmount` | Rechnung → Nettobetrag gesamt | Grundlage für die Steuerberechnung |
| Steuerbetrag gesamt | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxTotalAmount` | Rechnung → Steuerbetrag gesamt | Summe aller Steuerbeträge |
| Bruttobetrag gesamt | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount` | Netto + Steuer | Gesamtbetrag der Rechnung |
| Bereits gezahlt | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount` | 0 (fest) | Bereits gezahlter Betrag |
| Zu zahlender Betrag | `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount` | Netto + Steuer | Offener Zahlungsbetrag |
### Validierung
Das System prüft automatisch:
- Die Summe der Positions-Nettobeträge muss dem Gesamt-Nettobetrag entsprechen (Toleranz: ±3,00)
- Die Summe der Positions-Bruttobeträge muss dem Gesamt-Bruttobetrag entsprechen (Toleranz: ±3,00)
- Bei Abweichungen innerhalb der Toleranz wird eine Warnung ausgegeben
- Bei Abweichungen außerhalb der Toleranz schlägt der Export fehl
---
## Gutschriften: Verweis auf ursprüngliche Rechnung
Bei Gutschriften wird automatisch ein Verweis auf die ursprüngliche Rechnung erstellt:
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Original-Rechnungsnummer | `ram:InvoiceReferencedDocument/ram:IssuerAssignedID` | Ursprüngliche Rechnung → Nummer | Nummer der ursprünglichen Rechnung |
| Original-Rechnungsdatum | `ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime` | Ursprüngliche Rechnung → Datum | Datum der ursprünglichen Rechnung |
### Voraussetzung
- Gilt nur für Gutschriften
- Es muss genau eine Ursprungsrechnung vorhanden sein
- Der Verweis wird automatisch aus den Positionsursprüngen ermittelt
---
## Rechnungspositionen
Jede Position der Rechnung oder Gutschrift wird in die XML übernommen.
### Positionsnummer
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Positionsnummer | `ram:AssociatedDocumentLineDocument/ram:LineID` | Automatisch fortlaufend | Fortlaufende Nummer (1, 2, 3, ...) |
---
### Artikelinformationen
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| EAN-Code (GTIN) | `ram:SpecifiedTradeProduct/ram:GlobalID[@schemeID="0160"]` | Position → EAN-Code | EAN/GTIN Barcode des Artikels |
| Artikelnummer | `ram:SpecifiedTradeProduct/ram:SellerAssignedID` | Position → Artikelcode | Ihre interne Artikelnummer |
| Positionstext | `ram:SpecifiedTradeProduct/ram:Name` | Position → Text | Bezeichnung / Beschreibungstext |
| Seriennummer (Beschreibung) | `ram:ApplicableProductCharacteristic/ram:Description` | Automatisch: "Seriennummer 1", "Seriennummer 2", ... | Bezeichnung der Seriennummer |
| Seriennummer (Wert) | `ram:ApplicableProductCharacteristic/ram:Value` | Position → Barcodes | Barcode-Wert / Seriennummer |
### Seriennummern (Barcodes)
- Wenn einer Position Barcodes/Seriennummern zugeordnet sind, werden diese automatisch exportiert
- Pro Position können **mehrere Seriennummern** exportiert werden
- Jede Seriennummer wird mit einer fortlaufenden Nummer versehen (Seriennummer 1, 2, 3, ...)
- **Bei Titelpositionen**: Seriennummern aller untergeordneten Positionen werden zur Titelposition zusammengefasst
- Die Seriennummern werden als Produktmerkmale (`ApplicableProductCharacteristic`) im XML abgelegt
### Einstellungen
- Der Export von EAN-Codes kann in den Rechnungseinstellungen deaktiviert werden
- Der Export von Artikelnummern kann in den Rechnungseinstellungen deaktiviert werden
---
### Preise und Mengen
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Einzelpreis (Netto) | `ram:NetPriceProductTradePrice/ram:ChargeAmount` | Position → Netto-Einzelpreis | Netto-Einzelpreis des Artikels |
| Menge | `ram:BilledQuantity` | Position → Menge | Abgerechnete Menge |
| Mengeneinheit | `ram:BilledQuantity[@unitCode]` | Artikel → Mengeneinheit → UN/ECE-Code | UN/ECE-Code der Mengeneinheit (z.B. "C62" = Stück) |
| Positionssumme (Netto) | `ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount` | Position → Netto-Positionssumme | Netto-Gesamtbetrag der Position |
### Besonderheiten bei negativen Preisen
ZUGFeRD unterstützt keine negativen Preise. Das System:
1. Macht den Einzelpreis positiv
2. Negiert stattdessen die Menge
3. Das Ergebnis bleibt rechnerisch gleich
### Mengeneinheiten (UN/ECE-Codes)
- Wird automatisch aus den Artikelstammdaten übernommen
- Standardwert: "C62" (Stück/Einheit)
- Nur bei Artikelpositionen; bei anderen Positionsarten wird "C62" verwendet
---
### Steuern auf Positionsebene
| ZUGFeRD Feld | XML-Pfad | c-entron Quelle | Beschreibung |
|--------------|----------|-----------------|--------------|
| Steuerart | `ram:ApplicableTradeTax/ram:TypeCode` | Fest "VAT" | Mehrwertsteuer |
| Steuerkategorie | `ram:ApplicableTradeTax/ram:CategoryCode` | Abhängig vom Szenario | Steuerkategorie-Code (siehe oben) |
| Steuersatz | `ram:ApplicableTradeTax/ram:RateApplicablePercent` | Position → Steuersatz | Mehrwertsteuersatz in Prozent |
---
### Titelpositionen (Gruppierungen)
Titelpositionen ermöglichen die Gruppierung von Artikelpositionen:
| Verhalten | Beschreibung |
|-----------|--------------|
| **Eingeklappte Titelpositionen** | Werden als einzelne Position mit der Summe der untergeordneten Positionen exportiert |
| **Menge** | Wird immer auf 1 gesetzt |
| **Einzelpreis** | Entspricht der Positionssumme |
| **Steuersatz** | Wird aus den untergeordneten Positionen ermittelt |
### Einschränkungen bei Titelpositionen
- **Ausgeklappte Titelpositionen** werden nicht unterstützt (Export schlägt fehl)
- **Gemischte Steuersätze** in einer Titelposition werden nicht unterstützt (Export schlägt fehl)
- **Standard-Steuersatz**: Wenn eine Titelposition keine untergeordneten Positionen hat, wird 19% verwendet
- **Seriennummern**: Alle Seriennummern der untergeordneten Positionen werden zur Titelposition aggregiert
### Welche Positionen werden exportiert?
- Artikel
- Kundenrabatte
- Eingeklappte Titelpositionen
- Sortierung nach interner Position
---
## Export-Einstellungen
Die folgenden Einstellungen beeinflussen den ZUGFeRD-Export:
### Rechnungseinstellungen
- **Aktive ZUGFeRD-Version**: Legt fest, welche ZUGFeRD/XRechnung-Version exportiert wird
- **Mandantenname bevorzugen**: Verwendet den Mandantennamen statt des Filialnamens
- **EAN-Code exportieren**: Aktiviert/deaktiviert den Export von EAN-Codes
- **Artikelnummer exportieren**: Aktiviert/deaktiviert den Export von Artikelnummern
### Kundeneinstellungen
- **ZUGFeRD exportieren**: Kann pro Kunde aktiviert/deaktiviert werden
- **Mandantenbank**: Kann pro Kunde überschrieben werden
### SEPA-Einstellungen
- **SEPA aktiv**: Bestimmt, ob Lastschrift (SEPA) oder Überweisung verwendet wird
- **Mandatsreferenz**: SEPA-Mandatsreferenznummer der Bankverbindung
---
## Datenquellen im Überblick
### Stammdaten
- **Mandant**: Firmendaten, Bankverbindungen, Steuernummern
- **Filiale**: Filial-spezifische Adress- und Kontaktdaten
- **Kunde**: Kundenstammdaten mit Adressen und Kontakten
- **Artikel**: Artikelstammdaten mit Mengeneinheiten
### Belege
- **Rechnung/Gutschrift**: Alle rechnungsspezifischen Daten
- **Rechnungspositionen**: Positionsdaten mit Preisen, Mengen und Steuern
- **Bankverbindung**: IBAN, BIC, Mandatsreferenz
### Verträge
- **Vertragsabrechnung**: Abrechnungszeiträume für Vertragspositionen
### Einstellungen
- **Rechnungseinstellungen**: ZUGFeRD-Version, Export-Optionen
- **Zahlungsbedingungen**: Skonto-Konditionen
---
## Häufig gestellte Fragen
### Welche Daten muss ich in c-entron pflegen, damit der ZUGFeRD-Export funktioniert?
**Pflichtfelder für XRechnung:**
1. **Eigene Firma**: Vollständige Adressdaten, Steuernummer, Handelsregisternummer
2. **Kunde**: Vollständige Adressdaten, USt-IdNr. (bei EU-Geschäften)
3. **Rechnung**: Rechnungsdatum, Lieferdatum (wird notfalls automatisch gesetzt)
4. **Bankverbindung**: IBAN und BIC (für Überweisungen)
5. **Leitweg-ID**: Bei XRechnung-Pflicht im Feld "Externe Bestellnummer"
**Empfohlene Felder:**
- Zahlungsbedingungen mit Skonto
- Kontaktdaten (Telefon, E-Mail)
- EAN-Codes und Artikelnummern
- SEPA-Mandatsreferenz (bei Lastschrift)
---
### Wie werden die Empfänger-Adresszeilen aufgebaut?
c-entron verwendet die strukturierten Rechnungsempfänger-Daten und baut daraus intelligent die Adresszeilen auf:
**Verfügbare Felder:**
- **Firmenname** - Wird als Name des Empfängers verwendet
- **Adresszusatz** - Zusätzliche Adressinformation
- **Abteilung** - Abteilungsbezeichnung
- **Kontakt-Abteilung** - Abteilung der Kontaktperson
- **Kontaktname** - Name der Kontaktperson
- **Straße und Hausnummer** - Straßenadresse
- **Postfach** - Postfach (wird anstelle der Straße verwendet, wenn aktiviert)
**Automatischer Aufbau:**
1. Das erste verfügbare Feld wird als **Name** verwendet
2. Weitere Felder füllen **Adresszeile 1, 2 und 3** (maximal 3 zusätzliche Zeilen)
3. Die Reihenfolge von "Abteilung" und "Kontakt-Abteilung" ist konfigurierbar
4. Wenn ein Postfach angegeben ist, ersetzt dieses die Straßenadresse
**Beispiel:**
- **Name**: "Musterfirma GmbH"
- **Adresszeile 1**: "IT-Abteilung"
- **Adresszeile 2**: "z.Hd. Max Mustermann"
- **Straße**: "Musterstraße 123"
- **PLZ/Ort**: "12345 Musterstadt"
---
### Wie wird die Bankverbindung ausgewählt?
1. In den Rechnungseinstellungen wird festgelegt, welche Mandantenbank verwendet wird (Bank 1-4)
2. Diese Einstellung kann pro Kunde in den Kundenstammdaten überschrieben werden
3. Die Bankdaten werden aus den Mandantenstammdaten geladen
---
### Was passiert bei negativen Preisen?
ZUGFeRD unterstützt keine negativen Einzelpreise. Daher:
- Der Einzelpreis wird positiv gemacht
- Die Menge wird negativ gemacht
- Das Ergebnis (Positionssumme) bleibt identisch
---
### Wie funktionieren Titelpositionen im Export?
**Eingeklappte Titelpositionen:**
- Werden als eine Position exportiert
- Enthalten die Summe aller untergeordneten Positionen
- Menge ist immer 1
- Einzelpreis = Positionssumme
**Wichtige Einschränkungen:**
- Ausgeklappte Titelpositionen werden nicht unterstützt
- Alle untergeordneten Positionen müssen den gleichen Steuersatz haben
- Bei gemischten Steuersätzen schlägt der Export fehl
---
### Was bedeuten die Validierungs-Warnungen?
Das System prüft, ob die Summe der Positionen mit den Kopfbeträgen übereinstimmt:
- **Toleranz**: ±3,00 Euro
- **Warnung**: Abweichung innerhalb der Toleranz → Export erfolgt, Warnung wird protokolliert
- **Fehler**: Abweichung außerhalb der Toleranz → Export schlägt fehl
Ursachen können sein:
- Rundungsdifferenzen bei vielen Positionen
- Manuelle Korrekturen an Beträgen
- Fehlerhafte Steuerberechnungen
**Lösung**: Rechnung prüfen und ggf. Positionen anpassen
---
### Wie wird der Abrechnungszeitraum ermittelt?
Bei Vertragsabrechnungen:
- **Von**: Frühestes Startdatum aller Vertragspositionen
- **Bis**: Spätestes Enddatum aller Vertragspositionen
Bei normalen Rechnungen:
- Kein Abrechnungszeitraum (nur bei unterschiedlichen Daten relevant)
---
### Welche Mengeneinheiten werden unterstützt?
c-entron verwendet UN/ECE-Codes für Mengeneinheiten:
- **C62**: Stück / Einheit (Standard)
- **HUR**: Stunde
- **MTR**: Meter
- **MTK**: Quadratmeter
- **MTQ**: Kubikmeter
- **KGM**: Kilogramm
- **LTR**: Liter
- Und viele weitere...
Die Mengeneinheit wird aus den Artikelstammdaten übernommen. Falls nicht vorhanden, wird "C62" (Stück) verwendet.
---
### Wie werden Seriennummern / Barcodes exportiert?
c-entron unterstützt den Export von Seriennummern (Barcodes) in ZUGFeRD/XRechnung:
**Automatischer Export:**
- Wenn Sie einer Rechnungsposition Barcodes/Seriennummern zugeordnet haben, werden diese automatisch in die XML-Datei exportiert
- Jede Seriennummer wird als separates Produktmerkmal (`ApplicableProductCharacteristic`) gespeichert
**Mehrere Seriennummern pro Position:**
- Sie können beliebig viele Seriennummern pro Position exportieren
- Jede Seriennummer erhält automatisch eine fortlaufende Bezeichnung:
- "Seriennummer 1"
- "Seriennummer 2"
- "Seriennummer 3"
- usw.
**Titelpositionen:**
- Bei eingeklappten Titelpositionen werden alle Seriennummern der untergeordneten Positionen automatisch zur Titelposition zusammengefasst
- Die Reihenfolge der Seriennummern bleibt erhalten
**XML-Struktur:**
```xml
<ram:ApplicableProductCharacteristic>
<ram:Description>Seriennummer 1</ram:Description>
<ram:Value>SN-12345-ABC</ram:Value>
</ram:ApplicableProductCharacteristic>
<ram:ApplicableProductCharacteristic>
<ram:Description>Seriennummer 2</ram:Description>
<ram:Value>SN-67890-XYZ</ram:Value>
</ram:ApplicableProductCharacteristic>
```
**Anwendungsfall:**
Dies ist besonders nützlich für Produkte mit individuellen Seriennummern, z.B.:
- Elektronikgeräte (Laptops, Smartphones)
- Maschinen und Anlagen
- Fahrzeuge
- Medizinische Geräte
- Alle Produkte mit eindeutiger Identifikation
---
### Was ist der Unterschied zwischen ZUGFeRD und XRechnung?
**ZUGFeRD (Comfort):**
- Mehr optionale Felder
- Flexibler bei fehlenden Daten
- Für B2B-Geschäft geeignet
**XRechnung:**
- Pflicht für öffentliche Auftraggeber in Deutschland
- Strengere Validierung
- Leitweg-ID ist Pflichtfeld
- Lieferdatum ist Pflichtfeld
c-entron erstellt automatisch das richtige Format:
- **XRechnung**: Wenn eine Leitweg-ID angegeben ist
- **ZUGFeRD Comfort**: Wenn keine Leitweg-ID angegeben ist
---
## Version
| Version | Datum | Hinweise |
|---------|-------|----------|
| 1.2 | 2025 | Hinzugefügt: Strukturierte Rechnungsempfänger-Daten mit intelligentem Adressaufbau, verbesserte Ländercode-Unterstützung für Handelstyp-Ermittlung |
| 1.1 | 2025 | Hinzugefügt: Barcode/Seriennummern-Export in Rechnungspositionen |
| 1.0 | 2025 | Initiale Anwenderdokumentation für XRechnung 3.0.1 |
---
## Support
Bei Fragen zur ZUGFeRD/XRechnung-Funktionalität in c-entron wenden Sie sich bitte an:
- **c-entron Support**: erp-support@nexoware.com
- **Dokumentation**: Siehe auch die Online-Hilfe in c-entron
@@ -0,0 +1,421 @@
# ZUGFeRD XML Field Mapping
This document describes the complete mapping between ZUGFeRD XML nodes and c-entron database fields/entities.
## Overview
The ZUGFeRD XML generation is implemented in `InvoiceZugferdBL.cs` and supports multiple ZUGFeRD versions:
- ZUGFeRD 1.0 (legacy)
- ZUGFeRD 2.0 / XRechnung 1.2
- ZUGFeRD 2.1 / XRechnung 2.0, 2.2, 2.3.1
- ZUGFeRD 2.1 / XRechnung 3.0.1 (current)
The main data flow is:
1. Load receipt data from `BookKeepingExportBL.LoadReceipt()` → returns `IBookKeepingReceipt`
2. Convert to `ZugferdExportItem` in `GetZugferdExportItem()`
3. Generate XML document with `DoGenerateZugferdXRechnungXmlDocument()`
---
## Document Context & Header
### ExchangedDocumentContext
XML structure related to document context and format identification.
| XML Node | c-entron Source | Description |
|----------|----------------|-------------|
| `rsm:GuidelineSpecifiedDocumentContextParameter/ram:ID` | Derived from `ZugferdKind` enum | Format identifier (e.g., "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0") |
| `ram:BusinessProcessSpecifiedDocumentContextParameter/ram:ID` | Static value | For v3.0.1: "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0" |
### ExchangedDocument
Basic invoice/credit voucher information.
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:ID` | `Number` | `IBookKeepingReceipt` | Invoice/receipt number |
| `ram:TypeCode` | Derived from `Type` | `IBookKeepingReceipt` | "380" = Invoice, "381" = Credit voucher |
| `ram:IssueDateTime/udt:DateTimeString` | `Date` | `IBookKeepingReceipt` | Receipt date (format: yyyyMMdd) |
| `ram:IncludedNote` | `PaymentConditionsText` | `IBookKeepingReceipt` | Payment text/notes |
| `ram:IncludedNote[@SubjectCode="REG"]` | Composed string | Multiple sources | Seller information text (Name, address, CEO, HRB) |
| `ram:IncludedNote[@SubjectCode="ABT"]` | `PayeeAssignmentNotice` | Bank info via settings | Assignment notice for bank transfer |
---
## Seller Trade Party (Own Company)
The seller represents the own company (Mandator) or branch.
| XML Node | c-entron Field | Table/Entity | Path in Code |
|----------|----------------|--------------|--------------|
| `ram:SellerTradeParty/ram:ID` | `OwnSupplierNumber` | `IBookKeepingReceipt` | Customer's supplier number for own company |
| `ram:SellerTradeParty/ram:Name` | `Name` or `Name` | `Branch` or `Mandator` | Branch name (or Mandator if setting/no branch) |
| `ram:SellerTradeParty/ram:SpecifiedLegalOrganization/ram:ID` | `HRB` | `Mandator` | Commercial register number |
| `ram:SellerTradeParty/ram:DefinedTradeContact/ram:PersonName` | `CEO` | `Mandator` | Managing director name |
| `ram:SellerTradeParty/ram:DefinedTradeContact/ram:DepartmentName` | `Department.Department` | `ContactPerson` via `Branch` or `Mandator` | Department name |
| `ram:SellerTradeParty/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber` | `PhoneNumber` or `Phone` | `Branch` or `Mandator` | Phone number |
| `ram:SellerTradeParty/ram:DefinedTradeContact/ram:EmailURIUniversalCommunication/ram:URIID` | `EMail` | `Branch` or `Mandator` | Email address |
| `ram:SellerTradeParty/ram:PostalTradeAddress/ram:PostcodeCode` | `ZipCode` or `PostCode` | `Branch` or `Mandator` | ZIP code |
| `ram:SellerTradeParty/ram:PostalTradeAddress/ram:LineOne` | `Street` | `Branch` or `Mandator` | Street address |
| `ram:SellerTradeParty/ram:PostalTradeAddress/ram:CityName` | `City` | `Branch` or `Mandator` | City |
| `ram:SellerTradeParty/ram:PostalTradeAddress/ram:CountryID` | `CountryCode` | `Country` | Country code (ISO 2-letter) |
| `ram:SellerTradeParty/ram:URIUniversalCommunication/ram:URIID[@schemeID="EM"]` | `EMail` | `Branch` or `Mandator` | Email (duplicate for compatibility) |
| `ram:SellerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]` | `TaxIDNumber` | `Mandator` | Tax identification number |
**Internal Fields (not exported to XML):**
- `OwnCountryCode`: Country code of seller (used for trade type determination: inland/EU/export)
**Data Sources:**
- If `BranchI3D` is set: Load from `Branch` table
- Otherwise: Load from `Mandator` table (default mandator)
- Country: From `Branch.CountryI3D` or `Mandator.Country` or default country (default: "DE")
---
## Buyer Trade Party (Customer)
The buyer represents the customer/recipient.
| XML Node | c-entron Field | Table/Entity | Path in Code |
|----------|----------------|--------------|--------------|
| `ram:BuyerTradeParty/ram:ID` | `AddressNumber` | `IBookKeepingReceipt` | Customer number |
| `ram:BuyerTradeParty/ram:Name` | `CompanyName` or fallback to `AddressName` | `ReceiptReceiver` or `IBookKeepingReceipt` | Recipient name |
| `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:PersonName` | `ContactName` | `ReceiptReceiver` | Contact person name |
| `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:DepartmentName` | `ContactDepartment` | `ReceiptReceiver` | Contact department |
| `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:TelephoneUniversalCommunication/ram:CompleteNumber` | `ContactPhone` | `IBookKeepingReceipt` | Phone number |
| `ram:BuyerTradeParty/ram:DefinedTradeContact/ram:EmailURIUniversalCommunication/ram:URIID` | `ContactEMail` | `IBookKeepingReceipt` | Email address |
| `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:PostcodeCode` | `Zip` | `ReceiptReceiver` | ZIP code |
| `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:LineOne` | Structured from `ReceiptReceiver` | `ReceiptReceiver` | First address line (see below) |
| `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:LineTwo` | Structured from `ReceiptReceiver` | `ReceiptReceiver` | Second address line (see below) |
| `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:LineThree` | Structured from `ReceiptReceiver` | `ReceiptReceiver` | Third address line (see below) |
| `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:CityName` | `City` | `ReceiptReceiver` | City |
| `ram:BuyerTradeParty/ram:PostalTradeAddress/ram:CountryID` | `CountryCode` | `Country` via `ReceiptReceiver.CountryI3D` | Country code (ISO 2-letter) |
| `ram:BuyerTradeParty/ram:URIUniversalCommunication/ram:URIID[@schemeID="EM"]` | `ContactEMail` | `IBookKeepingReceipt` | Email (duplicate for compatibility) |
| `ram:BuyerTradeParty/ram:SpecifiedTaxRegistration/ram:ID[@schemeID="VA"]` | `SalesTaxIdentificationNumber` | `IBookKeepingReceipt` | VAT identification number |
**Internal Fields (not exported to XML):**
- `OwnCountryCode`: Country code of buyer (used for trade type determination: inland/EU/export)
- `IsEUTrade`: Flag indicating if buyer country is EU member
**Structured Address Line Building:**
The system intelligently builds address lines from `ReceiptReceiver` with a maximum of 3 lines:
1. **CompanyName** - Company name (first priority if available)
2. **AdditionalAddressSupplement** - Additional address supplement
3. **Department/ContactDepartment** - Department information (order configurable via `ReceiverContactDepartmentFirst` setting)
4. **ContactName** - Contact person name
5. **Street/HouseNumber or PostOfficeBox** - Either street address or P.O. Box (formatted as shown in PostOfficeBox)
The first item becomes the `Name`, subsequent items fill `AddressLine1`, `AddressLine2`, and `AddressLine3` (maximum 3 lines).
**Special Handling:**
- Post office box: If `HasPostOfficeBox` is true, uses `PostOfficeBox` instead of street address
- Street formatting: Combines `Street` and `HouseNumber` with proper trimming
- Department order: Configurable via `ReceiverContactDepartmentFirst` setting (ApplicationSettingID 10370)
- Invoice address: If alternative invoice address is used (`UsedAlternativeInvoiceAddress`), name is loaded from `Kunden` table
- Fallback: If `ReceiptReceiver` is null, falls back to legacy `IBookKeepingReceipt` fields
---
## Header Trade Agreement
Purchase order and party references.
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:ApplicableHeaderTradeAgreement/ram:BuyerReference` | `ExternalPurchaseOrderNumber` or `leitwegID` parameter | `IBookKeepingReceipt` | Leitweg-ID for XRechnung, otherwise purchase order number |
| `ram:ApplicableHeaderTradeAgreement/ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID` | `ExternalPurchaseOrderNumber` | `IBookKeepingReceipt` | Purchase order number (only if not XInvoice) |
---
## Header Trade Delivery
Delivery information.
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString` | `DeliveryDate` or `Date` | `IBookKeepingReceipt` | Delivery date (fallback to receipt date if not set) |
---
## Header Trade Settlement
Payment, banking, and monetary information.
### Banking & SEPA
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:ApplicableHeaderTradeSettlement/ram:CreditorReferenceID` | `SepaIdentificationNumber` | `Mandator` | SEPA creditor reference ID (only for direct debit) |
| `ram:ApplicableHeaderTradeSettlement/ram:InvoiceCurrencyCode` | `CurrencyISOCode` | `IBookKeepingReceipt` | Currency code (e.g., "EUR") |
| `ram:SpecifiedTradeSettlementPaymentMeans/ram:TypeCode` | `Untdid4461` or "ZZZ" | `IBookKeepingReceipt` | Payment means type code (UN/EDIFACT 4461) |
| `ram:SpecifiedTradeSettlementPaymentMeans/ram:Information` | `PaymentConditionsText` | `IBookKeepingReceipt` | Payment information text |
| `ram:SpecifiedTradeSettlementPaymentMeans/ram:PayerPartyDebtorFinancialAccount/ram:IBANID` | `Iban` | `BankAccount` via `BankAccountI3D` | Debtor IBAN (for direct debit) |
| `ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount/ram:IBANID` | Selected bank IBAN | `Mandator` bank info (Bank1-4) | Creditor IBAN (for bank transfer) |
| `ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeePartyCreditorFinancialAccount/ram:AccountName` | Selected bank holder or `Name` | `Mandator` bank info or `Mandator` | Account holder name |
| `ram:SpecifiedTradeSettlementPaymentMeans/ram:PayeeSpecifiedCreditorFinancialInstitution/ram:BICID` | Selected bank BIC | `Mandator` bank info (Bank1-4) | Bank BIC code |
**Bank Selection Logic:**
1. Check `ReceiptInvoiceSettings.UseMandatorBankForInvoice` (1-4)
2. For customer receipts, check `AccountCustomer.MandatorBank` override
3. Load bank details from `Mandator` (Bank1Iban/Bic, Bank2Iban/Bic, Bank3Iban/Bic, Bank4Iban/Bic)
### Tax Information
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:ApplicableTradeTax/ram:CalculatedAmount` | Calculated from positions | `BookKeepingReceiptItem` | Tax amount per tax rate |
| `ram:ApplicableTradeTax/ram:TypeCode` | Static "VAT" | - | Tax type |
| `ram:ApplicableTradeTax/ram:ExemptionReason` | Derived from tax scenario | Logic | Tax exemption reason text |
| `ram:ApplicableTradeTax/ram:BasisAmount` | Calculated from positions | `BookKeepingReceiptItem` | Net basis amount for tax |
| `ram:ApplicableTradeTax/ram:CategoryCode` | Derived from tax scenario | Logic | Tax category (S, E, K, G, AE) |
| `ram:ApplicableTradeTax/ram:RateApplicablePercent` | Grouped tax rates | `BookKeepingReceiptItem.TaxRate` | VAT percentage |
**Tax Category Codes:**
- `S` (Standard): Normal VAT rate
- `E` (Exempt): 0% VAT for domestic tax-free transactions
- `K` (Intra-community): 0% VAT for EU intra-community supply
- `G` (Export): 0% VAT for export outside EU
- `AE` (Reverse charge): Reverse charge scenario (`IsReverseCharge = true`)
**Tax Exemption Reasons:**
- Reverse charge: "Steuerschuldnerschaft des Leistungsempfängers gem. §13B Abs 2 Nr. 10 UStG."
- Tax-free domestic: "Steuerfrei"
- Intra-community: "Kein Ausweis der Umsatzsteuer bei innergemeinschaftlichen Lieferungen"
- Export: "Steuer nicht erhoben aufgrund von Export außerhalb der EU"
### Billing Period
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:BillingSpecifiedPeriod/ram:StartDateTime/udt:DateTimeString` | Min of position billing periods | `ContractBillingInfo` | Earliest billing start date |
| `ram:BillingSpecifiedPeriod/ram:EndDateTime/udt:DateTimeString` | Max of position billing periods | `ContractBillingInfo` | Latest billing end date |
### Payment Terms
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:SpecifiedTradePaymentTerms/ram:Description` | `PaymentConditionFull` + Skonto info | `IBookKeepingReceipt` + derived | Full payment condition text with line breaks |
| `ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString` | `DueDate` or `Date` | `IBookKeepingReceipt` | Payment due date |
| `ram:SpecifiedTradePaymentTerms/ram:DirectDebitMandateID` | `AuthorizationNumber` | `BankAccount` via `BankAccountI3D` | SEPA mandate ID (only for direct debit) |
**Skonto Information (BR-DE-18):**
- Generated via `AssetConditionBL.GetPaymentConditionSkontoInBR_DE_18Format()`
- Appended to description with proper XML line breaks (`&#xD;`)
### Monetary Summation
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:LineTotalAmount` | `NetPriceFCComplete` | `IBookKeepingReceipt` | Total net amount |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:ChargeTotalAmount` | Static 0 | - | Total charges |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:AllowanceTotalAmount` | Static 0 | - | Total allowances |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxBasisTotalAmount` | `NetPriceFCComplete` | `IBookKeepingReceipt` | Tax basis total |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TaxTotalAmount[@currencyID]` | `TaxPriceFCComplete` | `IBookKeepingReceipt` | Total tax amount |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:GrandTotalAmount` | Net + Tax | Calculated | Gross total |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:TotalPrepaidAmount` | Static 0 | - | Prepaid amount |
| `ram:SpecifiedTradeSettlementHeaderMonetarySummation/ram:DuePayableAmount` | Net + Tax | Calculated | Due payable amount |
**Validation:**
- System validates that sum of position net prices equals header net price (tolerance: ±3.00)
- System validates that sum of position gross prices equals header gross price (tolerance: ±3.00)
- Warnings are logged if differences are within tolerance, errors if exceeding
### Invoice Referenced Document (Credit Vouchers Only)
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:InvoiceReferencedDocument/ram:IssuerAssignedID` | `Number` | `ReceiptInvoice` (origin) | Original invoice number |
| `ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/qdt:DateTimeString` | `Date` | `ReceiptInvoice` (origin) | Original invoice date |
**Logic:**
- Only for credit vouchers (`Type = CreditVoucher`)
- Only if exactly one origin invoice exists
- Loaded via `ReceiptItem.OriginReceiptI3D` where `OriginKind = Invoice`
---
## Line Items (Positions)
Each invoice/credit voucher position.
### Line Item Document
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:AssociatedDocumentLineDocument/ram:LineID` | Sequential counter | Generated | Position number (1, 2, 3, ...) |
### Trade Product
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:SpecifiedTradeProduct/ram:GlobalID[@schemeID="0160"]` | `EANCode` | `BookKeepingReceiptItem` | EAN/GTIN barcode (if not disabled in settings) |
| `ram:SpecifiedTradeProduct/ram:SellerAssignedID` | `Articlecode` | `BookKeepingReceiptItem` | Article code (if not disabled in settings) |
| `ram:SpecifiedTradeProduct/ram:Name` | `Text` | `BookKeepingReceiptItem` (via compact entity) | Position text/description |
| `ram:SpecifiedTradeProduct/ram:ApplicableProductCharacteristic/ram:Description` | Static "Seriennummer {index}" | Generated | Serial number label (1, 2, 3, ...) |
| `ram:SpecifiedTradeProduct/ram:ApplicableProductCharacteristic/ram:Value` | `Barcodes[i]` | `IReceiptItemWithBarcodes` | Barcode value (serial number) |
**Barcode Handling:**
- Barcodes are loaded from receipt items that implement `IReceiptItemWithBarcodes`
- Multiple barcodes per position are supported (indexed sequentially)
- For title positions, barcodes from child items are aggregated into the parent position
- Each barcode is exported as a separate `ApplicableProductCharacteristic` node
- Code reference: `InvoiceZugferdBL.cs:625-626` (loading), `InvoiceZugferdBL.cs:648-651` (title aggregation), `InvoiceZugferdBL.cs:881-898` (XML export)
**Settings Flags:**
- `ReceiptInvoiceSettings.ZugferdExportDontExportEanCode`: Suppresses EAN export
- `ReceiptInvoiceSettings.ZugferdExportDontExportArticleCode`: Suppresses article code export
### Line Trade Agreement
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:NetPriceProductTradePrice/ram:ChargeAmount` | `NetPrice` | `InvoiceItemCompact` or `CreditVoucherItemCompact` | Unit net price (always positive) |
**Note:** If `NetPrice` is negative, the price is made positive and quantity is negated.
### Line Trade Delivery
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:BilledQuantity[@unitCode]` | `QuantityComplete` | `InvoiceItemCompact` or `CreditVoucherItemCompact` | Quantity (negated if price was negative) |
| `@unitCode` | `UNECECode` | `Article.ArticleUnit` | UN/ECE unit code (default: "C62" = piece) |
**UN/ECE Code Logic:**
- Only for article items (`Kind = Article` and `ArticleI3D` is set)
- Loaded from `Article.ArticleUnit.UNECECode`
- Fallback: "C62" (one/piece)
### Line Trade Settlement
| XML Node | c-entron Field | Table/Entity | Description |
|----------|----------------|--------------|-------------|
| `ram:ApplicableTradeTax/ram:TypeCode` | Static "VAT" | - | Tax type |
| `ram:ApplicableTradeTax/ram:CategoryCode` | Derived from tax scenario | Logic | Tax category code (see header tax) |
| `ram:ApplicableTradeTax/ram:RateApplicablePercent` | `TaxRate` or 0 | `InvoiceItemCompact`/`CreditVoucherItemCompact` | VAT percentage (0 if `ExclusiveOfVAT`) |
| `ram:SpecifiedTradeSettlementLineMonetarySummation/ram:LineTotalAmount` | `NetPriceTotalFCComplete` | `BookKeepingReceiptItem` | Line net total amount |
**Title Position Handling:**
- Title positions (`Kind = TitlePosition`, `Expanded = false`) are exported as collapsed items
- Child items (`Visible != Visible` or `Indent > 0`) are aggregated into parent title position
- Title position quantity is always 1
- Title position net price equals net total
- Mixed tax rates in title positions cause error
**Item Filtering:**
- Only exports: `Article`, `CustomerDiscount`, or collapsed `TitlePosition` items
- Must be `Visible = Visible` or `Indent = 0`
- Ordered by `InternalPosition`
---
## Data Sources Summary
### Primary Tables
| Table/Entity | Purpose | Key Fields |
|--------------|---------|------------|
| `IBookKeepingReceipt` | Receipt header | Number, Date, AddressNumber, DueDate, CurrencyISOCode, NetPriceFCComplete, TaxPriceFCComplete |
| `ReceiptReceiver` | Structured receiver address | CompanyName, AdditionalAddressSupplement, Department, ContactDepartment, ContactName, Street, HouseNumber, PostOfficeBox, Zip, City, CountryI3D |
| `BookKeepingReceiptItem` | Receipt line items | Position, Text, Articlecode, EANCode, NetPrice, QuantityComplete, TaxRate |
| `InvoiceItemCompact` | Invoice item details | NetPrice, NetPriceTotalFCComplete, TaxPriceTotalFCComplete, VATRate, QuantityComplete |
| `CreditVoucherItemCompact` | Credit voucher item details | NetPrice, NetPriceTotalFC, TaxPriceTotalFC, VATRate, QuantityComplete |
| `Mandator` | Own company | Name, CEO, TaxIDNumber, HRB, Street, City, PostCode, Phone, EMail, SepaIdentificationNumber |
| `Branch` | Own branch | Name, Street, City, ZipCode, PhoneNumber, EMail, CountryI3D |
| `Country` | Country data | CountryCode, EUMember |
| `BankAccount` | Bank account | Iban, AuthorizationNumber |
| `Article` | Article master data | ArticleUnit (for UN/ECE code) |
| `ReceiptInvoice` | Invoice entity | For credit voucher references |
| `ReceiptCreditVoucher` | Credit voucher entity | For origin tracking |
### Derived/Calculated Fields
| Concept | Calculation | Source |
|---------|-------------|--------|
| Tax category codes | Based on tax rate, reverse charge, country flags | Logic in `GetTaxCategoryCode()` |
| Tax exemption reasons | Based on tax scenario | Logic in `GetTaxExemptionReason()` |
| Structured address lines | Intelligent building from ReceiptReceiver | Logic in receiver address building (max 3 lines) |
| OwnCountryCode | Country code for trade type | Loaded from `Country` via `CountryI3D` (default: "DE") |
| Billing period | Min/Max from contract billing info | `ContractBL.GetContractBillingPeriodForInvoiceItem()` |
| Bank selection | Settings + customer override | `ReceiptInvoiceSettings.UseMandatorBankForInvoice` + `AccountCustomer.MandatorBank` |
| Skonto text | Payment condition formatting | `AssetConditionBL.GetPaymentConditionSkontoInBR_DE_18Format()` |
| UN/ECE code | Article unit lookup | `Article.ArticleUnit.UNECECode` |
---
## Special Cases & Business Logic
### Structured Receiver Address
The system uses the structured `ReceiptReceiver` entity to build multi-line addresses:
- **Maximum 3 lines** for Name and AddressLine1-3
- **Intelligent building** based on available fields (CompanyName, AdditionalAddressSupplement, Department, ContactDepartment, ContactName, Street)
- **Configurable department order** via `ReceiverContactDepartmentFirst` setting
- **Post office box handling** with `HasPostOfficeBox` flag
- **Fallback support** to legacy `IBookKeepingReceipt` fields if `ReceiptReceiver` is null
### Negative Prices
ZUGFeRD does not support negative prices. The system:
1. Makes `NetPrice` positive
2. Negates `Quantity` instead
3. Maintains correct calculation
### Title Positions
- Collapsed title positions are exported with aggregated child item values
- Expanded title positions are not supported (error)
- Mixed tax rates in title positions are not supported (error)
- Default tax rate for title positions without items: 19%
- Barcodes from child items are aggregated into the parent title position
### Payment Condition
Determined by SEPA active status:
- `IsSepaActive = true` → `DirectDebit` (BG-19)
- `IsSepaActive = false` → `BankTransfer` (BG-17)
### Country Trade Flags
Used for tax category determination:
- `IsInlandTrade`: Buyer country = Seller country
- `IsEUTrade`: Buyer country is EU member
### Validation Tolerances
- Net price difference tolerance: ±3.00 (between header and sum of positions)
- Gross price difference tolerance: ±3.00
- Within tolerance: Warning logged, value corrected
- Exceeding tolerance: Error, export fails
---
## Implementation Notes
### File Encoding
- XML files are generated with UTF-8 encoding
- BOM (Byte Order Mark) is removed from export (first 3 bytes stripped)
### Date Formats
- Standard date: `yyyyMMdd` (format code "102")
- Long date: `yyyy-MM-ddThh:mm:ss`
### Amount Formatting
- All amounts use "F2" format (2 decimal places)
- Culture: en-US (decimal separator: dot)
### Namespace Prefixes
- `rsm`: CrossIndustryInvoice
- `ram`: ReusableAggregateBusinessInformationEntity
- `udt`: UnqualifiedDataType
- `qdt`: QualifiedDataType
### Code References
- Main generator: `InvoiceZugferdBL.cs:117-158` (GenerateZugferdFile)
- Export item builder: `InvoiceZugferdBL.cs:237-434` (GetZugferdExportItem)
- XML document generator: `InvoiceZugferdBL.cs:727-750` (DoGenerateZugferdXRechnungXmlDocument)
- Tax logic: `InvoiceZugferdBL.cs:1369-1400` (GetTaxCategoryCode, GetTaxExemptionReason)
---
## Version History
| Version | Notes |
|---------|-------|
| 1.2 | Added: Structured `ReceiptReceiver` support for buyer address, `OwnCountryCode` for trade type determination |
| 1.1 | Added: Barcode/serial number export in line items via `ApplicableProductCharacteristic` |
| 1.0 | Initial documentation covering XRechnung 3.0.1 implementation |