Add hierarchical invoice position numbering (ADR 0017)
Adds the InvoiceLineNumbering class that InvoiceDraftCalculator.
RecomputePositions, InvoiceSetPricing.AssignPositionLabels and their
callers already depend on (this dependency was pushed in ecc74cf
without the class itself, breaking the build) — position labels are
now hierarchical ("1", "1.1", "1.2", …) matching a line's ADR 0016
nesting level, computed by one pure, shared algorithm for both the
online editor and the PDF so they can never disagree (ADR 0008).
Also fixes an InvoiceDraftServiceTests assertion that still expected
the old flat integer position field; it is a string label now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1186,7 +1186,7 @@ public class InvoiceDraftServiceTests
|
||||
Assert.Equal("B", (string)LineOf(s2!, "x")["SetItmId"]!);
|
||||
Assert.Equal("", s2!.History[0].OldValue);
|
||||
Assert.StartsWith("before:m1", s2.History[0].NewValue);
|
||||
Assert.Equal(3, LineOf(s2, "x")["p"]!.Value<int>());
|
||||
Assert.Equal("1.1.1", (string)LineOf(s2, "x")["p"]!); // hierarchical label (ADR 0017): lvl2 under B (1.1)
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using Fuchs.intranet;
|
||||
using Xunit;
|
||||
|
||||
namespace Fuchs.Tests;
|
||||
|
||||
/// <summary>Pure unit tests for the hierarchical position-numbering algorithm (ADR 0017).</summary>
|
||||
public class InvoiceLineNumberingTests
|
||||
{
|
||||
[Fact]
|
||||
public void Compute_EmptyInput_ReturnsEmpty()
|
||||
{
|
||||
Assert.Empty(InvoiceLineNumbering.Compute(new List<(bool, int)>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_FlatSequence_NumbersSequentially()
|
||||
{
|
||||
var input = new List<(bool Numbered, int Level)> { (true, 0), (true, 0), (true, 0) };
|
||||
Assert.Equal(new[] { "1", "2", "3" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_AllUnnumbered_ReturnsAllBlank()
|
||||
{
|
||||
var input = new List<(bool Numbered, int Level)> { (false, 0), (false, 0) };
|
||||
Assert.Equal(new[] { "", "" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_OneLevelOfNesting_ProducesSubsidiaryNumbers()
|
||||
{
|
||||
// header, member, member, top-level item
|
||||
var input = new List<(bool Numbered, int Level)> { (true, 0), (true, 1), (true, 1), (true, 0) };
|
||||
Assert.Equal(new[] { "1", "1.1", "1.2", "2" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_TwoLevelsOfNesting_ProducesDeepLabels()
|
||||
{
|
||||
// A(0), B(1, nested set), m1(2), m2(2), m3(1), x(0)
|
||||
var input = new List<(bool Numbered, int Level)>
|
||||
{
|
||||
(true, 0), (true, 1), (true, 2), (true, 2), (true, 1), (true, 0)
|
||||
};
|
||||
Assert.Equal(new[] { "1", "1.1", "1.1.1", "1.1.2", "1.2", "2" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_TextLinesInterleaved_GetNoLabelAndDoNotConsumeACounterSlot()
|
||||
{
|
||||
var input = new List<(bool Numbered, int Level)> { (true, 0), (false, 0), (true, 1), (false, 1), (true, 0) };
|
||||
Assert.Equal(new[] { "1", "", "1.1", "", "2" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_LevelJumpWithoutIntermediateLevelPresent_StillProducesAStableLabel()
|
||||
{
|
||||
// A level-2 line appears with no level-1 ancestor ever seen (defensive — shouldn't happen
|
||||
// given InvoiceSetStructure's guarantees, but the algorithm must not throw or misbehave).
|
||||
var input = new List<(bool Numbered, int Level)> { (true, 0), (true, 2) };
|
||||
Assert.Equal(new[] { "1", "1.0.1" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_ReturningToAShallowerLevel_TruncatesDeeperCounters()
|
||||
{
|
||||
var input = new List<(bool Numbered, int Level)> { (true, 0), (true, 1), (true, 2), (true, 1) };
|
||||
Assert.Equal(new[] { "1", "1.1", "1.1.1", "1.2" }, InvoiceLineNumbering.Compute(input));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user