factur-x-builder 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -1
- data/Gemfile.lock +1 -1
- data/README.md +52 -0
- data/lib/factur_x/codes.rb +3 -0
- data/lib/factur_x/party.rb +15 -0
- data/lib/factur_x/validation/rules.rb +23 -2
- data/lib/factur_x/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa8357e047314e65502e6f758ee4ec36cb9b58af3fbfd8b171cf0b4c69d1692a
|
|
4
|
+
data.tar.gz: f574b56e54c9e19768599b2092d4d38b3559df165289f6435d76db6d8fdc5bb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4df9ad9574aeb30e5c34d6b790e64fc7997137d0947f7370c964e8dea902bfe86e2a022f50434e0b13f2ef96acf23227f08dbc5d5a24480a1e01daa4113c3481
|
|
7
|
+
data.tar.gz: 129bbecb17198fdb472cd294d27ed702bd5ec6486e7ff85cd81c73632b9f5b023830f80ae64a80a0553dbafff112bf4a37f4a889e8bb0d721d2eafb4490f4b11
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.1.5] - 2026-11-10
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- `Party.addressed_by_email` builds a party with no registry identifier, routed by email under
|
|
6
|
+
scheme `EM` (`Codes::Scheme::EMAIL`), for invoices exchanged directly rather than deposited on an
|
|
7
|
+
accredited platform. The seller is deliberately excluded: BR-CO-9 still requires it to identify
|
|
8
|
+
itself, though it may be addressed by email while keeping its identifiers.
|
|
9
|
+
- An email routing address declared under scheme `0225` is now rejected. That scheme designates a
|
|
10
|
+
French directory address, and no schematron checks the value against it, so a mislabelled address
|
|
11
|
+
would have gone out silently.
|
|
12
|
+
- Conformance coverage for invoicing mode `S2` on a settled invoice, for a due date (BT-9) that
|
|
13
|
+
precedes the issue date, for an email-addressed buyer, and for a VAT exemption carried by the
|
|
14
|
+
reason code (BT-121) rather than the reason text (BT-120) — BR-E-10 accepts either.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- The BR-27 message on a negative unit price only mentioned credit notes, sending anyone trying to
|
|
19
|
+
bill a discount down the wrong path. It now names the two ways EN 16931 expresses a reduction —
|
|
20
|
+
a document allowance and a unit price discount — and keeps the credit note as the refund case.
|
|
2
21
|
|
|
3
22
|
### Added
|
|
4
23
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -151,6 +151,58 @@ FacturX::Invoice.new(
|
|
|
151
151
|
)
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
+
### A party with no SIREN
|
|
155
|
+
|
|
156
|
+
For an invoice exchanged directly rather than deposited on an accredited platform — a private
|
|
157
|
+
individual, a foreign counterpart, any buyer whose SIREN is unknown — the buyer can be addressed by
|
|
158
|
+
email instead:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
buyer = FacturX::Party.addressed_by_email(
|
|
162
|
+
name: "Client particulier",
|
|
163
|
+
email: "client@example.net",
|
|
164
|
+
address: FacturX::Address.new(line_one: "12 rue des Lilas", postcode: "69001",
|
|
165
|
+
city: "Lyon", country_code: "FR")
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
No `GlobalID`, no `SpecifiedLegalOrganization`, no `SpecifiedTaxRegistration`; the routing address
|
|
170
|
+
becomes `<ram:URIID schemeID="EM">client@example.net</ram:URIID>`. Name and postal address stay —
|
|
171
|
+
BR-11 still requires the buyer country code.
|
|
172
|
+
|
|
173
|
+
The **seller** cannot do the same: it is liable for the VAT and BR-CO-9 requires an identifier. It
|
|
174
|
+
may still be *addressed* by email while keeping its SIRET, SIREN and VAT number — `addressed_by_email`
|
|
175
|
+
only changes the routing; dropping the identifiers is the separate choice of not passing them.
|
|
176
|
+
|
|
177
|
+
Scheme `0225` means "French directory address", so an email declared under it is mislabelled. No
|
|
178
|
+
schematron notices, so the gem rejects that combination itself.
|
|
179
|
+
|
|
180
|
+
### Discounts
|
|
181
|
+
|
|
182
|
+
EN 16931 has no negative line. A reduction is an **allowance**, and a negative unit price is
|
|
183
|
+
rejected by BR-27. Two mechanisms, both covered by the conformance suite:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
# On the whole invoice (BG-20) — reduces the taxable base
|
|
187
|
+
allowances: [
|
|
188
|
+
FacturX::AllowanceCharge.allowance(amount: 159.20, basis_amount: 1592.00, percentage: 10.0,
|
|
189
|
+
reason: "Remise commerciale 10 %",
|
|
190
|
+
vat_category: "S", vat_rate: 20.0)
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
# Off a line's unit price (BT-147) — gross price, discount, net price billed
|
|
194
|
+
FacturX::Line.new(name: "Option spot TV",
|
|
195
|
+
gross_unit_price: 1290.00, unit_discount: 290.00, unit_price: 1000.00,
|
|
196
|
+
quantity: 1, vat_rate: 20.0, net_amount: 1000.00)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
A document allowance carries its own VAT category and rate, and is subtracted from the taxable base
|
|
200
|
+
of that rate — `line_total − allowance_total + charge_total = tax_basis_total` (BR-CO-13), checked
|
|
201
|
+
before generating.
|
|
202
|
+
|
|
203
|
+
Line level allowances (BG-27) are not implemented: a flat discount on a line is currently expressed
|
|
204
|
+
through the unit price.
|
|
205
|
+
|
|
154
206
|
### Routing address
|
|
155
207
|
|
|
156
208
|
`routing_id` is the electronic address the platform delivers to (BT-34 for the seller, BT-49 for the
|
data/lib/factur_x/codes.rb
CHANGED
data/lib/factur_x/party.rb
CHANGED
|
@@ -52,6 +52,21 @@ module FacturX
|
|
|
52
52
|
@routing_id_scheme = routing_id_scheme || (Codes::Scheme::ROUTING_SIRET if @routing_id)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# A party with no SIREN available, addressed by email instead (scheme EM).
|
|
56
|
+
# No GlobalID, no legal organization and no tax registration are emitted,
|
|
57
|
+
# which keeps the document valid for an invoice exchanged directly rather
|
|
58
|
+
# than deposited on an accredited platform.
|
|
59
|
+
def self.addressed_by_email(name:, email:, address: nil, contact: nil, **attributes)
|
|
60
|
+
new(name: name, address: address,
|
|
61
|
+
contact: contact || Contact.new(email: email),
|
|
62
|
+
routing_id: email, routing_id_scheme: Codes::Scheme::EMAIL,
|
|
63
|
+
**attributes)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def addressed_by_email?
|
|
67
|
+
routing_id_scheme == Codes::Scheme::EMAIL
|
|
68
|
+
end
|
|
69
|
+
|
|
55
70
|
def contact?
|
|
56
71
|
!contact.nil? && !contact.empty?
|
|
57
72
|
end
|
|
@@ -138,6 +138,23 @@ module FacturX
|
|
|
138
138
|
|
|
139
139
|
check_french_identifiers(invoice.seller, "seller")
|
|
140
140
|
check_french_identifiers(invoice.buyer, "buyer")
|
|
141
|
+
check_routing_address(invoice.seller, "seller")
|
|
142
|
+
check_routing_address(invoice.buyer, "buyer")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Scheme 0225 designates a French directory address, so an email carrying
|
|
146
|
+
# it is mislabelled. Neither schematron notices - they only check that the
|
|
147
|
+
# scheme code exists - which is why this is worth catching here. Pass
|
|
148
|
+
# Party.addressed_by_email, or routing_id_scheme: Codes::Scheme::EMAIL.
|
|
149
|
+
def check_routing_address(party, role)
|
|
150
|
+
return if party.nil? || party.routing_id.nil?
|
|
151
|
+
return unless party.routing_id.to_s.include?("@")
|
|
152
|
+
return unless party.routing_id_scheme == Codes::Scheme::ROUTING_SIRET
|
|
153
|
+
|
|
154
|
+
add("#{role} routing address #{party.routing_id.inspect} is an email address but is " \
|
|
155
|
+
"declared under scheme 0225, which designates a French directory address " \
|
|
156
|
+
"(BT-34-1 / BT-49-1) - use Party.addressed_by_email or scheme " \
|
|
157
|
+
"#{Codes::Scheme::EMAIL}")
|
|
141
158
|
end
|
|
142
159
|
|
|
143
160
|
def check_party(party, role)
|
|
@@ -199,8 +216,12 @@ module FacturX
|
|
|
199
216
|
# conveys the direction, not the sign of the prices.
|
|
200
217
|
def check_unit_prices(line, label)
|
|
201
218
|
if negative?(line.unit_price)
|
|
202
|
-
add("#{label}: unit price must not be negative (BT-146, BR-27)
|
|
203
|
-
"
|
|
219
|
+
add("#{label}: unit price must not be negative (BT-146, BR-27). EN 16931 expresses a " \
|
|
220
|
+
"reduction as an allowance, never as a negative line: use " \
|
|
221
|
+
"AllowanceCharge.allowance in `allowances` for a discount on the whole invoice, " \
|
|
222
|
+
"or gross_unit_price with unit_discount on the line for a discount off the unit " \
|
|
223
|
+
"price. A refund of a whole invoice is a credit note instead (type code " \
|
|
224
|
+
"#{Codes::CREDIT_NOTE}), also with positive amounts")
|
|
204
225
|
end
|
|
205
226
|
|
|
206
227
|
return unless negative?(line.gross_unit_price)
|
data/lib/factur_x/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: factur-x-builder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hotentic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|