SuggestCustomDescriptions

pull/1/head
DJh2o2 2023-03-02 21:13:27 +07:00
parent 8b16a23f80
commit c9653983aa
6 changed files with 260 additions and 431 deletions

@ -1,211 +0,0 @@
# Entfernen Sie die folgende Zeile, wenn Sie EDITORCONFIG-Einstellungen von höheren Verzeichnissen vererben möchten.
root = true
# C#-Dateien
[*.cs]
#### Wichtige EditorConfig-Optionen ####
# Einzüge und Abstände
indent_size = 4
indent_style = space
tab_width = 4
# Einstellungen für neue Zeilen
end_of_line = crlf
insert_final_newline = false
#### .NET-Codierungskonventionen ####
# Using-Direktiven organisieren
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this.- und Me.-Einstellungen
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Einstellungen für Sprachschlüsselwörter und BCL-Typen
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Einstellungen für Klammern
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
# Einstellungen für Modifizierer
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Einstellungen für Ausdrucksebene
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Einstellungen für Felder
dotnet_style_readonly_field = true
# Einstellungen für Parameter
dotnet_code_quality_unused_parameters = all
# Unterdrückungseinstellungen
dotnet_remove_unnecessary_suppression_exclusions = none
#### C#-Codierungskonventionen ####
# Var-Einstellungen
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Ausdruckskörpermember
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Einstellungen für den Musterabgleich
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Einstellungen für NULL-Überprüfung
csharp_style_conditional_delegate_call = true
# Einstellungen für Modifizierer
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
# Einstellungen für Codeblöcke
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = false
# Einstellungen für Ausdrucksebene
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_pattern_local_over_anonymous_function = true
csharp_style_prefer_index_operator = false
csharp_style_prefer_range_operator = false
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# Einstellungen für using-Anweisungen
csharp_using_directive_placement = outside_namespace
#### C#-Formatierungsregeln ####
# Einstellungen für neue Zeilen
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Einstellungen für Einrückung
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Einstellungen für Abstände
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Umbrucheinstellungen
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Benennungsstile ####
# Benennungsregeln
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbolspezifikationen
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Benennungsstile
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
[*.{cs,vb}]
indent_style = tab
end_of_line = lf

@ -1,128 +0,0 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\Coding\Gremlin codebase based on best match to current usage at 19.07.2021
# There already existed an .editorconfig file in this directory. Copy rules from this .editorconfig.inferred file to the existing .editorconfig file as desired to have them take effect at this location.
# You can modify the rules from these initially generated values to suit your own policies
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
[*.cs]
#Core editorconfig formatting - indentation
#use soft tabs (spaces) for indentation
indent_style = space
#Formatting - indentation options
#indent switch case contents.
csharp_indent_case_contents = true
#csharp_indent_case_contents_when_block
csharp_indent_case_contents_when_block = true
#indent switch labels
csharp_indent_switch_labels = true
#Formatting - new line options
#place catch statements on a new line
csharp_new_line_before_catch = true
#place else statements on a new line
csharp_new_line_before_else = true
#require finally statements to be on a new line after the closing brace
csharp_new_line_before_finally = true
#require members of object intializers to be on separate lines
csharp_new_line_before_members_in_object_initializers = true
#require braces to be on a new line for lambdas, object_collection_array_initializers, properties, accessors, methods, control_blocks, and types (also known as "Allman" style)
csharp_new_line_before_open_brace = lambdas, object_collection_array_initializers, properties, accessors, methods, control_blocks, types
#Formatting - organize using options
#do not place System.* using directives before other using directives
dotnet_sort_system_directives_first = false
#Formatting - spacing options
#require NO space between a cast and the value
csharp_space_after_cast = false
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#remove space within empty argument list parentheses
csharp_space_between_method_call_empty_parameter_list_parentheses = false
#remove space between method call name and opening parenthesis
csharp_space_between_method_call_name_and_opening_parenthesis = false
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
csharp_space_between_method_call_parameter_list_parentheses = false
#remove space within empty parameter list parentheses for a method declaration
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on single line
csharp_preserve_single_line_blocks = true
#leave statements and member declarations on the same line
csharp_preserve_single_line_statements = true
#Style - Code block preferences
#prefer curly braces even for one line of code
csharp_prefer_braces = true:suggestion
#Style - expression bodied member options
#prefer expression-bodied members for accessors
csharp_style_expression_bodied_accessors = true:suggestion
#prefer block bodies for methods
csharp_style_expression_bodied_methods = false:suggestion
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:suggestion
#Style - expression level options
#prefer out variables to be declared inline in the argument list of a method call when possible
csharp_style_inlined_variable_declaration = true:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - Expression-level preferences
#prefer default over default(T)
csharp_prefer_simple_default_expression = true:suggestion
#Style - implicit and explicit types
#prefer explicit type over var in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = false:suggestion
#prefer explicit type over var to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = false:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,private,internal,protected,static,async,readonly,override,virtual:suggestion
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
#Style - qualification options
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion

