saft 0.1.1

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.
@@ -0,0 +1,493 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-struct"
4
+
5
+ # This file contains the xml intermediate structure
6
+ # The node names are generated from the XSD located in
7
+ # ./vendor/SAF-T_Financial_Schema1.10.xsd. There are at least a couple of nodes
8
+ # which are ignored since it has the comment "Not in use.". I don't look at it as
9
+ # a bug since we don't need then but we can implement when/if thay are ever
10
+ # needed.
11
+
12
+ module SAFT::V2::Types
13
+ module Types
14
+ include Dry.Types
15
+
16
+ Date = Params::Date
17
+ DateTime = Params::DateTime
18
+ Integer = Coercible::Integer
19
+ Decimal = Coercible::Decimal
20
+ Bool = Params::Bool
21
+ String = Strict::String
22
+ PositiveInteger = Types::Integer.constrained(gteq: 0)
23
+ end
24
+
25
+ # not Ideal as we are extending the global scope
26
+ Dry::Logic::Predicates.predicate(:digits?) do |num, input|
27
+ _sign, significant_digits, _base, exponent = input.split
28
+ n_significant_digits = significant_digits.length
29
+ n_significant_digits += -exponent if exponent.negative?
30
+ n_significant_digits <= num
31
+ end
32
+
33
+ Dry::Logic::Predicates.predicate(:fraction_digitst?) do |num, input|
34
+ digits?(num, input.frac)
35
+ end
36
+
37
+ # Aka base
38
+ module ObjectStrutures
39
+ def self.extended(base)
40
+ base.class_eval do
41
+ # not how I wanted to write this but this is how it turn out then I
42
+ # wanted to have different level of strictness for types like
43
+ # SAFmiddle1textType
44
+
45
+ klass = Class.new(Dry::Struct) do
46
+ attribute(:amount, base::SAFmonetaryType)
47
+
48
+ attribute(:currency_code, base::ISOCurrencyCode.optional.meta(omittable: true))
49
+ attribute(:currency_amount, base::SAFmonetaryType.optional.meta(omittable: true))
50
+ attribute(:exchange_rate, base::SAFexchangerateType.optional.meta(omittable: true))
51
+ end
52
+
53
+ base.const_set(:AmountStructure, klass)
54
+
55
+ klass = Class.new(Dry::Struct) do
56
+ attribute(:analysis_type, base::SAFcodeType)
57
+ attribute(:analysis_id, base::SAFlongtextType)
58
+ attribute(:analysis_amount, base::AmountStructure.optional.meta(omittable: true))
59
+ end
60
+
61
+ base.const_set(:AnalysisStructure, klass)
62
+
63
+ klass = Class.new(Dry::Struct) do
64
+ attribute(:analysis_type, base::SAFcodeType)
65
+ attribute(:analysis_id, base::SAFlongtextType)
66
+ end
67
+
68
+ base.const_set(:AnalysisPartyInfoStructure, klass)
69
+
70
+ klass = Class.new(Dry::Struct) do
71
+ attribute(:tax_type, base::SAFcodeType.optional.meta(omittable: true))
72
+ attribute(:tax_code, base::SAFmiddle1textType.optional.meta(omittable: true))
73
+ attribute(:tax_percentage, Types::Decimal.optional.meta(omittable: true))
74
+ attribute(:country, base::ISOCountryCode.optional.meta(omittable: true))
75
+ attribute(:tax_base, Types::Decimal.optional.meta(omittable: true))
76
+ attribute(:tax_base_description, base::SAFmiddle2textType.optional.meta(omittable: true))
77
+ attribute(:tax_amount, base::AmountStructure)
78
+ attribute(:tax_exemption_reason, base::SAFmiddle2textType.optional.meta(omittable: true))
79
+ attribute(:tax_declaration_period, base::SAFmiddle1textType.optional.meta(omittable: true))
80
+ end
81
+
82
+ base.const_set(:TaxInformationStructure, klass)
83
+
84
+ klass = Class.new(Dry::Struct) do
85
+ attribute(:tax_registration_number, base::SAFmiddle1textType)
86
+ # ignored since xsd states "Not in use."
87
+ # attribute :tax_type, base::SAFcodeType.optional.meta(omittable: true)
88
+ # attribute :tax_number, base::SAFmiddle1textType.optional.meta(omittable: true)
89
+ attribute(:tax_authority, base::SAFmiddle1textType.optional.meta(omittable: true))
90
+ attribute(:tax_verification_date, Types::Date.optional.meta(omittable: true))
91
+ end
92
+
93
+ base.const_set(:TaxIDStructure, klass)
94
+
95
+ klass = Class.new(Dry::Struct) do
96
+ attribute(:tax_reporting_jurisdiction, base::SAFmiddle1textType.optional.meta(omittable: true))
97
+ attribute(:company_entity, base::SAFmiddle2textType.optional.meta(omittable: true))
98
+ attribute(:selection_start_date, Types::Date.optional.meta(omittable: true))
99
+ attribute(:selection_end_date, Types::Date.optional.meta(omittable: true))
100
+ attribute(:period_start, Types::PositiveInteger.optional.meta(omittable: true))
101
+ attribute(:period_start_year, Types::Integer.constrained(gteq: 1970).constrained(lteq: 2100).optional.meta(omittable: true))
102
+ attribute(:period_end, Types::PositiveInteger.optional.meta(omittable: true))
103
+ attribute(:period_end_year, Types::Integer.constrained(gteq: 1970).constrained(lteq: 2100).optional.meta(omittable: true))
104
+ attribute(:document_type, base::SAFlongtextType.optional.meta(omittable: true))
105
+ attribute(:other_criterias, Types::Array.of(base::SAFlongtextType).optional.meta(omittable: true))
106
+ end
107
+
108
+ base.const_set(:SelectionCriteriaStructure, klass)
109
+
110
+ klass = Class.new(Dry::Struct) do
111
+ attribute(:title, base::SAFcodeType.optional.meta(omittable: true))
112
+ attribute(:first_name, base::SAFmiddle1textType)
113
+ attribute(:initials, base::SAFshorttextType.optional.meta(omittable: true))
114
+ attribute(:last_name_prefix, base::SAFshorttextType.optional.meta(omittable: true))
115
+ attribute(:last_name, base::SAFmiddle2textType)
116
+ attribute(:birth_name, base::SAFmiddle2textType.optional.meta(omittable: true))
117
+ attribute(:salutation, base::SAFshorttextType.optional.meta(omittable: true))
118
+ attribute(:other_titles, Types::Array.of(base::SAFshorttextType).optional.meta(omittable: true))
119
+ end
120
+
121
+ base.const_set(:PersonNameStructure, klass)
122
+
123
+ klass = Class.new(Dry::Struct) do
124
+ attribute(:days, Types::PositiveInteger.optional.meta(omittable: true))
125
+ attribute(:months, Types::PositiveInteger.optional.meta(omittable: true))
126
+ attribute(:cash_discount_days, Types::PositiveInteger.optional.meta(omittable: true))
127
+ attribute(:cash_discount_rate, Types::Decimal.constrained(gteq: 0).constrained(lteq: 100).optional.meta(omittable: true))
128
+ attribute(:free_billing_month, Types::Bool.optional.meta(omittable: true))
129
+ end
130
+
131
+ base.const_set(:PaymentTerms, klass)
132
+
133
+ klass = Class.new(Dry::Struct) do
134
+ attribute(:payment_terms, base::PaymentTerms.optional.meta(omittable: true))
135
+ attribute(:nace_code, base::SAFshorttextType.optional.meta(omittable: true))
136
+ attribute(:currency_code, base::ISOCurrencyCode.optional.meta(omittable: true))
137
+ attribute(:type, base::SAFmiddle1textType.optional.meta(omittable: true))
138
+ attribute(:status, base::SAFmiddle1textType.optional.meta(omittable: true))
139
+ attribute(:analyses, Types::Array.of(base::AnalysisPartyInfoStructure).optional.meta(omittable: true))
140
+ attribute(:notes, Types::String.optional.meta(omittable: true))
141
+ end
142
+
143
+ base.const_set(:PartyInfoStructure, klass)
144
+
145
+ klass = Class.new(Dry::Struct) do
146
+ attribute(:street_name, base::SAFmiddle2textType.optional.meta(omittable: true))
147
+ attribute(:number, base::SAFshorttextType.optional.meta(omittable: true))
148
+ attribute(:additional_address_detail, base::SAFmiddle2textType.optional.meta(omittable: true))
149
+ attribute(:building, base::SAFmiddle1textType.optional.meta(omittable: true))
150
+ attribute(:city, base::SAFmiddle1textType.optional.meta(omittable: true))
151
+ attribute(:postal_code, base::SAFshorttextType.optional.meta(omittable: true))
152
+ attribute(:region, base::SAFmiddle1textType.optional.meta(omittable: true))
153
+ attribute(:country, base::ISOCountryCode.optional.meta(omittable: true))
154
+ attribute(:address_type, base::AddressType.optional.meta(omittable: true))
155
+ end
156
+
157
+ base.const_set(:AddressStructure, klass)
158
+
159
+ klass = Class.new(Dry::Struct) do
160
+ attribute(:delivery_id, base::SAFmiddle1textType.optional.meta(omittable: true))
161
+ attribute(:delivery_date, Types::Date.optional.meta(omittable: true))
162
+ attribute(:warehouse_id, base::SAFmiddle1textType.optional.meta(omittable: true))
163
+ attribute(:location_id, base::SAFshorttextType.optional.meta(omittable: true))
164
+ attribute(:ucr, base::SAFmiddle1textType.optional.meta(omittable: true))
165
+ attribute(:address, base::AddressStructure.optional.meta(omittable: true))
166
+ end
167
+
168
+ base.const_set(:ShippingPointStructure, klass)
169
+
170
+ klass = Class.new(Dry::Struct) do
171
+ attribute(:contact_person, base::PersonNameStructure)
172
+ attribute(:telephone, base::SAFshorttextType.optional.meta(omittable: true))
173
+ attribute(:fax, base::SAFshorttextType.optional.meta(omittable: true))
174
+ attribute(:email, base::SAFmiddle2textType.optional.meta(omittable: true))
175
+ attribute(:website, Types::String.optional.meta(omittable: true))
176
+ attribute(:mobile_phone, base::SAFshorttextType.optional.meta(omittable: true))
177
+ end
178
+
179
+ base.const_set(:ContactInformationStructure, klass)
180
+
181
+ klass = Class.new(Dry::Struct) do
182
+ attribute(:iban_number, base::SAFmiddle1textType.optional.meta(omittable: true))
183
+ attribute(:bank_account_number, base::SAFmiddle1textType.optional.meta(omittable: true))
184
+ attribute(:bank_account_name, base::SAFmiddle2textType.optional.meta(omittable: true))
185
+ attribute(:sort_code, base::SAFshorttextType.optional.meta(omittable: true))
186
+ attribute(:bic, base::SAFshorttextType.optional.meta(omittable: true))
187
+ attribute(:currency_code, base::ISOCurrencyCode.optional.meta(omittable: true))
188
+ attribute(:general_ledger_account_id, base::SAFmiddle2textType.optional.meta(omittable: true))
189
+ end
190
+
191
+ base.const_set(:BankAccountStructure, klass)
192
+
193
+ klass = Class.new(Dry::Struct) do
194
+ attribute(:registration_number, base::SAFmiddle1textType.optional.meta(omittable: true))
195
+ attribute(:name, base::SAFmiddle2textType)
196
+ attribute(:addresses, Types::Array.of(base::AddressStructure))
197
+ attribute(:contacts, Types::Array.of(base::ContactInformationStructure).optional.meta(omittable: true))
198
+ attribute(:tax_registrations, Types::Array.of(base::TaxIDStructure).optional.meta(omittable: true))
199
+ attribute(:bank_accounts, Types::Array.of(base::BankAccountStructure).optional.meta(omittable: true))
200
+ end
201
+
202
+ base.const_set(:CompanyStructure, klass)
203
+
204
+ klass = Class.new(base::CompanyStructure) do
205
+ attribute(:registration_number, base::SAFmiddle1textType)
206
+ attribute(:name, base::SAFmiddle2textType)
207
+ attribute(:addresses, Types::Array.of(base::AddressStructure))
208
+ attribute(:contacts, Types::Array.of(base::ContactInformationStructure))
209
+ attribute(:tax_registrations, Types::Array.of(base::TaxIDStructure).optional.meta(omittable: true))
210
+ attribute(:bank_accounts, Types::Array.of(base::BankAccountStructure).optional.meta(omittable: true))
211
+ end
212
+
213
+ base.const_set(:CompanyHeaderStructure, klass)
214
+
215
+ klass = Class.new(Dry::Struct) do
216
+ attribute(:audit_file_version, base::SAFcodeType)
217
+ attribute(:audit_file_country, base::ISOCountryCode)
218
+ # ignored sine XSD said "not in use."
219
+ # attribute :audit_file_region, base::SAFcodeType.optional.meta(omittable: true)
220
+ attribute(:audit_file_date_created, Types::Date)
221
+ attribute(:software_company_name, base::SAFmiddle2textType)
222
+ attribute(:software_id, base::SAFlongtextType)
223
+ attribute(:software_version, base::SAFshorttextType)
224
+ attribute(:company, base::CompanyHeaderStructure)
225
+ attribute(:default_currency_code, base::ISOCurrencyCode)
226
+ attribute(:selection_criteria, base::SelectionCriteriaStructure.optional.meta(omittable: true))
227
+ attribute(:header_comment, base::SAFlongtextType.optional.meta(omittable: true))
228
+ end
229
+
230
+ base.const_set(:HeaderStructure, klass)
231
+
232
+ klass = Class.new(base::HeaderStructure) do
233
+ attribute(:tax_accounting_basis, base::SAFshorttextType)
234
+ attribute(:tax_entity, base::SAFmiddle2textType.optional.meta(omittable: true))
235
+ attribute(:user_id, base::SAFmiddle1textType.optional.meta(omittable: true))
236
+ attribute(:audit_file_sender, base::CompanyStructure.optional.meta(omittable: true))
237
+ end
238
+
239
+ base.const_set(:Header, klass)
240
+
241
+ klass = Class.new(Dry::Struct) do
242
+ attribute(:account_id, base::SAFmiddle2textType)
243
+ attribute(:account_description, base::SAFlongtextType)
244
+ attribute(:standard_account_id, base::SAFmiddle1textType.optional.meta(omittable: true))
245
+ attribute(:grouping_category, base::SAFmiddle1textType.optional.meta(omittable: true))
246
+ attribute(:grouping_code, base::SAFmiddle1textType.optional.meta(omittable: true))
247
+ attribute(:account_type, base::SAFshorttextType)
248
+ attribute(:account_creation_date, Types::Date.optional.meta(omittable: true))
249
+ attribute(:opening_debit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
250
+ attribute(:opening_credit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
251
+ attribute(:closing_debit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
252
+ attribute(:closing_credit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
253
+ end
254
+
255
+ base.const_set(:Account, klass)
256
+
257
+ klass = Class.new(base::CompanyStructure) do
258
+ attribute(:customer_id, base::SAFmiddle1textType)
259
+ attribute(:self_billing_indicator, base::SAFcodeType.optional.meta(omittable: true))
260
+ attribute(:account_id, base::SAFmiddle2textType.optional.meta(omittable: true))
261
+ attribute(:opening_debit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
262
+ attribute(:opening_credit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
263
+ attribute(:closing_debit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
264
+ attribute(:closing_credit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
265
+ attribute(:party_info, base::PartyInfoStructure.optional.meta(omittable: true))
266
+ end
267
+
268
+ base.const_set(:Customer, klass)
269
+
270
+ klass = Class.new(base::CompanyStructure) do
271
+ attribute(:supplier_id, base::SAFmiddle1textType)
272
+ attribute(:self_billing_indicator, base::SAFcodeType.optional.meta(omittable: true))
273
+ attribute(:account_id, base::SAFmiddle2textType.optional.meta(omittable: true))
274
+ attribute(:opening_debit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
275
+ attribute(:opening_credit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
276
+ attribute(:closing_debit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
277
+ attribute(:closing_credit_balance, base::SAFmonetaryType.optional.meta(omittable: true))
278
+ attribute(:party_info, base::PartyInfoStructure.optional.meta(omittable: true))
279
+ end
280
+
281
+ base.const_set(:Supplier, klass)
282
+
283
+ klass = Class.new(Dry::Struct) do
284
+ attribute(:tax_code, base::SAFmiddle1textType)
285
+ attribute(:effective_date, Types::Date.optional.meta(omittable: true))
286
+ attribute(:expiration_date, Types::Date.optional.meta(omittable: true))
287
+ attribute(:description, base::SAFlongtextType.optional.meta(omittable: true))
288
+ attribute(:tax_percentage, Types::Decimal.optional.meta(omittable: true))
289
+ # ignored because XSD states "Not in Use."
290
+ # attribute :flat_tax_rate, AmountStructure.optional.meta(omittable: true)
291
+ attribute(:country, base::ISOCountryCode)
292
+ # ignored because XSD states "Not in Use."
293
+ # attribute :region, base::SAFcodeType.optional.meta(omittable: true)
294
+ attribute(:standard_tax_code, base::SAFmiddle1textType)
295
+ attribute(:compensation, Types::Bool.optional.meta(omittable: true))
296
+ attribute(:base_rates, Types::Array.of(Types::Decimal))
297
+ end
298
+
299
+ base.const_set(:TaxCodeDetails, klass)
300
+
301
+ klass = Class.new(Dry::Struct) do
302
+ attribute(:tax_type, base::SAFcodeType)
303
+ attribute(:description, base::SAFlongtextType)
304
+ attribute(:tax_code_details, Types::Array.of(base::TaxCodeDetails).constrained(min_size: 1))
305
+ end
306
+
307
+ base.const_set(:TaxTableEntry, klass)
308
+
309
+ klass = Class.new(Dry::Struct) do
310
+ attribute(:analysis_type, base::SAFcodeType)
311
+ attribute(:analysis_type_description, base::SAFlongtextType)
312
+ attribute(:analysis_id, base::SAFmiddle1textType)
313
+ attribute(:analysis_id_description, base::SAFlongtextType)
314
+ attribute(:start_date, Types::Date.optional.meta(omittable: true))
315
+ attribute(:end_date, Types::Date.optional.meta(omittable: true))
316
+ attribute(:status, base::SAFmiddle1textType.optional.meta(omittable: true))
317
+ attribute(:analyses, Types::Array.of(base::AnalysisPartyInfoStructure).optional.meta(omittable: true))
318
+ end
319
+
320
+ base.const_set(:AnalysisTypeTableEntry, klass)
321
+
322
+ klass = Class.new(base::CompanyStructure) do
323
+ attribute(:owner_id, base::SAFmiddle1textType.optional.meta(omittable: true))
324
+ attribute(:account_id, base::SAFmiddle2textType.optional.meta(omittable: true))
325
+ end
326
+
327
+ base.const_set(:Owner, klass)
328
+
329
+ klass = Class.new(Dry::Struct) do
330
+ attribute(:general_ledger_accounts, Types::Array.of(base::Account).optional.meta(omittable: true))
331
+ # ignored since xsd said "Not in use."
332
+ # attribute :taxonomies, Taxonomies
333
+ attribute(:customers, Types::Array.of(base::Customer).optional.meta(omittable: true))
334
+ attribute(:suppliers, Types::Array.of(base::Supplier).optional.meta(omittable: true))
335
+ attribute(:tax_table, Types::Array.of(base::TaxTableEntry).optional.meta(omittable: true))
336
+ # ignored since xsd said "Not in use."
337
+ # attribute :uom_table, UOMTable
338
+ attribute(:analysis_type_table, Types::Array.of(base::AnalysisTypeTableEntry).optional.meta(omittable: true))
339
+ # ignored since xsd said "Not in use."
340
+ # attribute :movement_type_table, MovementTypeTable
341
+ # ignored since xsd said "Not in use."
342
+ # attribute :products, Products
343
+ # ignored since xsd said "Not in use."
344
+ # attribute :physical_stock, PhysicalStock
345
+ attribute(:owners, Types::Array.of(base::Owner).optional.meta(omittable: true))
346
+ # ignored since xsd said "Not in use."
347
+ # attribute :assets, Assets
348
+ end
349
+
350
+ base.const_set(:MasterFiles, klass)
351
+
352
+ klass = Class.new(Dry::Struct) do
353
+ attribute(:record_id, base::SAFshorttextType)
354
+ attribute(:account_id, base::SAFmiddle2textType)
355
+ attribute(:analyses, Types::Array.of(base::AnalysisStructure).optional.meta(omittable: true))
356
+ attribute(:value_date, Types::Date.optional.meta(omittable: true))
357
+ attribute(:source_document_id, base::SAFmiddle1textType.optional.meta(omittable: true))
358
+ attribute(:customer_id, base::SAFmiddle1textType.optional.meta(omittable: true))
359
+ attribute(:supplier_id, base::SAFmiddle1textType.optional.meta(omittable: true))
360
+ attribute(:description, base::SAFlongtextType)
361
+ attribute(:debit_amount, base::AmountStructure.optional.meta(omittable: true))
362
+ attribute(:credit_amount, base::AmountStructure.optional.meta(omittable: true))
363
+ attribute(:tax_information, Types::Array.of(base::TaxInformationStructure).optional.meta(omittable: true))
364
+ attribute(:reference_number, base::SAFmiddle1textType.optional.meta(omittable: true))
365
+ attribute(:cid, base::SAFmiddle1textType.optional.meta(omittable: true))
366
+ attribute(:due_date, Types::Date.optional.meta(omittable: true))
367
+ attribute(:quantity, base::SAFquantityType.optional.meta(omittable: true))
368
+ attribute(:cross_reference, base::SAFmiddle1textType.optional.meta(omittable: true))
369
+ attribute(:system_entry_time, Types::DateTime.optional.meta(omittable: true))
370
+ attribute(:owner_id, base::SAFmiddle1textType.optional.meta(omittable: true))
371
+ end
372
+
373
+ base.const_set(:Line, klass)
374
+
375
+ klass = Class.new(Dry::Struct) do
376
+ attribute(:transaction_id, base::SAFmiddle2textType)
377
+ attribute(:period, Types::Integer)
378
+ attribute(:period_year, Types::Integer)
379
+ attribute(:transaction_date, Types::Date)
380
+ attribute(:source_id, base::SAFmiddle1textType.optional.meta(omittable: true))
381
+ attribute(:transaction_type, base::SAFshorttextType.optional.meta(omittable: true))
382
+ attribute(:description, base::SAFlongtextType)
383
+ attribute(:batch_id, base::SAFmiddle1textType.optional.meta(omittable: true))
384
+ attribute(:system_entry_date, Types::Date)
385
+ attribute(:gl_posting_date, Types::Date)
386
+ # ignored since xsd said "Not in use."
387
+ # attribute :CustomerID, base::SAFmiddle1textType.optional.meta(omittable: true)
388
+ # ignored since xsd said "Not in use."
389
+ # attribute :SupplierID, base::SAFmiddle1textType.optional.meta(omittable: true)
390
+ attribute(:system_id, base::SAFshorttextType.optional.meta(omittable: true))
391
+ attribute(:lines, Types::Array.of(base::Line))
392
+ end
393
+
394
+ base.const_set(:Transaction, klass)
395
+
396
+ klass = Class.new(Dry::Struct) do
397
+ attribute(:journal_id, base::SAFshorttextType)
398
+ attribute(:description, base::SAFlongtextType)
399
+ attribute(:type, base::SAFcodeType)
400
+ attribute(:transactions, Types::Array.of(base::Transaction).optional.meta(omittable: true))
401
+ end
402
+
403
+ base.const_set(:Journal, klass)
404
+
405
+ klass = Class.new(Dry::Struct) do
406
+ attribute(:number_of_entries, Types::Integer)
407
+ attribute(:total_debit, base::SAFmonetaryType)
408
+ attribute(:total_credit, base::SAFmonetaryType)
409
+ attribute(:journals, Types::Array.of(base::Journal).optional.meta(omittable: true))
410
+ end
411
+
412
+ base.const_set(:GeneralLedgerEntries, klass)
413
+
414
+ klass = Class.new(Dry::Struct) do
415
+ attribute(:header, base::Header)
416
+ attribute(:master_files, base::MasterFiles.optional.meta(omittable: true))
417
+ attribute(:general_ledger_entries, base::GeneralLedgerEntries.optional.meta(omittable: true))
418
+ # ignored since xsd said "Not in use."
419
+ # attribute :source_documents, base::SourceDocuments
420
+ end
421
+
422
+ base.const_set(:AuditFile, klass)
423
+ end
424
+ end
425
+ end
426
+
427
+ module Relaxed
428
+ ISOCurrencyCode = Types::String
429
+ ISOCountryCode = Types::String
430
+ SAFcodeType = Types::String
431
+ SAFshorttextType = Types::String
432
+ SAFmiddle1textType = Types::String
433
+ SAFmiddle2textType = Types::String
434
+ SAFlongtextType = Types::String
435
+ SAFmonetaryType = Types::Decimal
436
+ SAFexchangerateType = Types::Decimal
437
+ SAFquantityType = Types::Decimal
438
+ SAFweightType = Types::Decimal
439
+ AddressType = Types::String
440
+
441
+ extend ObjectStrutures
442
+ end
443
+
444
+ module Strict
445
+ ISOCurrencyCode = Types::String.constrained(size: 3)
446
+ ISOCountryCode = Types::String.constrained(size: 2)
447
+ SAFcodeType = Types::String.constrained(max_size: 9)
448
+ SAFshorttextType = Types::String.constrained(max_size: 18)
449
+ SAFmiddle1textType = Types::String.constrained(max_size: 35)
450
+ SAFmiddle2textType = Types::String.constrained(max_size: 70)
451
+ SAFlongtextType = Types::String.constrained(max_size: 256)
452
+ SAFmonetaryType = Types::Decimal.constrained(digits: 18, fraction_digitst: 2)
453
+ SAFexchangerateType = Types::Decimal.constrained(digits: 18, fraction_digitst: 8)
454
+ SAFquantityType = Types::Decimal.constrained(digits: 22, fraction_digitst: 6)
455
+ SAFweightType = Types::Decimal.constrained(digits: 14, fraction_digitst: 3)
456
+ AddressType = Types::String.enum("StreetAddress", "PostalAddress", "BillingAddress", "ShipToAddress", "ShipFromAddress")
457
+
458
+ extend ObjectStrutures
459
+ end
460
+
461
+ module Sliced
462
+ def self.slice_string(length)
463
+ proc do |value|
464
+ if value.is_a?(::String)
465
+ value.slice(0, length)
466
+ else
467
+ value
468
+ end
469
+ end
470
+ end
471
+
472
+ ISOCurrencyCode = Types.Constructor(Strict::ISOCurrencyCode, &slice_string(3))
473
+ ISOCountryCode = Types.Constructor(Strict::ISOCountryCode, &slice_string(2))
474
+ SAFcodeType = Types.Constructor(Strict::SAFcodeType, &slice_string(9))
475
+ SAFshorttextType = Types.Constructor(Strict::SAFshorttextType, &slice_string(18))
476
+ SAFmiddle1textType = Types.Constructor(Strict::SAFmiddle1textType, &slice_string(35))
477
+ SAFmiddle2textType = Types.Constructor(Strict::SAFmiddle2textType, &slice_string(70))
478
+ SAFlongtextType = Types.Constructor(Strict::SAFlongtextType, &slice_string(256))
479
+ SAFmonetaryType = Types::Decimal.constrained(digits: 18, fraction_digitst: 2)
480
+ SAFexchangerateType = Types::Decimal.constrained(digits: 18, fraction_digitst: 8)
481
+ SAFquantityType = Types::Decimal.constrained(digits: 22, fraction_digitst: 6)
482
+ SAFweightType = Types::Decimal.constrained(digits: 14, fraction_digitst: 3)
483
+
484
+ AddressType = Types::String.enum("StreetAddress", "PostalAddress", "BillingAddress", "ShipToAddress", "ShipFromAddress")
485
+
486
+ extend ObjectStrutures
487
+ end
488
+
489
+ # Want to keep developer experience where you can call
490
+ # SAFT::V2::Types::AuditFile and get a Audit file. We have three different
491
+ # AuditFile. We have Strict/Relaxed/Sliced.
492
+ include Strict
493
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require "pathname"
5
+
6
+ XSD_PATH = Pathname.new(__dir__) + ".." + ".." + ".." + "vendor" + "SAF-T_Financial_Schema_NO_1.10.xsd"
7
+
8
+ module SAFT::V2
9
+ class XsdValidate
10
+ def initialize(content)
11
+ @xml_errors = []
12
+ doc = Nokogiri::XML(content)
13
+ @xml_errors.push(*doc.errors)
14
+ xsd = Nokogiri::XML::Schema(XSD_PATH)
15
+ @xml_errors.push(*xsd.validate(doc))
16
+ # All error are of type Nokogiri::XML::SyntaxError
17
+ # https://nokogiri.org/rdoc/Nokogiri/XML/SyntaxError.html
18
+ end
19
+
20
+ def valid?
21
+ @xml_errors.none?
22
+ end
23
+
24
+ def errors
25
+ @xml_errors
26
+ end
27
+ end
28
+ end
data/lib/saft/v2.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require "dry-struct"
5
+
6
+ module SAFT::V2
7
+ module Types
8
+ include Dry.Types
9
+ end
10
+
11
+ def self.parse(xml_content)
12
+ doc = Nokogiri::XML(xml_content)
13
+ doc.remove_namespaces!
14
+ Parse.call(doc).then { Types::AuditFile.call(_1) }
15
+ end
16
+
17
+ def self.scribe(audit_file)
18
+ rule = Types.Instance(Types::Relaxed::AuditFile) |
19
+ Types.Instance(Types::Strict::AuditFile) |
20
+ Types.Instance(Types::Sliced::AuditFile)
21
+
22
+ raise ArgumentError unless rule.valid?(audit_file)
23
+
24
+ Scribe.write_xml(audit_file)
25
+ end
26
+
27
+ def self.validate(xml_content)
28
+ XsdValidate.new(xml_content)
29
+ end
30
+
31
+ def self.to_html(audit_file)
32
+ raise ArgumentError unless audit_file.is_a?(Types::AuditFile)
33
+
34
+ HTML.render(audit_file)
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module SAFT
2
+ VERSION = "0.1.1"
3
+ end
data/lib/saft.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require "nokogiri"
5
+
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.inflector.inflect("saft" => "SAFT", "html" => "HTML")
8
+ loader.setup
9
+
10
+ loader.do_not_eager_load("#{__dir__}/saft/v2/html.rb")
11
+
12
+ module SAFT
13
+ def self.gem_root
14
+ Pathname(__dir__).parent
15
+ end
16
+
17
+ def self.nokogiri_save_setting=(value)
18
+ @nokogiri_save_setting = value
19
+ end
20
+
21
+ def self.nokogiri_save_setting
22
+ return @nokogiri_save_setting if defined?(@nokogiri_save_setting)
23
+
24
+ @nokogiri_save_setting = Nokogiri::XML::Node::SaveOptions::AS_XML
25
+ end
26
+ end
data/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "saft_gem",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "dependencies": {
13
+ "tailwindcss": "^3.2.4"
14
+ }
15
+ }