Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ follow semantic versioning; release dates are ISO 8601.

### Public API

- **An invoice line carries a mark.** A design that opens each service line with a
glyph — a card for a billing line, a shield for fraud screening, a globe for a hosted
service — had nowhere to say which one, and deriving it from the description would
have been guesswork dressed as a feature. `InvoiceServiceLines.Line` now carries
`icon`, a plain string blank when absent, exactly as `CvEntry.icon` already works on
the CV side: the token means something only to the preset that packages it, and a
preset that draws no marks ignores it. It costs the layout nothing where nothing is
set. Both constructors that predate it — the one before the per-line tax rate and the
one before the mark — are kept explicitly, so existing calls compile and link
unchanged and every line built through them still carries no mark.

- **`CvSkill` carries the level as the document words it.** A rated skill could say how
much — a number in `[0, 1]` a preset draws as dots or a meter — or it could say it in
words by not being a skill at all and living in a `RowsSection` instead. It could not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,42 @@ void theColumnsConstructorThatPredatesTheVatColumnLeavesItBlank() {
}

@Test
void theLineConstructorThatPredatesTheVatRateLeavesItBlank() {
void theLineConstructorThatPredatesTheVatRateLeavesItAndTheMarkBlank() {
InvoiceServiceLines.Line line = new InvoiceServiceLines.Line(
1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day",
new BigDecimal("1200"), new BigDecimal("1200"));
assertThat(line.vatRate()).isEmpty();
assertThat(line.icon()).isEmpty();
assertThat(line.amount()).isEqualByComparingTo("1200");
}

@Test
void theLineConstructorThatPredatesTheMarkLeavesItBlank() {
// The tax rate arrived before the mark did, so a caller written against
// that version keeps compiling and keeps drawing no mark.
InvoiceServiceLines.Line line = new InvoiceServiceLines.Line(
1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day",
new BigDecimal("1200"), new BigDecimal("1200"), "20%");
assertThat(line.vatRate()).isEqualTo("20%");
assertThat(line.icon()).isEmpty();
}

@Test
void aLineCarriesTheMarkItIsGiven() {
InvoiceServiceLines.Line line = new InvoiceServiceLines.Line(
1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day",
new BigDecimal("1200"), new BigDecimal("1200"), "20%", "card");
assertThat(line.icon()).isEqualTo("card");
}

@Test
void aNullMarkNormalizesToBlankLikeTheFieldsBesideIt() {
InvoiceServiceLines.Line line = new InvoiceServiceLines.Line(
1, "Workshop", "A day of it", "May", BigDecimal.ONE, "day",
new BigDecimal("1200"), new BigDecimal("1200"), "20%", null);
assertThat(line.icon()).isEmpty();
}

@Test
void theDataConstructorThatPredatesShipToLeavesItEmptyRatherThanNull() {
// The record normalizes an absent block to its empty form, so preset
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ public Columns(String index, String description, String servicePeriod,
* the design shows it (e.g. {@code "20%"}) rather
* than as a number, because the wording differs by
* jurisdiction; blank when absent
* @param icon the mark a preset draws for this line; the token
* means something only to the preset that packages
* it, and a preset that draws no marks ignores it.
* Blank when absent
*/
public record Line(
int lineNumber,
Expand All @@ -110,7 +114,8 @@ public record Line(
String unit,
BigDecimal unitPrice,
BigDecimal amount,
String vatRate) {
String vatRate,
String icon) {

/**
* Normalizes optional fields.
Expand All @@ -124,6 +129,27 @@ public record Line(
unitPrice = Objects.requireNonNullElse(unitPrice, BigDecimal.ZERO);
amount = Objects.requireNonNullElse(amount, BigDecimal.ZERO);
vatRate = Objects.requireNonNullElse(vatRate, "");
icon = Objects.requireNonNullElse(icon, "");
}

/**
* Backward-compatible constructor for callers that predate the mark.
*
* @param lineNumber the printed line number
* @param title the service title
* @param description the description under the title
* @param servicePeriod the period this line covers
* @param quantity how much was delivered
* @param unit what the quantity counts
* @param unitPrice the price per unit
* @param amount the line total
* @param vatRate the tax rate printed for this line
*/
public Line(int lineNumber, String title, String description, String servicePeriod,
BigDecimal quantity, String unit, BigDecimal unitPrice,
BigDecimal amount, String vatRate) {
this(lineNumber, title, description, servicePeriod, quantity, unit,
unitPrice, amount, vatRate, "");
}

/**
Expand All @@ -143,7 +169,7 @@ public Line(int lineNumber, String title, String description, String servicePeri
BigDecimal quantity, String unit, BigDecimal unitPrice,
BigDecimal amount) {
this(lineNumber, title, description, servicePeriod, quantity, unit,
unitPrice, amount, "");
unitPrice, amount, "", "");
}
}
}
Loading