@ -5,32 +5,26 @@ using static System.String;
namespace Gremlin_BlazorServer.Services;
public abstract class TexService
{
public abstract class TexService {
private static readonly GenericController genericController = new();
public static async Task<StringBuilder?> CreateTex(Quote quote)
{
StringBuilder? texStringBuilder = await Task.Run(() => CreateTexFile(quote));
if (texStringBuilder == null)
return null;
public static async Task<StringBuilder?> CreateTex(Quote quote) {
StringBuilder? texStringBuilder = await CreateTexFile(quote);
if (texStringBuilder == null) return null;
string correctedTex = await Task.Run(() => Replace(texStringBuilder.ToString()));
return new(correctedTex);
}
private static StringBuilder? CreateBriefkopf(Contact recipient, bool tex = false)
{
private static StringBuilder? CreateBriefkopf(Contact recipient, bool tex = false) {
if (recipient.Account?.AccountName == null)
return null;
StringBuilder briefkopf = new();
if (recipient.Gender == (byte)Gender.Male)
{
if (recipient.Gender == (byte)Gender.Male) {
briefkopf.AppendLine($"Herr {recipient.FirstName} {recipient.LastName}");
}
else
{
else {
briefkopf.AppendLine($"Frau {recipient.FirstName} {recipient.LastName}");
}
@ -55,8 +49,7 @@ public abstract class TexService
return briefkopf;
}
private static StringBuilder? CreateTexFile(Quote quote)
{
private static async Task<StringBuilder?> CreateTexFile(Quote quote) {
if (quote.Recipient == null || quote.LineItems == null)
return null;
const string rand = "2"; //RUSettingModel.GetSettingValue(Properties.Settings.Default.userSettingID, "texRand");
@ -105,21 +98,22 @@ public abstract class TexService
+ "\n\\par\n\\begin{flushright}"
);
if (quote.IsPriceInformation)
if (quote.IsPriceInformation) {
texFile.AppendLine(
"\n\\colorbox{AgilentBlau}{\\textcolor{white}{\\textsc{\\Huge{Preisinformation}}}}\n\\end{flushright}\n\\begin{tabular}{p{0.4\\hsize}p{0.6\\hsize}}"
);
else
}
else {
texFile.AppendLine(
"\n\\colorbox{AgilentBlau}{\\textcolor{white}{\\textsc{\\Huge{Angebot}}}}\n\\end{flushright}\n\\begin{tabular}{p{0.4\\hsize}p{0.6\\hsize}}"
);
}
texFile.AppendLine("\n &\n\\multirow{4}{*}{" + "\n\\begin{tabular}{|ll|}" + "\n\\hline");
texFile.AppendLine($"\\textbf{{Angebotsnummer:}}&{quote.QuotationNumber}\\\\");
texFile.Append($"Angebotdatum:&\\today\\\\\nAngebotsgültigkeit:&{quote.ValidFor} Tage\\\\");
if (quote.SalesRep != null)
{
if (quote.SalesRep != null) {
texFile.AppendLine(
$"\\textbf{{Ansprechpartner:}}&{quote.SalesRep.FirstName} {quote.SalesRep.LastName}\\\\"
);
@ -137,17 +131,15 @@ public abstract class TexService
texFile.AppendLine("&\\\\\n&\\\\\n\\end{tabular}\n\\vspace{1cm}\\par ");
//Anrede
if (quote.Recipient.Gender == (byte)Gender.Male)
{
if (quote.Recipient.Gender == (byte)Gender.Male) {
texFile.AppendLine($"Sehr geehrter Herr {quote.Recipient.LastName},\\par ");
}
else
{
else {
texFile.AppendLine($"Sehr geehrte Frau {quote.Recipient.LastName},\\par ");
}
//Anschreiben
texFile.AppendLine(CreateCoverletter(quote));
texFile.AppendLine(await CreateCoverletter(quote));
//RB-Disclaimer
if (quote.QuoteContainsRb)
@ -157,18 +149,15 @@ public abstract class TexService
texFile.AppendLine("\\begin{center}");
texFile.AppendLine("\\begin{longtable}");
if (quote.ShowSinglePrices)
{
if (!quote.ShowDiscounts)
{
if (quote.ShowSinglePrices) {
if (!quote.ShowDiscounts) {
//mit Einzelpreisen
texFile.AppendLine("{| cp{0.71\\textwidth} cr |} \\hline");
texFile.AppendLine(
@"\textbf{\#} & \textbf{Produktbeschreibung} (Produktnummer) & \textbf{Menge} & \textbf{Preis}\\ \hline \endhead"
);
}
else if (quote.ShowDiscounts)
{
else if (quote.ShowDiscounts) {
//mit Einzelpreisen und Discounts
texFile.AppendLine("{| cp{0.595\\textwidth} crr |} \\hline");
texFile.AppendLine(
@ -176,8 +165,7 @@ public abstract class TexService
);
}
}
else
{
else {
//ohne Einzelpreise
texFile.AppendLine("{| cp{0.83\\textwidth} c |} \\hline");
texFile.AppendLine(
@ -185,13 +173,11 @@ public abstract class TexService
);
}
foreach (LineItem lI in quote.LineItems)
{
foreach (LineItem lI in quote.LineItems) {
string lineItemTex = string.Empty;
CustomDescription cD = GetCustomDescription(lI);
CustomDescription cD = await GetCustomDescription(lI);
switch (quote.ShowSinglePrices)
{
switch (quote.ShowSinglePrices) {
case true when !quote.ShowDiscounts:
//mit Einzelpreisen
lineItemTex =
@ -199,14 +185,15 @@ public abstract class TexService
? $"{lI.Position} &\\textbf{{{cD.Heading}}} ({lI.ProductNumber}\\#{lI.OptionNumber})\\newline {cD.DescriptionText}&{lI.Amount}&\\SI{{{lI.Total}}}{{\\sieuro}}\\\\"
: $"{lI.Position} &\\textbf{{{cD.Heading}}} ({lI.ProductNumber})\\newline {cD.DescriptionText}&{lI.Amount}&\\SI{{{lI.Total}}}{{\\sieuro}}\\\\";
break;
case true:
{
if (quote.ShowDiscounts)
case true: {
if (quote.ShowDiscounts) {
//mit Einzelpreisen und Discounts
lineItemTex =
lI.OptionNumber != ""
? $"{lI.Position} &\\textbf{{{cD.Heading}}} ({lI.ProductNumber}\\#{lI.OptionNumber})\\newline {cD.DescriptionText}\\newline Listenpreis: \\SI{{{lI.ListPrice}}}{{\\sieuro}}&{lI.Amount}&\\SI{{{lI.TotalDiscount}}}{{\\%}}&\\SI{{{lI.Total}}}{{\\sieuro}}\\\\"
: $"{lI.Position} &\\textbf{{{cD.Heading}}} ({lI.ProductNumber})\\newline {cD.DescriptionText}\\newline Listenpreis: \\SI{{{lI.ListPrice}}}{{\\sieuro}}&{lI.Amount}&\\SI{{{lI.TotalDiscount}}}{{\\%}}&\\SI{{{lI.Total}}}{{\\sieuro}}\\\\";
}
break;
}
case false:
@ -241,8 +228,7 @@ public abstract class TexService
);
//mit Mehrwertsteuer
if (quote.ShowBrutto)
{
if (quote.ShowBrutto) {
texFile.AppendLine(
$"\\textbf{{Umsatzsteuer ({quote.Vat}\\%)}} & \\SI{{{quote.TotalVat}}}{{\\sieuro}}\\\\"
);
@ -274,8 +260,7 @@ public abstract class TexService
return texFile;
}
private static string CreateRbDisclaimer(Quote quote)
{
private static string CreateRbDisclaimer(Quote quote) {
if (quote.LineItems == null)
return Empty;
@ -300,8 +285,7 @@ public abstract class TexService
return rbDisclaimer;
}
private static string Create3PpDisclaimer(Quote quote)
{
private static string Create3PpDisclaimer(Quote quote) {
if (quote.LineItems == null)
return Empty;
@ -313,15 +297,14 @@ public abstract class TexService
List<LineItem> lineItemsWith3Pp = quote.LineItems
.Where(lI => lI.ProductLine == "3PP")
.ToList();
for (int i = 0; i < lineItemsWith3Pp.Count; i++)
if (i < lineItemsWith3Pp.Count - 1)
{
for (int i = 0; i < lineItemsWith3Pp.Count; i++) {
if (i < lineItemsWith3Pp.Count - 1) {
dreipp += $"{lineItemsWith3Pp[i].ProductNumber}, ";
}
else
{
else {
dreipp += $"{lineItemsWith3Pp[i].ProductNumber}";
}
}
//Get all 3PP Supplier
//List<Supllier> supllier3PP = lineItemWith3PP.ProductLine.Supplier;
dreipp +=
@ -334,25 +317,18 @@ public abstract class TexService
return dreipp;
}
private static string GetCoverletterRow(LineItem lineItem)
{
CustomDescription customDescription = GetCustomDescription(lineItem);
if (customDescription.CoverletterText == "")
{
return customDescription == null
private static async Task<string> GetCoverletterRow(LineItem lineItem) {
CustomDescription cD = await GetCustomDescription(lineItem);
return cD.CoverletterText == ""
? cD == null
? Empty
: $"\\item {customDescription.Heading} (\\#{lineItem.Position})\n";
}
else
{
return customDescription == null
: $"\\item {cD.Heading} (\\#{lineItem.Position})\n"
: cD == null
? Empty
: $"\\item {customDescription.CoverletterText} (\\#{lineItem.Position})\n";
}
: $"\\item {cD.CoverletterText} (\\#{lineItem.Position})\n";
}
private static string CreateCoverletter(Quote quote)
{
private static async Task<string> CreateCoverletter(Quote quote) {
bool subitem = false;
string coverLetter =
$"nachfolgend erhalten Sie Ihr gewünschtes Angebot über ein(e) {quote.Description}.\\\\\n"
@ -362,29 +338,24 @@ public abstract class TexService
if (quote.LineItems == null)
return Empty;
foreach (LineItem lineItem in quote.LineItems)
{
if (lineItem.OptionNumber == "")
{
foreach (LineItem lineItem in quote.LineItems) {
if (lineItem.OptionNumber == "") {
//Hauptitem
if (subitem)
{
if (subitem) {
//vorheriges Subitem schließen
coverLetter += "\\end{itemize}\n";
subitem = false;
}
}
else
{
if (!subitem)
{
else {
if (!subitem) {
//neues Subitem
subitem = true;
coverLetter += "\\begin{itemize}\n";
}
}
coverLetter += GetCoverletterRow(lineItem);
coverLetter += await GetCoverletterRow(lineItem);
}
if (subitem)
@ -401,8 +372,7 @@ public abstract class TexService
return coverLetter;
}
private static string Replace(string text)
{
private static string Replace(string text) {
if (text == "")
return text;
@ -411,22 +381,20 @@ public abstract class TexService
return text;
}
private static CustomDescription GetCustomDescription(LineItem lineItem)
{
CustomDescription? customDescription = genericController.Get<CustomDescription>(
cD =>
cD.ProductNumber.Equals(lineItem.ProductNumber)
&& cD.OptionNumber.Equals(lineItem.OptionNumber)
private static async Task<CustomDescription> GetCustomDescription(LineItem lineItem) {
CustomDescription? customDescription = await genericController.GetAsync<CustomDescription>(
cD => cD.ProductNumber.Equals(lineItem.ProductNumber)
&& cD.OptionNumber.Equals(lineItem.OptionNumber, StringComparison.Ordinal)
);
if (customDescription == null)
{
Console.WriteLine(
$"Keine CustomDescription für {lineItem.ProductNumber}#{lineItem.OptionNumber} verfügbar! Verwende SarShortDescription..."
);
if (customDescription == null) {
Console.WriteLine($"Keine CustomDescription für {lineItem.ProductNumber}#{lineItem.OptionNumber} verfügbar! Verwende SapShortDescription...");
List<CustomDescription>? suggestedCustomDescriptions = await SuggestCustomDescriptions(lineItem);
if (suggestedCustomDescriptions == null) {
Console.WriteLine($"Keine Vorschläge mit ähnlicher Produktnummer oder Optionsnummer gefunden!");
}
//TODO generate new CustomDescription
customDescription = new()
{
customDescription = new() {
Heading = lineItem.SapShortDescription,
CoverletterText = lineItem.SapShortDescription,
DescriptionText = lineItem.SapLongDescription
@ -435,4 +403,14 @@ public abstract class TexService
return customDescription;
}
private static async Task<List<CustomDescription>?> SuggestCustomDescriptions(LineItem lineItem) {
IList<CustomDescription>? fromProductNumber = await genericController.GetAllAsync<CustomDescription>(cD => cD.ProductNumber.Equals(lineItem.ProductNumber, StringComparison.Ordinal));
IList<CustomDescription>? fromOptionNumber = await genericController.GetAllAsync<CustomDescription>(cD => cD.OptionNumber.Equals(lineItem.OptionNumber, StringComparison.Ordinal));
if (fromOptionNumber == null && fromProductNumber == null) return null;
if (fromOptionNumber == null) return fromProductNumber.ToList();
if (fromProductNumber == null) return fromOptionNumber.ToList();
return fromProductNumber.Union(fromOptionNumber).ToList();
}
}

@ -0,0 +1,169 @@
\documentclass[a4paper,ngerman,parskip,10pt]{scrlttr2}
\usepackage{lmodern}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{babel}
\usepackage[hidelinks]{hyperref}
\usepackage[left=2cm, right=2cm, top=2cm, bottom=2cm]{geometry}
\usepackage[table]{xcolor}
\usepackage[right]{{eurosym}}
\usepackage[locale=DE]{{siunitx}}
\usepackage{{scrlayer-scrpage}}
\usepackage{{lastpage}}
\usepackage{{graphicx}}
\usepackage{{multirow}}
\usepackage{{longtable}}
\usepackage{{enumitem}}
\usepackage{{fp, xstring, spreadtab, numprint}}
\DeclareSIUnit{{\sieuro}}{{\mbox{{\euro}}}}
\rohead{DE-83PE89-323-223}
\cfoot{Seite \thepage/\pageref{LastPage}}
\sisetup{round-integer-to-decimal,round-precision=2,round-mode=places}
\newcommand{\produkttitel}[1]{\textsc{#1}}
\renewcommand{\arraystretch}{1.2}
\definecolor{AgilentBlau}{HTML}{0085d5}
\setlist{noitemsep}
\begin{document}
\begin{tabular}{p{0.4\hsize}p{0.5\hsize}}
\multirow{4}{*}{\includegraphics[width=0.9\hsize]{agilentLogo.png}}
&\normalsize{Agilent Technologies Deutschland GmbH}\\
&\normalsize{Life Sciences \& Chemical Analysis}\\
&\normalsize{Hewlett-Packard-Str. 8}\\
&\normalsize{D-76337 Waldbronn}
\end{tabular}
\par
\begin{flushright}
\colorbox{AgilentBlau}{\textcolor{white}{\textsc{\Huge{Angebot}}}}
\end{flushright}
\begin{tabular}{p{0.4\hsize}p{0.6\hsize}}
&
\multirow{4}{*}{
\begin{tabular}{|ll|}
\hline
\textbf{Angebotsnummer:}&DE-83PE89-323-223\\
Angebotdatum:&\today\\
Angebotsgültigkeit:&60 Tage\\\textbf{Ansprechpartner:}&Sascha Woitschetzki\\
Telefon: &+49 208 74129134\\
Mobil:&+49 176 22285334\\
E-Mail:&\href{mailto:sascha.woitschetzki@non.agilent.com}{sascha.woitschetzki@non.agilent.com}\\
\textbf{Auftragsannahme:}&\href{mailto:salesservices\_germany@agilent.com}{salesservices\_germany@agilent.com}\\
\hline
\end{tabular}
}\\
Herr Christoph Dickhoven
\\
BBT Biotech GmbH
\\
Arnold-Sommerfeld-Ring 28
\\
52499 Baesweiler
\\
&\\
&\\
\end{tabular}
\vspace{1cm}\par
Sehr geehrter Herr Dickhoven,\par
nachfolgend erhalten Sie Ihr gewünschtes Angebot über ein(e) UHPLC-QTOF.\\
Es umfasst im Einzelnen:
\begin{itemize}
\item 1290 Infinity II Flexible Pumpe (\#1)
\begin{itemize}
\item ULD-Kit (\#2)
\item LC/MS-Kit (\#3)
\item InfinityLab StaySafe Starter-Kit (\#4)
\item Poroshell 120 Säule (\#5)
\end{itemize}
\item 1290 Infinity II Multisampler (\#6)
\begin{itemize}
\item Nutzung vorhandene Lizenz (\#7)
\item Multi-Wash (\#8)
\item Schublade (1H) (\#9)
\end{itemize}
\item Säulenthermostat (\#10)
\begin{itemize}
\item ULD-Wärmetauscher (\#11)
\item Säulenidentifikations-Kit (\#12)
\item Agilent Temperaturaequilibrierungsgeraet (\#13)
\end{itemize}
\item 1290 Infinity II HPLC mit Zusatzfunktionen (\#14)
\begin{itemize}
\item Einführung (\#15)
\end{itemize}
\item 6530C Q-TOF LC/MS (\#16)
\item MassHunter WS SW fuer LC/(Q)TOF (\#17)
\item MassHunter BioConfirm Workstation-SW (\#18)
\item Standard-PC fuer MassHunter Workstation (\#19)
\item Training für Anwender (\#20)
\end{itemize}
Für Rückfragen und Änderungswünsche stehe ich Ihnen gerne zur Verfügung.\par
Mit freundlichen Grüßen\\
\includegraphics[width = 5cm]{signWoitschetzki.png}
\vspace{1cm} \\
\begin{center}
\begin{longtable}
{| cp{0.595\textwidth} crr |} \hline
\textbf{\#} & \textbf{Produktbeschreibung} (Produktnummer) & \textbf{Menge} & \textbf{Discount} & \textbf{Preis}\\ \hline \endhead
1 &\textbf{1290 Infinity II Flexible Pumpe} (G7104A)\newline 1290 II Technologie für höchste Präzision und Genauigkeit. \newline Quaternäre Pumpe (bis zu 1300 bar und 5 ml/min) mit integriertem Entgaser, Niederdruckmischer, aktiver Kolbenhinterspülung, ISET und BlendAssist. Inkl. Werkzeug-Kit und Säule (RRHD Eclipse Plus C18, 50 x 2,1 mm, 1,8 µm).\newline Listenpreis: \SI{42463}{\sieuro}&1&\SI{47}{\%}&\SI{22505.39}{\sieuro}\\
2 &\textbf{ULD-Kit} (G7104A\#006)\newline Ultra Low Dispersion Kit für Agilent 1290 Infinity II LCs mit Multisampler. Enthält 0,075 mm Edelstahlkapillaren und ULD-Quick Connect Wärmetauscher (0,6 µl).\newline Listenpreis: \SI{3246}{\sieuro}&1&\SI{47}{\%}&\SI{1720.38}{\sieuro}\\
3 &\textbf{LC/MS-Kit} (G7104A\#033)\newline Kit für 1290 Infinity II  LC-System mit MSD. Inklusive Lösemittelschläuchen und Fittingen aus hochinertem Material und Lösemittelansaugfiltern aus Edelstahl.\newline Listenpreis: \SI{269}{\sieuro}&1&\SI{47}{\%}&\SI{142.57}{\sieuro}\\
4 &\textbf{InfinityLab StaySafe Starter-Kit} (G7104A\#034)\newline Starter-Kit für maximale Sicherheit beim Arbeiten mit HPLC-Eluenten. Enthält Lösungsmittelflaschen, InfinityLab Stay Safe-Verschlüsse mit Indikatorstreifen zum Herausfiltern von Lösungsmitteldämpfen und passende Fittinge.\newline Listenpreis: \SI{705}{\sieuro}&1&\SI{47}{\%}&\SI{373.65}{\sieuro}\\
5 &\textbf{Poroshell 120 Säule} (G7104A\#096)\newline InfinityLab Poroshell 120 EC-C18 2.1 x 50 mm, 1.9 µm, mit Säulen-ID-Tag.\newline Listenpreis: \SI{1}{\sieuro}&1&\SI{47}{\%}&\SI{0.53}{\sieuro}\\
6 &\textbf{1290 Infinity II Multisampler} (G7167B)\newline 1290 Infinity II Multisampler zur Verwendung bei bis zu 1300 bar für Wellplates, Vials, Eppendorfs und individuelle Probenbehälter. Standardinjektionszyklus < 10 Sek., Injektionsvolumen 0.1 20 µl.\newline Inkl. 1 Probenschublade, 2x 54 2,0 ml Vial-Probenträgern und Nadelspülanschluss zur Minimierung von Verschleppungen. Optional aufrüstbar mit Doppelnadel-Konfiguration (überlappende Injektionen, unterschiedliche Injektionsvolumina, getrennte Flusswege für Standards und Proben), Multi-Wash (Nadelspülung mit bis zu 3 Lösungsmitteln) oder Multi-Draw (Erhöhung des Injektionsvolumens).\newline Listenpreis: \SI{32125}{\sieuro}&1&\SI{47}{\%}&\SI{17026.25}{\sieuro}\\
7 &\textbf{Nutzung vorhandene Lizenz} (G7167B\#060)\newline \newline Listenpreis: \SI{-1793}{\sieuro}&1&\SI{47}{\%}&\SI{-950.29}{\sieuro}\\
8 &\textbf{Multi-Wash} (G7167B\#112)\newline Zur Minimierung der Verschleppung durch Spülung der Nadelaußenseite und des Nadelsitzes mit bis zu drei Lösungsmitteln. \newline Erweitert Multisampler um eine Hochleistungspumpe, ein Lösungsmittelauswahlventil und einen Hochdruckspülkopf.\newline Listenpreis: \SI{6204}{\sieuro}&1&\SI{47}{\%}&\SI{3288.12}{\sieuro}\\
9 &\textbf{Schublade (1H)} (G7167B\#131)\newline 2 Schubladen einfacher Höhe (1H), mit jeweils 2 Positionen für flache Mikrotiterplatten (MTP; max. 19 mm Plattenhöhe).\newline Listenpreis: \SI{2344}{\sieuro}&1&\SI{47}{\%}&\SI{1242.32}{\sieuro}\\
10 &\textbf{Säulenthermostat} (G7116B)\newline 1290 Infinity II Thermostat für bis zu 8 Säulen, Temperaturbereich: 4 bis 110 °C. Mit QuickConnect Wärmetauscher (V = 1.6 µl), QuickConnect Fitting und zwei QuickTurn-Fittings. Ventilantrieb optional.\newline Listenpreis: \SI{8472}{\sieuro}&1&\SI{47}{\%}&\SI{4490.16}{\sieuro}\\
11 &\textbf{ULD-Wärmetauscher} (G7116B\#064)\newline Quick-Connect Wärmetauscher für ultraniedrige Dispersion (1 µl).\newline Listenpreis: \SI{393}{\sieuro}&1&\SI{47}{\%}&\SI{208.29}{\sieuro}\\
12 &\textbf{Säulenidentifikations-Kit} (G7116B\#072)\newline Upgrade für 1290 Infinity II Multicolumn-Thermostats. Identifizierung von bis zu 8 Säulen über RFID-Chips.\newline Listenpreis: \SI{1255}{\sieuro}&1&\SI{47}{\%}&\SI{665.15}{\sieuro}\\
13 &\textbf{Agilent Temperaturaequilibrierungsgeraet} (G7116B\#073)\newline \newline Listenpreis: \SI{657}{\sieuro}&1&\SI{47}{\%}&\SI{348.21}{\sieuro}\\
14 &\textbf{1290 Infinity II HPLC mit Zusatzfunktionen} (SYS-LC-1290IIE)\newline \newline Listenpreis: \SI{0}{\sieuro}&1&\SI{20}{\%}&\SI{0}{\sieuro}\\
15 &\textbf{Einführung} (SYS-LC-1290IIE\#2A9)\newline Standardeinweisung für neue Anwender im Rahmen der Installation.\newline Listenpreis: \SI{1193}{\sieuro}&1&\SI{20}{\%}&\SI{954.4}{\sieuro}\\
16 &\textbf{6530C Q-TOF LC/MS} (G6530CA)\newline \newline Listenpreis: \SI{375179}{\sieuro}&1&\SI{55}{\%}&\SI{168830.55}{\sieuro}\\
17 &\textbf{MassHunter WS SW fuer LC/(Q)TOF} (M5960AA)\newline \newline Listenpreis: \SI{14076}{\sieuro}&1&\SI{55}{\%}&\SI{6334.2}{\sieuro}\\
18 &\textbf{MassHunter BioConfirm Workstation-SW} (M6025AA)\newline \newline Listenpreis: \SI{18092}{\sieuro}&1&\SI{55}{\%}&\SI{8141.4}{\sieuro}\\
19 &\textbf{Standard-PC fuer MassHunter Workstation} (M6081AA)\newline \newline Listenpreis: \SI{6638}{\sieuro}&1&\SI{55}{\%}&\SI{2987.1}{\sieuro}\\
20 &\textbf{Training für Anwender} (H2620A)\newline Anwendertraining vor Ort, X Teilnehmer, Y Tage.\newline Listenpreis: \SI{7900}{\sieuro}&1&\SI{20}{\%}&\SI{6320}{\sieuro}\\
\hline
\end{longtable}
\end{center}
\vspace{-2cm}
\begin{flushright}
\begin{tabular}{|rr|}
\hline
\textbf{Summe netto} & \SI{244628.38}{\sieuro}\\
\textbf{Versand und Bereitstellungskosten (3\%)} & \SI{3000}{\sieuro}\\
\textbf{Gesamtsumme netto} & \SI{247628.38}{\sieuro}\\
\hline
\end{tabular}
\end{flushright}
Der Betrag versteht sich zzgl. der gesetzlichen Steuern.\\
Diese werden im Rechnungszeitraum auf der Rechnung gesondert ausgewiesen.\\
Zahlungsbedingungen: 30 Tage netto ab Rechnungsdatum.\\
Incoterm (2010) für Lieferungen innerhalb Deutschlands: DDP.
\begin{small}
\textbf{Gewährleistung:}\\
Die Gewährleistung für Zubehör und Ersatzteilprodukte und für Analytik-Hardwareprodukte beträgt 12 Monate.
\textbf{Hinweis:}\\
Für den Verkauf der in diesem Angebot aufgeführten Standard-Produkte und -Services gelten die aktuellen \emph{Agilent Geschäftsbedingungen} und alle sonstigen anwendbaren Zusatzbedingungen sowie zusätzliche Bedingungen, soweit darauf hier Bezug genommen wird. Soweit Produkte oder Services nach speziellen Kundenanforderungen hergestellt, konfiguriert oder angepasst werden, gelten für den Verkauf aller in diesem Angebot aufgeführten Produkte und Services die aktuellen \emph{Agilent Geschäftsbedingungen für kundenspezifische Produkte} und alle sonstigen anwendbaren Zusatzbedingungen sowie zusätzliche Bedingungen, soweit darauf hier Bezug genommen wird. Eine Kopie der maßgeblichen Bedingungen ist entweder beigefügt oder wurde Ihnen bereits zur Verfügung gestellt. Sollten Sie keine Kopie erhalten haben oder eine weitere Kopie benötigen, setzen Sie sich bitte mit uns in Verbindung. Soweit Sie mit Agilent eine gesonderte Vereinbarung getroffen haben, die den Verkauf der in diesem Angebot aufgeführten Produkte und Services umfasst, sind die Bestimmungen dieser Vereinbarung anwendbar. Abweichende oder ergänzende Vereinbarungen, insbesondere widersprechende Geschäftsbedingungen, sind nur gültig, wenn sie ausdrücklich schriftlich vereinbart worden sind. Die angegebenen Daten zur Verfügbarkeit von Produkten und Services sind vorläufig. Die tatsächlichen Lieferzeiten bzw. Lieferperioden werden Ihnen bei Auftragsbestätigung mitgeteilt. Waren, Technologien oder Software, die aus den Vereinigten Staaten von Amerika (\emph{USA}) oder anderen exportierenden Ländern ausgeführt werden, unterliegen den Ausfuhrbestimmungen der USA sowie anderer Rechtsordnungen. Bei Ausfuhr ist der Kunde dafür verantwortlich, dass die anwendbaren Ausfuhrbestimmungen eingehalten werden.
\end{small}
\begin{scriptsize}
Agilent Technologies Deutschland GmbH, Hewlett-Packard-Str. 8, D-76337 Waldbronn\\
Telefon +49 (0)7243-602-0\\
USt.-IdNr.: DE812729296, WEEE-Reg.-Nr. DE 86631749\\
Sitz der Gesellschaft: Waldbronn Amtsgericht Mannheim, HRB 723782\\
Geschäftsführer: Dr. Andreas Kistner (Vorsitzender der Geschäftsführung), Armin Jehle, Norbert Sabatzki, Dr. Knut Wintergerst\\
\href{www.agilent.com}{www.agilent.com}
\end{scriptsize}
\end{document}

@ -0,0 +1,21 @@
# Part Number Opt PL Description Qty Price EUR Breaks EUR Uplift % Total Discount % Net EUR Total EUR Sales Discount YA9% Contractual Discount Y99% Promotion Discount Y07% Demo Discount Y04% PH Code PH Description YMax
1 G7104A 29 1290 Infinity II flexible Pumpe 1 42463 0 0 47 22505.39 22505.39 47 0 0 0 ISL100P1 Pumps
2 G7104A 006 29 Kit f. ultraniedr. Dispersion 1 3246 0 0 47 1720.38 1720.38 47 0 0 0
3 G7104A 033 29 Ultrareines Schlauchkit 1 269 0 0 47 142.57 142.57 47 0 0 0
4 G7104A 034 29 A-Line Stay Safe-Verschl. Starter-Kit 1 705 0 0 47 373.65 373.65 47 0 0 0
5 G7104A 096 29 Poroshell 120 EC-C18, 2,1x50mm, 1,9um 1 1 0 0 47 0.53 0.53 47 0 0 0
6 G7167B 29 1290 Infinity II Mehrfachprobengeber 1 32125 0 0 47 17026.25 17026.25 47 0 0 0 ISL100A1 Autosamplers
7 G7167B 060 29 Nutzung vorhandene Lizenz 1 -1793 0 0 47 -950.29 -950.29 47 0 0 0
8 G7167B 112 29 1290 Infinity Mehrfachspuelfunktion 1 6204 0 0 47 3288.12 3288.12 47 0 0 0
9 G7167B 131 29 Schublade mit einfacher Hoehe (1H) 1 2344 0 0 47 1242.32 1242.32 47 0 0 0
10 G7116B 29 1290 Infinity II Therm. f. mehr. Saeulen 1 8472 0 0 47 4490.16 4490.16 47 0 0 0 ISL100LC1 LC Hardware
11 G7116B 064 29 Quick-Connect WT ultran. Disp. 1 393 0 0 47 208.29 208.29 47 0 0 0
12 G7116B 072 29 Saeulen-ID-Kit f. 1290 Infinity II MCT 1 1255 0 0 47 665.15 665.15 47 0 0 0
13 G7116B 073 29 Agilent Temperaturaequilibrierungsgeraet 1 657 0 0 47 348.21 348.21 47 0 0 0
14 SYS-LC-1290IIE 74 Infinity II 1290 LC Extended System 1 0 0 0 20 0 0 20 0 0 0 TSSYS0SYLC Service Systems - Liquid Chromatography
15 SYS-LC-1290IIE 2A9 74 Standard-Einweisung 1 1193 0 0 20 954.4 954.4 20 0 0 0 TSSTRN Training Services
16 G6530CA 89 6530C Q-TOF LC/MS 1 375179 0 0 55 168830.55 168830.55 55 0 0 0 ISL810QT1 LCMS Q-TOF Hardware
17 M5960AA 89 MassHunter WS SW fuer LC/(Q)TOF 1 14076 0 0 55 6334.2 6334.2 55 0 0 0 ISL800QT2 MS Q-TOF Software
18 M6025AA 89 MassHunter BioConfirm Workstation-SW 1 18092 0 0 55 8141.4 8141.4 55 0 0 0 ISL800QT2 MS Q-TOF Software
19 M6081AA 89 Standard-PC fuer MassHunter Workstation 1 6638 0 0 55 2987.1 2987.1 55 0 0 0 ISL200PC2 PC Bundles
20 H2620A 3 Tage 3 Personen Training vor Ort 1 7900 0 0 20 6320 6320 20 0 0 0
1 # Part Number Opt PL Description Qty Price EUR Breaks EUR Uplift % Total Discount % Net EUR Total EUR Sales Discount YA9% Contractual Discount Y99% Promotion Discount Y07% Demo Discount Y04% PH Code PH Description YMax
2 1 G7104A 29 1290 Infinity II flexible Pumpe 1 42463 0 0 47 22505.39 22505.39 47 0 0 0 ISL100P1 Pumps
3 2 G7104A 006 29 Kit f. ultraniedr. Dispersion 1 3246 0 0 47 1720.38 1720.38 47 0 0 0
4 3 G7104A 033 29 Ultrareines Schlauchkit 1 269 0 0 47 142.57 142.57 47 0 0 0
5 4 G7104A 034 29 A-Line Stay Safe-Verschl. Starter-Kit 1 705 0 0 47 373.65 373.65 47 0 0 0
6 5 G7104A 096 29 Poroshell 120 EC-C18, 2,1x50mm, 1,9um 1 1 0 0 47 0.53 0.53 47 0 0 0
7 6 G7167B 29 1290 Infinity II Mehrfachprobengeber 1 32125 0 0 47 17026.25 17026.25 47 0 0 0 ISL100A1 Autosamplers
8 7 G7167B 060 29 Nutzung vorhandene Lizenz 1 -1793 0 0 47 -950.29 -950.29 47 0 0 0
9 8 G7167B 112 29 1290 Infinity Mehrfachspuelfunktion 1 6204 0 0 47 3288.12 3288.12 47 0 0 0
10 9 G7167B 131 29 Schublade mit einfacher Hoehe (1H) 1 2344 0 0 47 1242.32 1242.32 47 0 0 0
11 10 G7116B 29 1290 Infinity II Therm. f. mehr. Saeulen 1 8472 0 0 47 4490.16 4490.16 47 0 0 0 ISL100LC1 LC Hardware
12 11 G7116B 064 29 Quick-Connect WT ultran. Disp. 1 393 0 0 47 208.29 208.29 47 0 0 0
13 12 G7116B 072 29 Saeulen-ID-Kit f. 1290 Infinity II MCT 1 1255 0 0 47 665.15 665.15 47 0 0 0
14 13 G7116B 073 29 Agilent Temperaturaequilibrierungsgeraet 1 657 0 0 47 348.21 348.21 47 0 0 0
15 14 SYS-LC-1290IIE 74 Infinity II 1290 LC Extended System 1 0 0 0 20 0 0 20 0 0 0 TSSYS0SYLC Service Systems - Liquid Chromatography
16 15 SYS-LC-1290IIE 2A9 74 Standard-Einweisung 1 1193 0 0 20 954.4 954.4 20 0 0 0 TSSTRN Training Services
17 16 G6530CA 89 6530C Q-TOF LC/MS 1 375179 0 0 55 168830.55 168830.55 55 0 0 0 ISL810QT1 LCMS Q-TOF Hardware
18 17 M5960AA 89 MassHunter WS SW fuer LC/(Q)TOF 1 14076 0 0 55 6334.2 6334.2 55 0 0 0 ISL800QT2 MS Q-TOF Software
19 18 M6025AA 89 MassHunter BioConfirm Workstation-SW 1 18092 0 0 55 8141.4 8141.4 55 0 0 0 ISL800QT2 MS Q-TOF Software
20 19 M6081AA 89 Standard-PC fuer MassHunter Workstation 1 6638 0 0 55 2987.1 2987.1 55 0 0 0 ISL200PC2 PC Bundles
21 20 H2620A 3 Tage 3 Personen Training vor Ort 1 7900 0 0 20 6320 6320 20 0 0 0