Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

### Changed

- `org`: an `Attribute` may now hold more than one of the `text`, `code`, `amount`, or `date` values, where before exactly one was required, so that formats grouping related values under a single name can be mapped directly. At least one value is still needed.

## [v0.503.0] - 2026-07-15

### Added
Expand Down
4 changes: 2 additions & 2 deletions data/rules/org.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
},
{
"id": "GOBL-ORG-ATTRIBUTE-02",
"desc": "attribute must have exactly one of the text, code, amount, or date values",
"tests": "(Text == \"\" ? 0 : 1) + (string(Code) == \"\" ? 0 : 1) + (Amount == nil ? 0 : 1) + (Date == nil ? 0 : 1) == 1"
"desc": "attribute must have at least one of the text, code, amount, or date values",
"tests": "(Text == \"\" ? 0 : 1) + (string(Code) == \"\" ? 0 : 1) + (Amount == nil ? 0 : 1) + (Date == nil ? 0 : 1) \u003e= 1"
}
],
"subsets": [
Expand Down
8 changes: 4 additions & 4 deletions org/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ var AttributeKeyDefinitions = []*cbc.Definition{

// Attribute describes a named feature or property of the parent object,
// such as the color or size of an item. Attributes are identified by
// either a key or a type, and hold exactly one of the text, code, amount,
// either a key or a type, and hold at least one of the text, code, amount,
// or date value fields.
type Attribute struct {
// Label for the attribute, used for presentation in converted outputs
Expand All @@ -168,7 +168,7 @@ type Attribute struct {
// and customer.
Type cbc.Code `json:"type,omitempty" jsonschema:"title=Type"`

// Value fields; exactly one must be provided.
// Value fields; at least one must be provided.

// Text value of the attribute.
Text string `json:"text,omitempty" jsonschema:"title=Text"`
Expand All @@ -188,8 +188,8 @@ func attributeRules() *rules.Set {
rules.Assert("01", "attribute must have either a key or a type, but not both",
is.Expr(`(string(Key) == "") != (string(Type) == "")`),
),
rules.Assert("02", "attribute must have exactly one of the text, code, amount, or date values",
is.Expr(`(Text == "" ? 0 : 1) + (string(Code) == "" ? 0 : 1) + (Amount == nil ? 0 : 1) + (Date == nil ? 0 : 1) == 1`),
rules.Assert("02", "attribute must have at least one of the text, code, amount, or date values",
is.Expr(`(Text == "" ? 0 : 1) + (string(Code) == "" ? 0 : 1) + (Amount == nil ? 0 : 1) + (Date == nil ? 0 : 1) >= 1`),
),
rules.When(is.Expr(`string(Unit) != ""`),
rules.Assert("03", "attribute unit may only be used alongside an amount",
Expand Down
22 changes: 15 additions & 7 deletions org/attribute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,35 @@ func TestAttributeValidation(t *testing.T) {
}
assert.ErrorContains(t, rules.Validate(a), "attribute must have either a key or a type, but not both")
})
t.Run("missing value", func(t *testing.T) {
t.Run("valid with text and date", func(t *testing.T) {
a := &org.Attribute{
Key: org.AttributeKeyColor,
Type: "INTENTO",
Text: "08060120341234567-000001",
Date: cal.NewDate(2026, time.March, 10),
}
assert.ErrorContains(t, rules.Validate(a), "attribute must have exactly one of the text, code, amount, or date values")
assert.NoError(t, rules.Validate(a))
})
t.Run("multiple values", func(t *testing.T) {
t.Run("valid with text and amount", func(t *testing.T) {
a := &org.Attribute{
Key: org.AttributeKeyWeight,
Text: "200g",
Amount: num.NewAmount(200, 0),
}
assert.ErrorContains(t, rules.Validate(a), "attribute must have exactly one of the text, code, amount, or date values")
assert.NoError(t, rules.Validate(a))
})
t.Run("text and code", func(t *testing.T) {
t.Run("valid with text and code", func(t *testing.T) {
a := &org.Attribute{
Key: org.AttributeKeyColor,
Text: "Black",
Code: "RAL 9005",
}
assert.ErrorContains(t, rules.Validate(a), "attribute must have exactly one of the text, code, amount, or date values")
assert.NoError(t, rules.Validate(a))
})
t.Run("missing value", func(t *testing.T) {
a := &org.Attribute{
Key: org.AttributeKeyColor,
}
assert.ErrorContains(t, rules.Validate(a), "attribute must have at least one of the text, code, amount, or date values")
})
t.Run("unit without amount", func(t *testing.T) {
a := &org.Attribute{
Expand Down
Loading