facturx 0.1.0 → 1.0.0
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 +29 -2
- data/NOTICE.md +2 -0
- data/README.md +62 -4
- data/lib/facturx/builders/association.rb +8 -0
- data/lib/facturx/builders/base.rb +129 -0
- data/lib/facturx/builders/schema.rb +112 -0
- data/lib/facturx/builders.rb +25 -0
- data/lib/facturx/coerce.rb +74 -0
- data/lib/facturx/conformance.rb +163 -0
- data/lib/facturx/diagnostic.rb +20 -0
- data/lib/facturx/document.rb +45 -0
- data/lib/facturx/error.rb +3 -0
- data/lib/facturx/format.rb +85 -0
- data/lib/facturx/generate.rb +21 -0
- data/lib/facturx/group.rb +5 -0
- data/lib/facturx/model/common.rb +8 -0
- data/lib/facturx/model/immutable.rb +75 -0
- data/lib/facturx/model/line.rb +14 -0
- data/lib/facturx/model/party.rb +10 -0
- data/lib/facturx/model/payment.rb +9 -0
- data/lib/facturx/model/reference.rb +7 -0
- data/lib/facturx/model/trade.rb +9 -0
- data/lib/facturx/model.rb +9 -0
- data/lib/facturx/pdf/extractor.rb +7 -4
- data/lib/facturx/profile_resolver.rb +40 -0
- data/lib/facturx/reader/semantic_mapper/delivery_mapping.rb +43 -0
- data/lib/facturx/reader/semantic_mapper/document_mapping.rb +109 -0
- data/lib/facturx/reader/semantic_mapper/helpers.rb +114 -0
- data/lib/facturx/reader/semantic_mapper/line_mapping.rb +120 -0
- data/lib/facturx/reader/semantic_mapper/line_references.rb +19 -0
- data/lib/facturx/reader/semantic_mapper/party_mapping.rb +90 -0
- data/lib/facturx/reader/semantic_mapper/payment_mapping.rb +108 -0
- data/lib/facturx/reader/semantic_mapper/trade_mapping.rb +83 -0
- data/lib/facturx/reader/semantic_mapper.rb +76 -0
- data/lib/facturx/reader/term_reader/coercion.rb +69 -0
- data/lib/facturx/reader/term_reader/unmapped.rb +37 -0
- data/lib/facturx/reader/term_reader.rb +132 -0
- data/lib/facturx/reader.rb +97 -0
- data/lib/facturx/reading.rb +23 -0
- data/lib/facturx/source_reader.rb +30 -0
- data/lib/facturx/term.rb +5 -0
- data/lib/facturx/terms/declaration.rb +20 -0
- data/lib/facturx/terms/declarations/adjustments.rb +230 -0
- data/lib/facturx/terms/declarations/delivery.rb +83 -0
- data/lib/facturx/terms/declarations/document.rb +223 -0
- data/lib/facturx/terms/declarations/groups.rb +356 -0
- data/lib/facturx/terms/declarations/lines.rb +166 -0
- data/lib/facturx/terms/declarations/parties.rb +609 -0
- data/lib/facturx/terms/declarations/payment.rb +106 -0
- data/lib/facturx/terms/declarations/products.rb +103 -0
- data/lib/facturx/terms/declarations/references.rb +72 -0
- data/lib/facturx/terms/declarations/taxes.rb +92 -0
- data/lib/facturx/terms/declarations/totals.rb +107 -0
- data/lib/facturx/terms/reference.rb +21 -0
- data/lib/facturx/terms.rb +109 -0
- data/lib/facturx/version.rb +1 -1
- data/lib/facturx/writer/context.rb +53 -0
- data/lib/facturx/writer/stage.rb +47 -0
- data/lib/facturx/writer/stage_support/contact_details.rb +57 -0
- data/lib/facturx/writer/stage_support/emission.rb +78 -0
- data/lib/facturx/writer/stage_support/identifiers.rb +129 -0
- data/lib/facturx/writer/stage_support/structure.rb +88 -0
- data/lib/facturx/writer/stages/document_context.rb +31 -0
- data/lib/facturx/writer/stages/exchanged_document.rb +32 -0
- data/lib/facturx/writer/stages/trade_agreement/parties.rb +105 -0
- data/lib/facturx/writer/stages/trade_agreement/references.rb +92 -0
- data/lib/facturx/writer/stages/trade_agreement.rb +45 -0
- data/lib/facturx/writer/stages/trade_delivery.rb +77 -0
- data/lib/facturx/writer/stages/trade_lines/agreement.rb +92 -0
- data/lib/facturx/writer/stages/trade_lines/product.rb +78 -0
- data/lib/facturx/writer/stages/trade_lines/quantity.rb +17 -0
- data/lib/facturx/writer/stages/trade_lines/settlement.rb +89 -0
- data/lib/facturx/writer/stages/trade_lines.rb +67 -0
- data/lib/facturx/writer/stages/trade_settlement/parties.rb +33 -0
- data/lib/facturx/writer/stages/trade_settlement/payment.rb +97 -0
- data/lib/facturx/writer/stages/trade_settlement/summary.rb +88 -0
- data/lib/facturx/writer/stages/trade_settlement/taxes.rb +121 -0
- data/lib/facturx/writer/stages/trade_settlement.rb +70 -0
- data/lib/facturx/writer.rb +68 -0
- data/lib/facturx/xml/namespaces.rb +19 -0
- data/lib/facturx/xml/profile_detector.rb +2 -4
- data/lib/facturx.rb +42 -0
- data/rbi/facturx.rbi +1090 -0
- metadata +79 -4
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
module TradeSettlementSummary
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def payment_terms(parent, direct_debit)
|
|
10
|
+
values = [document.payment_terms, document.payment_due_date, direct_debit&.mandate_identifier]
|
|
11
|
+
return missing_payment_terms? unless values.any?
|
|
12
|
+
|
|
13
|
+
container(parent, 'ram:SpecifiedTradePaymentTerms') do |node|
|
|
14
|
+
emit('BT-20', document.payment_terms, element: 'ram:Description', parent: node)
|
|
15
|
+
emit_date(node, 'BT-9', document.payment_due_date, wrapper: 'ram:DueDateDateTime')
|
|
16
|
+
emit_identifier_value('BT-89', direct_debit&.mandate_identifier, 'ram:DirectDebitMandateID', node)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def missing_payment_terms?
|
|
21
|
+
observe?('BT-20', nil)
|
|
22
|
+
observe?('BT-9', nil)
|
|
23
|
+
observe?('BT-89', nil)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def totals(parent)
|
|
27
|
+
within_group('BG-22', document.totals, element: 'ram:SpecifiedTradeSettlementHeaderMonetarySummation',
|
|
28
|
+
parent:) do |node, item|
|
|
29
|
+
emit_subtotals(node, item)
|
|
30
|
+
emit_tax_totals(node, item)
|
|
31
|
+
emit_balance(node, item)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def emit_subtotals(parent, totals)
|
|
36
|
+
emit('BT-106', totals.line_total, element: 'ram:LineTotalAmount', parent:)
|
|
37
|
+
emit('BT-108', totals.charge_total, element: 'ram:ChargeTotalAmount', parent:)
|
|
38
|
+
emit('BT-107', totals.allowance_total, element: 'ram:AllowanceTotalAmount', parent:)
|
|
39
|
+
emit('BT-109', totals.tax_basis_total, element: 'ram:TaxBasisTotalAmount', parent:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def emit_tax_totals(parent, totals)
|
|
43
|
+
emit('BT-110', totals.tax_total, element: 'ram:TaxTotalAmount', parent:,
|
|
44
|
+
attributes: { 'currencyID' => document.currency })
|
|
45
|
+
emit_tax_total_in_tax_currency(parent, totals.tax_total_in_tax_currency)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def emit_tax_total_in_tax_currency(parent, value)
|
|
49
|
+
return observe?('BT-111', nil) unless value
|
|
50
|
+
return unrepresentable('BT-111', 'Tax total requires a distinct tax currency') unless distinct_tax_currency?
|
|
51
|
+
|
|
52
|
+
emit('BT-111', value, element: 'ram:TaxTotalAmount', parent:,
|
|
53
|
+
attributes: { 'currencyID' => document.tax_currency })
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def distinct_tax_currency?
|
|
57
|
+
tax_currency = document.tax_currency
|
|
58
|
+
!tax_currency.to_s.strip.empty? && tax_currency != document.currency
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def emit_balance(parent, totals)
|
|
62
|
+
emit('BT-114', totals.rounding, element: 'ram:RoundingAmount', parent:)
|
|
63
|
+
emit('BT-112', totals.grand_total, element: 'ram:GrandTotalAmount', parent:)
|
|
64
|
+
emit('BT-113', totals.prepaid, element: 'ram:TotalPrepaidAmount', parent:)
|
|
65
|
+
emit('BT-115', totals.due_payable, element: 'ram:DuePayableAmount', parent:)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def preceding_invoices(parent)
|
|
69
|
+
each_group('BG-3', document.preceding_invoices,
|
|
70
|
+
element: 'ram:InvoiceReferencedDocument', parent:) do |node, item|
|
|
71
|
+
emit('BT-25', item.id, element: 'ram:IssuerAssignedID', parent: node)
|
|
72
|
+
emit_date(node, 'BT-26', item.issue_date, wrapper: 'ram:FormattedIssueDateTime',
|
|
73
|
+
value_element: 'qdt:DateTimeString')
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def accounting_reference(parent)
|
|
78
|
+
value = document.buyer_accounting_reference
|
|
79
|
+
return observe?('BT-19', nil) unless value
|
|
80
|
+
|
|
81
|
+
container(parent, 'ram:ReceivableSpecifiedTradeAccountingAccount') do |node|
|
|
82
|
+
emit('BT-19', value, element: 'ram:ID', parent: node)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
class Writer
|
|
5
|
+
module Stages
|
|
6
|
+
module TradeSettlementTaxes
|
|
7
|
+
ADJUSTMENT_IDS = {
|
|
8
|
+
false => %w[BT-94 BT-93 BT-92 BT-98 BT-97 BT-95 BT-96],
|
|
9
|
+
true => %w[BT-101 BT-100 BT-99 BT-105 BT-104 BT-102 BT-103]
|
|
10
|
+
}.freeze
|
|
11
|
+
ADJUSTMENT_FIELDS = [
|
|
12
|
+
[:percentage, 'ram:CalculationPercent'],
|
|
13
|
+
[:base_amount, 'ram:BasisAmount'],
|
|
14
|
+
[:amount, 'ram:ActualAmount'],
|
|
15
|
+
[:reason_code, 'ram:ReasonCode'],
|
|
16
|
+
[:reason, 'ram:Reason']
|
|
17
|
+
].freeze
|
|
18
|
+
private_constant :ADJUSTMENT_IDS, :ADJUSTMENT_FIELDS
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def taxes(parent)
|
|
23
|
+
first = true
|
|
24
|
+
each_group('BG-23', document.tax_breakdowns, element: 'ram:ApplicableTradeTax', parent:,
|
|
25
|
+
represented_attributes: [:type_code]) do |node, item|
|
|
26
|
+
emit_tax(node, item, include_tax_point: first)
|
|
27
|
+
first = false
|
|
28
|
+
end
|
|
29
|
+
return unless first
|
|
30
|
+
|
|
31
|
+
report_vat_point_attributes
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def emit_tax(parent, tax, include_tax_point:)
|
|
35
|
+
emit('BT-117', tax.tax_amount, element: 'ram:CalculatedAmount', parent:)
|
|
36
|
+
technical(parent, 'ram:TypeCode', tax.type_code, default: 'VAT')
|
|
37
|
+
emit('BT-120', tax.exemption_reason, element: 'ram:ExemptionReason', parent:)
|
|
38
|
+
emit('BT-116', tax.basis_amount, element: 'ram:BasisAmount', parent:)
|
|
39
|
+
emit('BT-118', tax.category_code, element: 'ram:CategoryCode', parent:)
|
|
40
|
+
emit('BT-121', tax.exemption_reason_code, element: 'ram:ExemptionReasonCode', parent:)
|
|
41
|
+
tax_point_date(parent) if include_tax_point
|
|
42
|
+
tax_due_date_type(parent, tax, check_conflict: include_tax_point)
|
|
43
|
+
emit('BT-119', tax.rate, element: 'ram:RateApplicablePercent', parent:)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def tax_due_date_type(parent, tax, check_conflict:)
|
|
47
|
+
document_code = document.vat_point_date_code
|
|
48
|
+
if check_conflict && tax.due_date_type_code && document_code && tax.due_date_type_code != document_code
|
|
49
|
+
report_unrepresentable_attribute('BG-23', model: :document, attribute: :vat_point_date_code)
|
|
50
|
+
end
|
|
51
|
+
emit('BT-8', tax.due_date_type_code || document_code, element: 'ram:DueDateTypeCode', parent:)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tax_point_date(parent)
|
|
55
|
+
emit_date(
|
|
56
|
+
parent, 'BT-7', document.vat_point_date, wrapper: 'ram:TaxPointDate', value_element: 'udt:DateString'
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def report_vat_point_attributes
|
|
61
|
+
%i[vat_point_date vat_point_date_code].each do |attribute|
|
|
62
|
+
value = document.public_send(attribute)
|
|
63
|
+
report_unrepresentable_attribute('BG-23', model: :document, attribute:) if value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def billing_period(parent)
|
|
68
|
+
within_group('BG-14', document.billing_period, element: 'ram:BillingSpecifiedPeriod', parent:) do |node, item|
|
|
69
|
+
emit_date(node, 'BT-73', item.start_date, wrapper: 'ram:StartDateTime')
|
|
70
|
+
emit_date(node, 'BT-74', item.end_date, wrapper: 'ram:EndDateTime')
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def adjustments(parent, values, group_id, indicator)
|
|
75
|
+
ids = ADJUSTMENT_IDS.fetch(indicator)
|
|
76
|
+
each_group(group_id, values, element: 'ram:SpecifiedTradeAllowanceCharge', parent:,
|
|
77
|
+
represented_attributes: [:indicator]) do |node, item|
|
|
78
|
+
emit_adjustment(node, item, group_id, indicator, ids)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def emit_adjustment(parent, adjustment, group_id, indicator, ids)
|
|
83
|
+
adjustment_indicator(parent, group_id, indicator)
|
|
84
|
+
emit_adjustment_values(parent, adjustment, ids)
|
|
85
|
+
adjustment_tax(parent, adjustment.tax, group_id, ids[5], ids[6])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def emit_adjustment_values(parent, adjustment, ids)
|
|
89
|
+
ADJUSTMENT_FIELDS.each_with_index do |(attribute, element), index|
|
|
90
|
+
emit(ids[index], adjustment.public_send(attribute), element:, parent:)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def adjustment_indicator(parent, group_id, value)
|
|
95
|
+
within_group("#{group_id}-0", value, element: 'ram:ChargeIndicator', parent:) do |node, indicator|
|
|
96
|
+
within_group("#{group_id}-1", indicator, element: 'udt:Indicator', parent: node) do |leaf, raw|
|
|
97
|
+
leaf.content = raw ? 'true' : 'false'
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def adjustment_tax(parent, tax, group_id, category_id, rate_id)
|
|
103
|
+
return missing_adjustment_tax?(category_id, rate_id) unless tax
|
|
104
|
+
|
|
105
|
+
report_unrepresentable_attributes(group_id, tax, represented_attributes: %i[type_code category_code rate])
|
|
106
|
+
|
|
107
|
+
container(parent, 'ram:CategoryTradeTax') do |node|
|
|
108
|
+
technical(node, 'ram:TypeCode', tax.type_code, default: 'VAT')
|
|
109
|
+
emit(category_id, tax.category_code, element: 'ram:CategoryCode', parent: node)
|
|
110
|
+
emit(rate_id, tax.rate, element: 'ram:RateApplicablePercent', parent: node)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def missing_adjustment_tax?(category_id, rate_id)
|
|
115
|
+
observe?(category_id, nil)
|
|
116
|
+
observe?(rate_id, nil)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'trade_settlement/parties'
|
|
4
|
+
require_relative 'trade_settlement/payment'
|
|
5
|
+
require_relative 'trade_settlement/taxes'
|
|
6
|
+
require_relative 'trade_settlement/summary'
|
|
7
|
+
|
|
8
|
+
module Facturx
|
|
9
|
+
class Writer
|
|
10
|
+
module Stages
|
|
11
|
+
class TradeSettlement < Stage
|
|
12
|
+
include TradeSettlementParties
|
|
13
|
+
include TradeSettlementPayment
|
|
14
|
+
include TradeSettlementTaxes
|
|
15
|
+
include TradeSettlementSummary
|
|
16
|
+
|
|
17
|
+
term_ids(*%w[
|
|
18
|
+
BT-90 BT-83 BT-6 BT-5 BT-60 BT-60-1 BT-59 BT-61 BT-61-1 BT-81 BT-82 BT-87 BT-88 BT-91 BT-84
|
|
19
|
+
BT-85 BT-86 BT-117 BT-120 BT-116 BT-118 BT-121 BT-7 BT-8 BT-119 BT-73 BT-74 BT-94 BT-93 BT-92
|
|
20
|
+
BT-98 BT-97 BT-95 BT-96 BT-101 BT-100 BT-99 BT-105 BT-104 BT-102 BT-103 BT-20 BT-9 BT-89 BT-106
|
|
21
|
+
BT-108 BT-107 BT-109 BT-110 BT-111 BT-114 BT-112 BT-113 BT-115 BT-25 BT-26 BT-19
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
node = context.element(context.transaction, 'ram:ApplicableHeaderTradeSettlement')
|
|
26
|
+
payment = document.payment
|
|
27
|
+
emit_header(node, payment)
|
|
28
|
+
emit_content(node, payment)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def emit_header(parent, payment)
|
|
34
|
+
direct_debit = payment&.direct_debit
|
|
35
|
+
emit_identifier_value('BT-90', direct_debit&.creditor_identifier, 'ram:CreditorReferenceID', parent)
|
|
36
|
+
emit('BT-83', payment&.remittance_information, element: 'ram:PaymentReference', parent:)
|
|
37
|
+
emit('BT-6', tax_currency_term_value, element: 'ram:TaxCurrencyCode', parent:)
|
|
38
|
+
emit('BT-5', document.currency, element: 'ram:InvoiceCurrencyCode', parent:)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def tax_currency_term_value
|
|
42
|
+
return if profile.id == :minimum && document.totals&.tax_total_in_tax_currency
|
|
43
|
+
|
|
44
|
+
document.tax_currency
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def emit_content(parent, payment)
|
|
48
|
+
payee(parent)
|
|
49
|
+
payment_means(parent, payment)
|
|
50
|
+
taxes(parent)
|
|
51
|
+
billing_period(parent)
|
|
52
|
+
document_adjustments(parent)
|
|
53
|
+
settlement_summary(parent, payment&.direct_debit)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def document_adjustments(parent)
|
|
57
|
+
adjustments(parent, document.allowances, 'BG-20', false)
|
|
58
|
+
adjustments(parent, document.charges, 'BG-21', true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def settlement_summary(parent, direct_debit)
|
|
62
|
+
payment_terms(parent, direct_debit)
|
|
63
|
+
totals(parent)
|
|
64
|
+
preceding_invoices(parent)
|
|
65
|
+
accounting_reference(parent)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'conformance'
|
|
4
|
+
require_relative 'error'
|
|
5
|
+
require_relative 'terms'
|
|
6
|
+
require_relative 'writer/context'
|
|
7
|
+
require_relative 'writer/stage'
|
|
8
|
+
require_relative 'writer/stages/document_context'
|
|
9
|
+
require_relative 'writer/stages/exchanged_document'
|
|
10
|
+
require_relative 'writer/stages/trade_lines'
|
|
11
|
+
require_relative 'writer/stages/trade_agreement'
|
|
12
|
+
require_relative 'writer/stages/trade_delivery'
|
|
13
|
+
require_relative 'writer/stages/trade_settlement'
|
|
14
|
+
require_relative 'xml/schema_validator'
|
|
15
|
+
|
|
16
|
+
module Facturx
|
|
17
|
+
class Writer
|
|
18
|
+
STAGES = [
|
|
19
|
+
Stages::DocumentContext,
|
|
20
|
+
Stages::ExchangedDocument,
|
|
21
|
+
Stages::TradeLines,
|
|
22
|
+
Stages::TradeAgreement,
|
|
23
|
+
Stages::TradeDelivery,
|
|
24
|
+
Stages::TradeSettlement
|
|
25
|
+
].freeze
|
|
26
|
+
MODELED_TERM_IDS = STAGES.flat_map(&:term_ids).freeze
|
|
27
|
+
|
|
28
|
+
unless MODELED_TERM_IDS.sort == Terms.all.map(&:id).sort
|
|
29
|
+
raise 'Factur-X writer stages must cover every modeled term exactly once'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(schema_validator: Xml::SchemaValidator.new, tracker: Conformance::Tracker, stages: STAGES)
|
|
33
|
+
@schema_validator = schema_validator
|
|
34
|
+
@tracker_class = tracker
|
|
35
|
+
@stages = stages.freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def call(document:, profile:)
|
|
39
|
+
context = compile(document:, profile:)
|
|
40
|
+
report = context.tracker.report
|
|
41
|
+
raise_conformance_error(profile, report) if report.invalid?
|
|
42
|
+
|
|
43
|
+
@schema_validator.call(document: context.xml, profile:)
|
|
44
|
+
context.to_xml
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def validate(document:, profile:)
|
|
48
|
+
compile(document:, profile:).tracker.report
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def compile(document:, profile:)
|
|
54
|
+
tracker = @tracker_class.new(profile:)
|
|
55
|
+
tracker.check_document(document, path: Terms.fetch('BT-24').xpath)
|
|
56
|
+
context = Context.new(document:, profile:, tracker:)
|
|
57
|
+
@stages.each { |stage| stage.new(context).call }
|
|
58
|
+
context
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def raise_conformance_error(profile, report)
|
|
62
|
+
raise ConformanceError.new(
|
|
63
|
+
'Factur-X document does not conform to the selected profile',
|
|
64
|
+
profile: profile.id, report:, issues: report.issues
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Facturx
|
|
4
|
+
module Xml
|
|
5
|
+
module Namespaces
|
|
6
|
+
CII = 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'
|
|
7
|
+
QUALIFIED = 'urn:un:unece:uncefact:data:standard:QualifiedDataType:100'
|
|
8
|
+
REUSABLE = 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100'
|
|
9
|
+
UNQUALIFIED = 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100'
|
|
10
|
+
|
|
11
|
+
MAP = {
|
|
12
|
+
'qdt' => QUALIFIED,
|
|
13
|
+
'ram' => REUSABLE,
|
|
14
|
+
'rsm' => CII,
|
|
15
|
+
'udt' => UNQUALIFIED
|
|
16
|
+
}.freeze
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative '../profiles'
|
|
4
4
|
require_relative '../error'
|
|
5
|
+
require_relative 'namespaces'
|
|
5
6
|
|
|
6
7
|
module Facturx
|
|
7
8
|
module Xml
|
|
8
9
|
class ProfileDetector
|
|
9
|
-
NAMESPACES =
|
|
10
|
-
'ram' => 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100',
|
|
11
|
-
'rsm' => 'urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100'
|
|
12
|
-
}.freeze
|
|
10
|
+
NAMESPACES = Namespaces::MAP
|
|
13
11
|
GUIDELINE_XPATH = '/rsm:CrossIndustryInvoice/rsm:ExchangedDocumentContext/' \
|
|
14
12
|
'ram:GuidelineSpecifiedDocumentContextParameter/ram:ID'
|
|
15
13
|
|
data/lib/facturx.rb
CHANGED
|
@@ -5,6 +5,18 @@ require 'facturx/error'
|
|
|
5
5
|
require 'facturx/embedding'
|
|
6
6
|
require 'facturx/profile'
|
|
7
7
|
require 'facturx/profiles'
|
|
8
|
+
require 'facturx/profile_resolver'
|
|
9
|
+
require 'facturx/term'
|
|
10
|
+
require 'facturx/group'
|
|
11
|
+
require 'facturx/terms'
|
|
12
|
+
require 'facturx/model'
|
|
13
|
+
require 'facturx/document'
|
|
14
|
+
require 'facturx/builders'
|
|
15
|
+
require 'facturx/diagnostic'
|
|
16
|
+
require 'facturx/reading'
|
|
17
|
+
require 'facturx/coerce'
|
|
18
|
+
require 'facturx/format'
|
|
19
|
+
require 'facturx/conformance'
|
|
8
20
|
require 'facturx/xml/verifier'
|
|
9
21
|
require 'facturx/pdf/document'
|
|
10
22
|
require 'facturx/pdf/inspector'
|
|
@@ -12,10 +24,22 @@ require 'facturx/pdf/extractor'
|
|
|
12
24
|
require 'facturx/pdf/verifier'
|
|
13
25
|
require 'facturx/composers/ghostscript'
|
|
14
26
|
require 'facturx/attach'
|
|
27
|
+
require 'facturx/reader'
|
|
28
|
+
require 'facturx/writer'
|
|
29
|
+
require 'facturx/generate'
|
|
15
30
|
|
|
16
31
|
module Facturx
|
|
17
32
|
DEFAULT_ATTACHER = Attach.new
|
|
33
|
+
DEFAULT_READER = Reader.new
|
|
34
|
+
DEFAULT_PROFILE_RESOLVER = ProfileResolver.new
|
|
35
|
+
DEFAULT_WRITER = Writer.new
|
|
36
|
+
DEFAULT_GENERATOR = Generate.new(writer: DEFAULT_WRITER, attacher: DEFAULT_ATTACHER,
|
|
37
|
+
profile_resolver: DEFAULT_PROFILE_RESOLVER)
|
|
18
38
|
private_constant :DEFAULT_ATTACHER
|
|
39
|
+
private_constant :DEFAULT_READER
|
|
40
|
+
private_constant :DEFAULT_PROFILE_RESOLVER
|
|
41
|
+
private_constant :DEFAULT_WRITER
|
|
42
|
+
private_constant :DEFAULT_GENERATOR
|
|
19
43
|
|
|
20
44
|
class << self
|
|
21
45
|
def verify_xml(xml:)
|
|
@@ -30,5 +54,23 @@ module Facturx
|
|
|
30
54
|
def extract_xml(pdf:)
|
|
31
55
|
Pdf::Extractor.new.call(pdf).xml
|
|
32
56
|
end
|
|
57
|
+
|
|
58
|
+
def read(source, on_unknown_profile: :fallback)
|
|
59
|
+
DEFAULT_READER.call(source, on_unknown_profile:)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_document(document:, profile:)
|
|
63
|
+
canonical_profile = DEFAULT_PROFILE_RESOLVER.call(profile)
|
|
64
|
+
DEFAULT_WRITER.validate(document:, profile: canonical_profile)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_xml(document:, profile:)
|
|
68
|
+
canonical_profile = DEFAULT_PROFILE_RESOLVER.call(profile)
|
|
69
|
+
DEFAULT_WRITER.call(document:, profile: canonical_profile)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def generate(pdf:, document:, profile:)
|
|
73
|
+
DEFAULT_GENERATOR.call(pdf:, document:, profile:)
|
|
74
|
+
end
|
|
33
75
|
end
|
|
34
76
|
end
|