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,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\shared\Centron.Controls\Centron.Controls.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,139 @@
using CentronSoftware.Centron.Controls.PositionGrid.Helpers;
using CentronSoftware.Centron.Controls.PositionGrid.Interfaces;
namespace Centron.Tests.Controls.PositionGrid
{
/// <summary>
/// Tests for the indent normalization of a cut-out of positions that is pasted into the receipt
/// </summary>
public class PasteIndentNormalizerTests
{
private static (ReceiptArticleKind kind, bool? isExpanded, int indent) Position(int indent)
=> (ReceiptArticleKind.Article, null, indent);
private static (ReceiptArticleKind kind, bool? isExpanded, int indent) PartListHead(int indent)
=> (ReceiptArticleKind.Article, true, indent);
private static (ReceiptArticleKind kind, bool? isExpanded, int indent) Title(int indent)
=> (ReceiptArticleKind.Titleposition, true, indent);
[Fact]
public void CalculateIndents_TitleIndentDoesNotAffectFollowingPartList()
{
// A title with a non-zero source indent must not stay the indent reference for the
// positions after it, otherwise the following part-list head pushes currentIndent below
// zero and its child ends up at indent 0 instead of 1.
var positions = new[]
{
Title(2),
PartListHead(0),
Position(1),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 0, 1 }, indents);
}
[Fact]
public void CalculateIndents_TitleResetsIndentOfPrecedingPartList()
{
var positions = new[]
{
PartListHead(0),
Position(1),
Title(0),
Position(0),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 1, 0, 0 }, indents);
}
[Fact]
public void CalculateIndents_RebasesCutOutTakenFromInsideAPartList()
{
// Cut-out starts at indent 2 because it was copied out of a nested part-list.
var positions = new[]
{
PartListHead(2),
Position(3),
Position(3),
Position(2),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 1, 1, 0 }, indents);
}
[Fact]
public void CalculateIndents_KeepsNestedPartLists()
{
var positions = new[]
{
PartListHead(0),
PartListHead(1),
Position(2),
Position(1),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 1, 2, 1 }, indents);
}
[Fact]
public void CalculateIndents_ClosesAllPartListsLeftAtOnce()
{
// The last position leaves three part-lists at the same time and therefore belongs back
// on the top level.
var positions = new[]
{
PartListHead(0),
PartListHead(1),
PartListHead(2),
Position(0),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 1, 2, 0 }, indents);
}
[Fact]
public void CalculateIndents_ClosesPartListsUpToTheRemainingLevel()
{
// The cut-out drops from indent 3 back to indent 1, so only the two inner part-lists are
// closed and the position stays inside the outermost one.
var positions = new[]
{
PartListHead(0),
PartListHead(1),
PartListHead(2),
Position(3),
Position(1),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 1, 2, 3, 1 }, indents);
}
[Fact]
public void CalculateIndents_FlatPositionsStayAtZero()
{
var positions = new[]
{
Position(0),
Position(0),
Position(0),
};
var indents = PasteIndentNormalizer.CalculateIndents(positions);
Assert.Equal(new[] { 0, 0, 0 }, indents);
}
}
}
@@ -0,0 +1,76 @@
using CentronSoftware.Centron.Controls.PositionGrid.Helpers;
using CentronSoftware.Centron.Controls.PositionGrid.Interfaces;
namespace Centron.Tests.Controls.PositionGrid
{
/// <summary>
/// Tests for the Reverse-Charge threshold (Bagatellgrenze) calculation. Reproduces the
/// scenario from Ticket 163492: informative/alternative positions must not be counted
/// against the threshold, only Standard/Cargo positions do.
/// </summary>
public class ReverseChargeThresholdCalculatorTests
{
[Fact]
public void CalculateThresholdRelevantTotal_ExcludesInformativeAndAlternativePositions()
{
// Ticket 163492 scenario: two Standard positions (3000 + 1999 = 4999) plus one
// informative and two alternative positions (each 3000). Only the Standard net
// total (4999) is relevant for the threshold.
var positions = new (bool isReverseCharge, ReceiptArticlePositionKind kind, decimal totalPrice)[]
{
(true, ReceiptArticlePositionKind.Default, 3000m),
(true, ReceiptArticlePositionKind.Default, 1999m),
(true, ReceiptArticlePositionKind.Informative, 3000m),
(true, ReceiptArticlePositionKind.Alternative, 3000m),
(true, ReceiptArticlePositionKind.Alternative, 3000m),
};
var total = ReverseChargeThresholdCalculator.CalculateThresholdRelevantTotal(positions);
Assert.Equal(4999m, total);
}
[Fact]
public void CalculateThresholdRelevantTotal_IncludesCargoPositions()
{
var positions = new (bool isReverseCharge, ReceiptArticlePositionKind kind, decimal totalPrice)[]
{
(true, ReceiptArticlePositionKind.Default, 2500m),
(true, ReceiptArticlePositionKind.Cargo, 100m),
};
var total = ReverseChargeThresholdCalculator.CalculateThresholdRelevantTotal(positions);
Assert.Equal(2600m, total);
}
[Fact]
public void CalculateThresholdRelevantTotal_IgnoresNonReverseChargePositions()
{
var positions = new (bool isReverseCharge, ReceiptArticlePositionKind kind, decimal totalPrice)[]
{
(true, ReceiptArticlePositionKind.Default, 3000m),
(false, ReceiptArticlePositionKind.Default, 5000m),
};
var total = ReverseChargeThresholdCalculator.CalculateThresholdRelevantTotal(positions);
Assert.Equal(3000m, total);
}
[Theory]
[InlineData(ReceiptArticlePositionKind.Default, true)]
[InlineData(ReceiptArticlePositionKind.Cargo, true)]
[InlineData(ReceiptArticlePositionKind.Alternative, false)]
[InlineData(ReceiptArticlePositionKind.Optional, false)]
[InlineData(ReceiptArticlePositionKind.Informative, false)]
[InlineData(ReceiptArticlePositionKind.OnDemand, false)]
[InlineData(ReceiptArticlePositionKind.OnEffort, false)]
[InlineData(ReceiptArticlePositionKind.NoService, false)]
[InlineData(ReceiptArticlePositionKind.NoServiceOptional, false)]
public void CountsTowardNetTotal_MatchesNetTotalGroupingRules(ReceiptArticlePositionKind kind, bool expected)
{
Assert.Equal(expected, ReverseChargeThresholdCalculator.CountsTowardNetTotal(kind));
}
}
}
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="System.Data.SQLite" Version="1.0.119" />
<!-- Override vulnerable transitive dependencies -->
<PackageReference Include="System.Drawing.Common" Version="10.0.7" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\shared\Centron.Core\Centron.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,44 @@
using JetBrains.Annotations;
namespace Centron.Core.Extensions;
[TestSubject(typeof(StringExtensions))]
public class StringExtensionsTest
{
[Theory]
[InlineData("", "")]
[InlineData(null, "")]
[InlineData("test\r\ntest", "test test")]
[InlineData("test\ntest", "test test")]
[InlineData("test test", "test test")]
public void TestMakeOneLiner(string? input, string expected)
{
var result = input.MakeOneLiner();
Assert.Equal(expected, result);
}
[Theory]
[InlineData("", null)]
[InlineData(null, null)]
[InlineData("\n", null)]
[InlineData("\r\n", null)]
[InlineData("\n\n", null)]
[InlineData("\r\n\r\n", null)]
[InlineData("\r\n \r\n ", null)]
[InlineData("\n \n ", null)]
[InlineData(" ", null)]
[InlineData("First line\nSecond line", "First line")]
[InlineData("First line\r\nSecond line", "First line")]
[InlineData(" \r\nFirst line", "First line")]
[InlineData(" \nFirst line", "First line")]
[InlineData("\r\n\r\nFirst line", "First line")]
[InlineData("\n\nFirst line", "First line")]
[InlineData("Single line text", "Single line text")]
[InlineData("\nStarts with newline", "Starts with newline")]
[InlineData("\r\nStarts with newline", "Starts with newline")]
public void TestTakeFirstLine(string? input, string? expected)
{
var result = input.TakeFirstLine();
Assert.Equal(expected, result);
}
}