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.
- checksums.yaml +7 -0
- data/.gemspec +34 -0
- data/.rspec +4 -0
- data/.rubocop.yml +72 -0
- data/.rubocop_strict.yml +7 -0
- data/.ruby-gemset +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +26 -0
- data/Rakefile +18 -0
- data/docs/Norwegian SAF-T Financial data.pdf +0 -0
- data/docs/Norwegian SAF-T Standard VAT:Tax codes.pdf +0 -0
- data/docs/saf-t v2 xsd by oecd.pdf +0 -0
- data/lib/saft/v2/html.css +3 -0
- data/lib/saft/v2/html.rb +685 -0
- data/lib/saft/v2/html_dist.css +594 -0
- data/lib/saft/v2/norway.rb +133 -0
- data/lib/saft/v2/parse.rb +351 -0
- data/lib/saft/v2/scribe.rb +364 -0
- data/lib/saft/v2/types.rb +493 -0
- data/lib/saft/v2/xsd_validate.rb +28 -0
- data/lib/saft/v2.rb +36 -0
- data/lib/saft/version.rb +3 -0
- data/lib/saft.rb +26 -0
- data/package.json +15 -0
- data/pnpm-lock.yaml +443 -0
- data/readme.md +92 -0
- data/tailwind.config.js +10 -0
- data/vendor/SAF-T_Financial_Schema_NO_1.10.xsd +3343 -0
- data/vendor/norway/general_ledger_standard_accounts.xsd +54 -0
- data/vendor/norway/general_ledger_standard_accounts_2_character.csv +75 -0
- data/vendor/norway/general_ledger_standard_accounts_2_character.xml +373 -0
- data/vendor/norway/general_ledger_standard_accounts_4_character.csv +747 -0
- data/vendor/norway/general_ledger_standard_accounts_4_character.xml +3733 -0
- data/vendor/norway/standard_tax_codes.csv +31 -0
- data/vendor/norway/standard_tax_codes.xml +199 -0
- data/vendor/norway/standard_tax_codes.xsd +67 -0
- metadata +121 -0
data/lib/saft/v2/html.rb
ADDED
@@ -0,0 +1,685 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-struct"
|
4
|
+
require "tubby"
|
5
|
+
|
6
|
+
module SAFT::V2
|
7
|
+
module HTML
|
8
|
+
def self.css
|
9
|
+
File.read(css_path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.css_path
|
13
|
+
Pathname.new(__dir__) + "html_dist.css"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.format_big_decimal(big_decimal)
|
17
|
+
integer, decimal = big_decimal.to_s("F").split(".")
|
18
|
+
integer = integer.reverse.scan(/.{1,3}/).join(" ").reverse
|
19
|
+
|
20
|
+
"#{integer},#{decimal.ljust(2, "0")}"
|
21
|
+
end
|
22
|
+
|
23
|
+
module DryStructRenderTubby
|
24
|
+
refine(String) do
|
25
|
+
def to_tubby
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
refine(Date) do
|
31
|
+
def to_tubby
|
32
|
+
to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
refine(NilClass) do
|
37
|
+
def to_tubby
|
38
|
+
"-"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
refine(DateTime) do
|
43
|
+
def to_tubby
|
44
|
+
to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
refine(Integer) do
|
49
|
+
def to_tubby
|
50
|
+
to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
refine(BigDecimal) do
|
55
|
+
def to_tubby
|
56
|
+
HTML.format_big_decimal(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
refine(Array) do
|
61
|
+
def to_tubby
|
62
|
+
Tubby.new { |t| each { t << _1 } }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
refine(Dry::Struct) do
|
67
|
+
def to_tubby
|
68
|
+
Tubby.new { |t| t.div(class: "mb-2 pl-2 border-l-2") { t << RenderHash.new(attributes) } }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
refine(SAFT::V2::Types::AuditFile) do
|
73
|
+
def to_tubby
|
74
|
+
Tubby.new { |t|
|
75
|
+
t.div(class: "pl-2 border-l-2") {
|
76
|
+
t.div(class: "mb-2 border-b-2") { t.strong("Header") }
|
77
|
+
t << RenderHash.new(header.attributes)
|
78
|
+
}
|
79
|
+
|
80
|
+
if master_files
|
81
|
+
t.div(class: "pl-2 border-l-2") {
|
82
|
+
t.div(class: "mb-2 border-b-2") { t.strong("MasterFiles") }
|
83
|
+
t << master_files
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
if general_ledger_entries
|
88
|
+
t.div(class: "pl-2 border-l-2") {
|
89
|
+
t.div(class: "mb-2 border-b-2") { t.strong("GeneralLedgerEntries") }
|
90
|
+
t << general_ledger_entries
|
91
|
+
}
|
92
|
+
end
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
refine(SAFT::V2::Types::MasterFiles) do
|
98
|
+
def to_tubby
|
99
|
+
Tubby.new { |t|
|
100
|
+
if general_ledger_accounts
|
101
|
+
t.strong("General ledger accounts")
|
102
|
+
t.div(class: "pl-2 border-l-2") { t << RenderGeneralLedgerTable.new(general_ledger_accounts) }
|
103
|
+
end
|
104
|
+
|
105
|
+
if customers
|
106
|
+
t.strong("Customers")
|
107
|
+
t.div(class: "pl-2 border-l-2 flex flex-wrap") {
|
108
|
+
customers.each do |customer|
|
109
|
+
t << CompanyCard.new(customer)
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
if suppliers
|
115
|
+
t.strong("Suppliers")
|
116
|
+
t.div(class: "pl-2 border-l-2 flex flex-wrap") {
|
117
|
+
suppliers.each do |supplier|
|
118
|
+
t << CompanyCard.new(supplier)
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
if tax_table
|
124
|
+
t.strong("Tax able")
|
125
|
+
t.div(class: "pl-2 border-l-2") { t << TaxTable.new(tax_table) }
|
126
|
+
end
|
127
|
+
|
128
|
+
if analysis_type_table
|
129
|
+
t.strong("Analysis type table")
|
130
|
+
t.div(class: "pl-2 border-l-2") { t << AnalysisTypeTable.new(analysis_type_table) }
|
131
|
+
end
|
132
|
+
|
133
|
+
if owners
|
134
|
+
t.strong("Owners")
|
135
|
+
t.div(class: "pl-2 border-l-2") { owners.each { |owner| t << owner } }
|
136
|
+
end
|
137
|
+
}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
refine(SAFT::V2::Types::Transaction) do
|
142
|
+
def to_tubby
|
143
|
+
Tubby.new { |t|
|
144
|
+
t.div(
|
145
|
+
id: "transaction-#{transaction_id}",
|
146
|
+
class: "mb-2 pl-2 border-l-2 flex",
|
147
|
+
) {
|
148
|
+
t.div(class: "w-80") {
|
149
|
+
t
|
150
|
+
.a(
|
151
|
+
class: "whitespace-pre underline underline-offset-1 hover:underline-offset-2 visited:underline-decoration-2",
|
152
|
+
href: "#transaction-#{transaction_id}",
|
153
|
+
) {
|
154
|
+
t.div {
|
155
|
+
t.strong("Transaction id ")
|
156
|
+
t << transaction_id
|
157
|
+
}
|
158
|
+
}
|
159
|
+
t.div { t << RenderHash.new(attributes.except(:transaction_id, :lines)) }
|
160
|
+
}
|
161
|
+
t.div {
|
162
|
+
t.strong("Lines ")
|
163
|
+
t << LinesTable.new(lines)
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
using DryStructRenderTubby
|
172
|
+
|
173
|
+
class RenderHash
|
174
|
+
def initialize(hash)
|
175
|
+
@hash = hash.select { |_, value| value }
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_tubby
|
179
|
+
Tubby.new { |t|
|
180
|
+
@hash.each do |key, value|
|
181
|
+
t.div {
|
182
|
+
t.strong("#{key.to_s.tr("_", " ").capitalize} ")
|
183
|
+
t << value
|
184
|
+
}
|
185
|
+
end
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class RenderGeneralLedgerTable
|
191
|
+
def initialize(accounts)
|
192
|
+
@accounts = accounts
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_tubby
|
196
|
+
Tubby.new { |t|
|
197
|
+
t.table {
|
198
|
+
t.thead {
|
199
|
+
t.tr {
|
200
|
+
t.th("Id")
|
201
|
+
t.th("Description")
|
202
|
+
t.th("Std account")
|
203
|
+
t.th("Opening balance")
|
204
|
+
t.th("Closing balance")
|
205
|
+
t.th("Rest")
|
206
|
+
}
|
207
|
+
}
|
208
|
+
t.tbody {
|
209
|
+
@accounts.each do |account|
|
210
|
+
std_account = SAFT::V2::Norway.std_account(account.standard_account_id)
|
211
|
+
std_account_title = "Not found"
|
212
|
+
if std_account
|
213
|
+
std_account_title = <<~TEXT
|
214
|
+
Account no #{std_account.number}
|
215
|
+
#{std_account.description_en}
|
216
|
+
#{std_account.description_no}
|
217
|
+
TEXT
|
218
|
+
end
|
219
|
+
|
220
|
+
t.tr {
|
221
|
+
t.td(account.account_id)
|
222
|
+
t.td(account.account_description)
|
223
|
+
t.td(account.standard_account_id, title: std_account_title)
|
224
|
+
t.td {
|
225
|
+
t.div(class: "flex justify-between") {
|
226
|
+
if account.opening_debit_balance
|
227
|
+
t.span("Debit")
|
228
|
+
t.span(account.opening_debit_balance)
|
229
|
+
end
|
230
|
+
|
231
|
+
if account.opening_credit_balance
|
232
|
+
t.span("Credit")
|
233
|
+
t.span(-account.opening_credit_balance)
|
234
|
+
end
|
235
|
+
}
|
236
|
+
}
|
237
|
+
t.td {
|
238
|
+
t.div(class: "flex justify-between") {
|
239
|
+
if account.closing_debit_balance
|
240
|
+
t.span("Debit")
|
241
|
+
t.span(account.closing_debit_balance)
|
242
|
+
end
|
243
|
+
|
244
|
+
if account.closing_credit_balance
|
245
|
+
t.span("Credit")
|
246
|
+
t.span(-account.closing_credit_balance)
|
247
|
+
end
|
248
|
+
}
|
249
|
+
}
|
250
|
+
t.td(class: "pl-2") {
|
251
|
+
t.div(class: "pl-2 border-l-2") {
|
252
|
+
t <<
|
253
|
+
RenderHash.new(
|
254
|
+
account
|
255
|
+
.attributes
|
256
|
+
.except(
|
257
|
+
:account_id,
|
258
|
+
:account_description,
|
259
|
+
:standard_account_id,
|
260
|
+
:opening_debit_balance,
|
261
|
+
:opening_credit_balance,
|
262
|
+
:closing_debit_balance,
|
263
|
+
:closing_credit_balance,
|
264
|
+
),
|
265
|
+
)
|
266
|
+
}
|
267
|
+
}
|
268
|
+
}
|
269
|
+
end
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class CompanyCard
|
277
|
+
def initialize(company)
|
278
|
+
@company = company
|
279
|
+
end
|
280
|
+
|
281
|
+
def to_tubby
|
282
|
+
Tubby.new { |t|
|
283
|
+
t.div(class: "min-w-[20rem] max-w-[20rem] mr-8 mb-2") {
|
284
|
+
t.div(class: "pl-2 border-l-2") {
|
285
|
+
t.span("Supplier", class: "font-semibold") if @company.is_a?(Types::Supplier)
|
286
|
+
t.span("Customer", class: "font-semibold") if @company.is_a?(Types::Customer)
|
287
|
+
t << RenderHash.new(@company.attributes)
|
288
|
+
}
|
289
|
+
}
|
290
|
+
}
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class TaxTable
|
295
|
+
def initialize(tax_table)
|
296
|
+
@tax_table = tax_table
|
297
|
+
end
|
298
|
+
|
299
|
+
def to_tubby
|
300
|
+
Tubby.new { |t|
|
301
|
+
t.table {
|
302
|
+
t.thead {
|
303
|
+
t.tr {
|
304
|
+
t.th("Tax code")
|
305
|
+
t.th("Description")
|
306
|
+
t.th("Country")
|
307
|
+
t.th("Std code")
|
308
|
+
t.th("Tax %", class: "text-right")
|
309
|
+
t.th("Base rate", class: "text-right")
|
310
|
+
t.th("rest")
|
311
|
+
}
|
312
|
+
}
|
313
|
+
t.tbody {
|
314
|
+
@tax_table.each { |table|
|
315
|
+
table.tax_code_details.each { |detail|
|
316
|
+
vat_code = SAFT::V2::Norway.vat_code(detail.standard_tax_code)
|
317
|
+
vat_code_title = "Not found"
|
318
|
+
if vat_code
|
319
|
+
vat_code_title = <<~TEXT
|
320
|
+
Vat Code #{vat_code.code}
|
321
|
+
#{vat_code.description_en}
|
322
|
+
#{vat_code.description_no}
|
323
|
+
#{vat_code.tax_rate}
|
324
|
+
#{"Can be used for compensation" if vat_code.compensation}
|
325
|
+
TEXT
|
326
|
+
end
|
327
|
+
|
328
|
+
t.tr {
|
329
|
+
t.td(detail.tax_code)
|
330
|
+
t.td(detail.description)
|
331
|
+
t.td(detail.country)
|
332
|
+
t.td(detail.standard_tax_code, title: vat_code_title)
|
333
|
+
t.td(detail.tax_percentage, class: "text-right")
|
334
|
+
t.td(class: "text-right") { detail.base_rates.each { t.div(_1) } }
|
335
|
+
t.td(class: "pl-2") {
|
336
|
+
t.div(class: "pl-2 border-l-2") {
|
337
|
+
t <<
|
338
|
+
RenderHash.new(
|
339
|
+
detail
|
340
|
+
.attributes
|
341
|
+
.except(
|
342
|
+
:tax_code,
|
343
|
+
:description,
|
344
|
+
:country,
|
345
|
+
:standard_tax_code,
|
346
|
+
:base_rate,
|
347
|
+
:tax_percentage,
|
348
|
+
),
|
349
|
+
)
|
350
|
+
}
|
351
|
+
}
|
352
|
+
}
|
353
|
+
}
|
354
|
+
}
|
355
|
+
}
|
356
|
+
}
|
357
|
+
}
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
class AnalysisTypeTable
|
362
|
+
def initialize(analysis_type_table)
|
363
|
+
@analysis_type_table = analysis_type_table
|
364
|
+
end
|
365
|
+
|
366
|
+
def to_tubby
|
367
|
+
Tubby.new { |t|
|
368
|
+
t.table {
|
369
|
+
t.thead {
|
370
|
+
t.tr {
|
371
|
+
t.th("Type")
|
372
|
+
t.th("Type description")
|
373
|
+
t.th("ID")
|
374
|
+
t.th("ID Description")
|
375
|
+
t.th("Rest")
|
376
|
+
}
|
377
|
+
}
|
378
|
+
t.tbody {
|
379
|
+
@analysis_type_table.each { |entry|
|
380
|
+
html_analysis = t.get_analysis(entry.analysis_id, entry.analysis_type)
|
381
|
+
t.tr(id: html_analysis.html_id) {
|
382
|
+
t.td(entry.analysis_type)
|
383
|
+
t.td(entry.analysis_type_description)
|
384
|
+
t.td(entry.analysis_id)
|
385
|
+
t.td(entry.analysis_id_description)
|
386
|
+
t.td(class: "pl-2") {
|
387
|
+
t.div(class: "pl-2 border-l-2") {
|
388
|
+
t <<
|
389
|
+
RenderHash.new(
|
390
|
+
entry
|
391
|
+
.attributes
|
392
|
+
.except(
|
393
|
+
:analysis_type,
|
394
|
+
:analysis_type_description,
|
395
|
+
:analysis_id,
|
396
|
+
:analysis_id_description,
|
397
|
+
),
|
398
|
+
)
|
399
|
+
}
|
400
|
+
}
|
401
|
+
}
|
402
|
+
}
|
403
|
+
}
|
404
|
+
}
|
405
|
+
}
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
class LinesTable
|
410
|
+
def initialize(lines)
|
411
|
+
@lines = lines
|
412
|
+
end
|
413
|
+
|
414
|
+
def to_tubby
|
415
|
+
Tubby.new { |t|
|
416
|
+
t.table {
|
417
|
+
t.thead {
|
418
|
+
t.tr {
|
419
|
+
t.th("RecordID")
|
420
|
+
t.th("AccountID")
|
421
|
+
t.th("Analysis")
|
422
|
+
t.th("ValueDate")
|
423
|
+
t.th("Description")
|
424
|
+
t.th("Dedit Amount", class: "text-right")
|
425
|
+
t.th("Credit Amount", class: "text-right")
|
426
|
+
t.th("Rest")
|
427
|
+
}
|
428
|
+
}
|
429
|
+
|
430
|
+
t.tbody {
|
431
|
+
@lines.each { |line|
|
432
|
+
t.tr {
|
433
|
+
t.td(line.record_id)
|
434
|
+
t.td {
|
435
|
+
account = t.get_account(line.account_id)
|
436
|
+
t.div(title: account.title) { t << line.account_id }
|
437
|
+
}
|
438
|
+
|
439
|
+
t.td {
|
440
|
+
line.analyses&.each do |line_analysis|
|
441
|
+
analysis = t.get_analysis(
|
442
|
+
line_analysis.analysis_id,
|
443
|
+
line_analysis.analysis_type,
|
444
|
+
)
|
445
|
+
t.div(title: analysis.title) { t << analysis.link { t << "#{line_analysis.analysis_type} #{line_analysis.analysis_id}" } }
|
446
|
+
end
|
447
|
+
}
|
448
|
+
t.td(line.value_date)
|
449
|
+
t.td(line.description)
|
450
|
+
t.td(line.debit_amount&.amount, class: "text-right")
|
451
|
+
t.td(line.credit_amount&.amount, class: "text-right")
|
452
|
+
t.td {
|
453
|
+
t.div(class: "mb-2 pl-2 border-l-2") {
|
454
|
+
if line.customer_id
|
455
|
+
customer = t.get_customer(line.customer_id)
|
456
|
+
t.div(title: customer.title) {
|
457
|
+
t.strong("Customer ")
|
458
|
+
t << line.customer_id
|
459
|
+
t << " #{customer.name}"
|
460
|
+
}
|
461
|
+
end
|
462
|
+
|
463
|
+
if line.supplier_id
|
464
|
+
supplier = t.get_supplier(line.supplier_id)
|
465
|
+
t.div(title: supplier.title) {
|
466
|
+
t.strong("Supplier ")
|
467
|
+
t << line.supplier_id
|
468
|
+
t << " #{supplier.name}"
|
469
|
+
}
|
470
|
+
end
|
471
|
+
|
472
|
+
t <<
|
473
|
+
RenderHash.new(
|
474
|
+
line
|
475
|
+
.attributes
|
476
|
+
.except(
|
477
|
+
:record_id,
|
478
|
+
:account_id,
|
479
|
+
:customer_id,
|
480
|
+
:supplier_id,
|
481
|
+
:analyses,
|
482
|
+
:value_date,
|
483
|
+
:description,
|
484
|
+
:debit_amount,
|
485
|
+
:credit_amount,
|
486
|
+
),
|
487
|
+
)
|
488
|
+
}
|
489
|
+
}
|
490
|
+
}
|
491
|
+
}
|
492
|
+
}
|
493
|
+
}
|
494
|
+
}
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
class Analysis
|
499
|
+
def initialize(analysis)
|
500
|
+
@analysis = analysis
|
501
|
+
end
|
502
|
+
|
503
|
+
attr_reader :analysis
|
504
|
+
|
505
|
+
def title
|
506
|
+
<<~TEXT
|
507
|
+
#{analysis.analysis_type}(#{analysis.analysis_type_description})
|
508
|
+
#{analysis.analysis_id}(#{analysis.analysis_id_description})
|
509
|
+
TEXT
|
510
|
+
end
|
511
|
+
|
512
|
+
def html_id
|
513
|
+
"analysis-#{analysis.analysis_type}-#{analysis.analysis_id}"
|
514
|
+
end
|
515
|
+
|
516
|
+
def link
|
517
|
+
Tubby.new { |t| t.a(href: "##{html_id}") { yield } }
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
class NotFoundAnalysys
|
522
|
+
include Singleton
|
523
|
+
|
524
|
+
def title
|
525
|
+
"Could not find analysis"
|
526
|
+
end
|
527
|
+
|
528
|
+
def link
|
529
|
+
yield
|
530
|
+
nil
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
class Account
|
535
|
+
attr_reader(:account)
|
536
|
+
|
537
|
+
def initialize(account)
|
538
|
+
@account = account
|
539
|
+
end
|
540
|
+
|
541
|
+
def title
|
542
|
+
<<~TEXT
|
543
|
+
#{account.account_id} #{account.account_description}
|
544
|
+
Std account #{account.standard_account_id}
|
545
|
+
opening balance #{HTML.format_big_decimal(account.opening_debit_balance || -account.opening_credit_balance)}
|
546
|
+
closing balance #{HTML.format_big_decimal(account.closing_debit_balance || -account.closing_credit_balance)}
|
547
|
+
TEXT
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
class NotFoundAccount
|
552
|
+
include(Singleton)
|
553
|
+
|
554
|
+
def title
|
555
|
+
"Could not find account"
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
class Customer
|
560
|
+
attr_reader(:customer)
|
561
|
+
|
562
|
+
def initialize(customer)
|
563
|
+
@customer = customer
|
564
|
+
end
|
565
|
+
|
566
|
+
def title
|
567
|
+
<<~TEXT
|
568
|
+
#{customer.name} #{customer.registration_number}
|
569
|
+
opening balance #{HTML.format_big_decimal(customer.opening_debit_balance || -customer.opening_credit_balance)}
|
570
|
+
closing balance #{HTML.format_big_decimal(customer.closing_debit_balance || -customer.closing_credit_balance)}
|
571
|
+
TEXT
|
572
|
+
end
|
573
|
+
|
574
|
+
def name
|
575
|
+
customer.name
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
class NotFoundCustomer
|
580
|
+
include(Singleton)
|
581
|
+
|
582
|
+
def title
|
583
|
+
"Could not find customer"
|
584
|
+
end
|
585
|
+
|
586
|
+
def name
|
587
|
+
"Not found in Customers block"
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
class Supplier
|
592
|
+
attr_reader(:supplier)
|
593
|
+
|
594
|
+
def initialize(supplier)
|
595
|
+
@supplier = supplier
|
596
|
+
end
|
597
|
+
|
598
|
+
def title
|
599
|
+
<<~TEXT
|
600
|
+
#{supplier.name} #{supplier.registration_number}
|
601
|
+
opening balance #{HTML.format_big_decimal(supplier.opening_debit_balance || -supplier.opening_credit_balance)}
|
602
|
+
closing balance #{HTML.format_big_decimal(supplier.closing_debit_balance || -supplier.closing_credit_balance)}
|
603
|
+
TEXT
|
604
|
+
end
|
605
|
+
|
606
|
+
def name
|
607
|
+
supplier.name
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
class NotFoundSupplier
|
612
|
+
include(Singleton)
|
613
|
+
|
614
|
+
def title
|
615
|
+
"Could not find supplier"
|
616
|
+
end
|
617
|
+
|
618
|
+
def name
|
619
|
+
"Not found in Suppliers block"
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
class SaftRenderer < Tubby::Renderer
|
624
|
+
def <<(obj)
|
625
|
+
obj = obj.to_tubby if obj.respond_to?(:to_tubby)
|
626
|
+
if obj.is_a?(Tubby::Template)
|
627
|
+
obj.render_with(self)
|
628
|
+
else
|
629
|
+
@target << CGI.escape_html(obj.to_s)
|
630
|
+
end
|
631
|
+
|
632
|
+
self
|
633
|
+
end
|
634
|
+
|
635
|
+
def audit_file=(audit_file)
|
636
|
+
(audit_file.master_files&.analysis_type_table || [])
|
637
|
+
.each_with_object({}) { _2[[_1.analysis_id, _1.analysis_type]] = Analysis.new(_1) }
|
638
|
+
.tap { @analysis_lookup = _1 }
|
639
|
+
|
640
|
+
(audit_file.master_files&.customers || [])
|
641
|
+
.each_with_object({}) { _2[_1.customer_id] = Customer.new(_1) }
|
642
|
+
.tap { @customer_lookup = _1 }
|
643
|
+
|
644
|
+
(audit_file.master_files&.suppliers || [])
|
645
|
+
.each_with_object({}) { _2[_1.supplier_id] = Supplier.new(_1) }
|
646
|
+
.tap { @supplier_lookup = _1 }
|
647
|
+
|
648
|
+
(audit_file.master_files&.general_ledger_accounts || [])
|
649
|
+
.each_with_object({}) { _2[_1.account_id] = Account.new(_1) }
|
650
|
+
.tap { @account_lookup = _1 }
|
651
|
+
end
|
652
|
+
|
653
|
+
def get_analysis(id, type)
|
654
|
+
@analysis_lookup.fetch([id, type]) { NotFoundAnalysys.instance }
|
655
|
+
end
|
656
|
+
|
657
|
+
def get_account(id)
|
658
|
+
@account_lookup.fetch(id) { NotFoundAccount.instance }
|
659
|
+
end
|
660
|
+
|
661
|
+
def get_customer(id)
|
662
|
+
@customer_lookup.fetch(id) { NotFoundCustomer.instance }
|
663
|
+
end
|
664
|
+
|
665
|
+
def get_supplier(id)
|
666
|
+
@supplier_lookup.fetch(id) { NotFoundSupplier.instance }
|
667
|
+
end
|
668
|
+
|
669
|
+
def a(*args, **kwargs, &block)
|
670
|
+
kwargs[:class] ||= ""
|
671
|
+
kwargs[:class] += " whitespace-pre underline underline-offset-1 hover:underline-offset-2 visited:underline-decoration-2"
|
672
|
+
super(*args, **kwargs, &block)
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
def self.render(audit_file)
|
677
|
+
target = +""
|
678
|
+
renderer = SaftRenderer.new(target)
|
679
|
+
renderer.audit_file = audit_file
|
680
|
+
renderer << audit_file
|
681
|
+
|
682
|
+
Tubby.new { |t| t.raw!(target) }
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|