tallty_import_export 1.0.32 → 1.1.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/Gemfile.lock +20 -16
- data/lib/tallty_import_export/attr/export_header.rb +100 -0
- data/lib/tallty_import_export/attr.rb +1 -0
- data/lib/tallty_import_export/export.rb +72 -24
- data/lib/tallty_import_export/export_form.rb +134 -0
- data/lib/tallty_import_export/export_payload/cell.rb +101 -0
- data/lib/tallty_import_export/export_payload/cell_column.rb +54 -0
- data/lib/tallty_import_export/export_payload/value.rb +15 -0
- data/lib/tallty_import_export/export_payload.rb +62 -0
- data/lib/tallty_import_export/exportable_form.rb +21 -0
- data/lib/tallty_import_export/import.rb +61 -14
- data/lib/tallty_import_export/importable.rb +4 -0
- data/lib/tallty_import_export/version.rb +1 -1
- data/lib/tallty_import_export.rb +8 -1
- data/tallty_import_export.gemspec +2 -2
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72c586f1a3e13625520d1ce6378e478ee8dc6cf0055a16528f63ead86c20794f
|
4
|
+
data.tar.gz: 857a44c2a36c69347a2baed91855d943a16581427bdd1b562dd02e54d4a7d97c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ae472d4119ebb57fe5066615d49001ba99a417a067d0586f25010338e7bba1d5fdda737cf8a7892483e68b079af065e21862e87225409f3a7f2e7e0683f8d5f
|
7
|
+
data.tar.gz: fd6af86be0af8b552d2d5170e86b807a301550e7b7f3e604fa0cef539fe6f4630083a4117a494071bc734f83fb29950425f6cdb147b82e130b978c0a2fc8af6f
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tallty_import_export (1.0.
|
4
|
+
tallty_import_export (1.0.35)
|
5
5
|
activesupport
|
6
|
+
attr_json
|
6
7
|
caxlsx
|
7
8
|
redis
|
8
9
|
redis-objects
|
@@ -14,36 +15,40 @@ PATH
|
|
14
15
|
GEM
|
15
16
|
remote: https://gems.ruby-china.com/
|
16
17
|
specs:
|
17
|
-
activemodel (
|
18
|
-
activesupport (=
|
19
|
-
|
18
|
+
activemodel (7.0.2.4)
|
19
|
+
activesupport (= 7.0.2.4)
|
20
|
+
activerecord (7.0.2.4)
|
21
|
+
activemodel (= 7.0.2.4)
|
22
|
+
activesupport (= 7.0.2.4)
|
23
|
+
activesupport (7.0.2.4)
|
20
24
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
25
|
i18n (>= 1.6, < 2)
|
22
26
|
minitest (>= 5.1)
|
23
27
|
tzinfo (~> 2.0)
|
24
|
-
|
25
|
-
|
28
|
+
attr_json (1.4.0)
|
29
|
+
activerecord (>= 5.0.0, < 7.1)
|
30
|
+
caxlsx (3.2.0)
|
26
31
|
htmlentities (~> 4.3, >= 4.3.4)
|
27
32
|
marcel (~> 1.0)
|
28
33
|
nokogiri (~> 1.10, >= 1.10.4)
|
29
34
|
rubyzip (>= 1.3.0, < 3)
|
30
|
-
concurrent-ruby (1.1.
|
35
|
+
concurrent-ruby (1.1.10)
|
31
36
|
diff-lcs (1.4.4)
|
32
37
|
htmlentities (4.3.4)
|
33
|
-
i18n (1.
|
38
|
+
i18n (1.10.0)
|
34
39
|
concurrent-ruby (~> 1.0)
|
35
40
|
marcel (1.0.2)
|
36
|
-
mini_portile2 (2.
|
37
|
-
minitest (5.
|
38
|
-
nokogiri (1.
|
39
|
-
mini_portile2 (~> 2.
|
41
|
+
mini_portile2 (2.8.0)
|
42
|
+
minitest (5.15.0)
|
43
|
+
nokogiri (1.13.4)
|
44
|
+
mini_portile2 (~> 2.8.0)
|
40
45
|
racc (~> 1.4)
|
41
|
-
racc (1.
|
46
|
+
racc (1.6.0)
|
42
47
|
rake (12.3.3)
|
43
|
-
redis (4.
|
48
|
+
redis (4.6.0)
|
44
49
|
redis-objects (1.5.1)
|
45
50
|
redis (~> 4.2)
|
46
|
-
roo (2.
|
51
|
+
roo (2.9.0)
|
47
52
|
nokogiri (~> 1)
|
48
53
|
rubyzip (>= 1.3.0, < 3.0.0)
|
49
54
|
roo-xls (1.2.0)
|
@@ -74,7 +79,6 @@ GEM
|
|
74
79
|
tallty_duck_record
|
75
80
|
tzinfo (2.0.4)
|
76
81
|
concurrent-ruby (~> 1.0)
|
77
|
-
zeitwerk (2.4.2)
|
78
82
|
zip-zip (0.3)
|
79
83
|
rubyzip (>= 1.0.0)
|
80
84
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module TalltyImportExport
|
2
|
+
module Attr
|
3
|
+
class ExportHeaderItem
|
4
|
+
include AttrJson::Model
|
5
|
+
attr_json_config(bad_cast: :as_nil, unknown_key: :allow)
|
6
|
+
|
7
|
+
attr_json :key, :string
|
8
|
+
attr_json :name, :string
|
9
|
+
attr_json :attr_type, :string
|
10
|
+
attr_json :format, :string
|
11
|
+
attr_json :method, :string
|
12
|
+
attr_json :chain, :string, array: true
|
13
|
+
attr_json :merge, :boolean
|
14
|
+
attr_json :json, :string
|
15
|
+
attr_json :select, ActiveModel::Type::Value.new, array: true
|
16
|
+
attr_json :source, :boolean
|
17
|
+
attr_json :proc, ActiveModel::Type::Value.new, array: true
|
18
|
+
attr_json :children, self.to_type, array: true
|
19
|
+
|
20
|
+
attr_accessor :depth, :parent_path, :seq
|
21
|
+
|
22
|
+
def calcute_flatten_value _depth, _parent_path
|
23
|
+
@depth = _depth
|
24
|
+
@parent_path = _parent_path
|
25
|
+
if children && children.count > 0
|
26
|
+
children.map do |child|
|
27
|
+
child.calcute_flatten_value(_depth + 1, [*_parent_path, self])
|
28
|
+
end.compact.flatten
|
29
|
+
else
|
30
|
+
[self]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def cluster_seqs
|
35
|
+
calcute_flatten_value(0, []).map(&:seq)
|
36
|
+
end
|
37
|
+
|
38
|
+
def seq
|
39
|
+
@seq ||= SecureRandom.uuid
|
40
|
+
end
|
41
|
+
|
42
|
+
# def living_alone?
|
43
|
+
# depth == 0 && children.present?
|
44
|
+
# end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ExportHeader
|
48
|
+
include AttrJson::Model
|
49
|
+
attr_json_config(bad_cast: :as_nil, unknown_key: :allow)
|
50
|
+
|
51
|
+
attr_json :items, ExportHeaderItem.to_type, array: true
|
52
|
+
|
53
|
+
attr_reader :header_seq_to_axios
|
54
|
+
|
55
|
+
def flatten_value
|
56
|
+
items.map do |item|
|
57
|
+
item.calcute_flatten_value(0, [])
|
58
|
+
end.flatten
|
59
|
+
end
|
60
|
+
|
61
|
+
def height
|
62
|
+
flatten_value.map(&:depth).max + 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def header_lines
|
66
|
+
result = []
|
67
|
+
|
68
|
+
height.times.each do |row_index|
|
69
|
+
col_index = 0
|
70
|
+
|
71
|
+
next_line = flatten_value.map do |header|
|
72
|
+
next_header = [*header.try(:parent_path), header]&.[](row_index)
|
73
|
+
|
74
|
+
@header_seq_to_axios ||= {}
|
75
|
+
|
76
|
+
|
77
|
+
if next_header
|
78
|
+
@header_seq_to_axios[next_header.seq] ||= []
|
79
|
+
@header_seq_to_axios[next_header.seq].push([col_index, row_index])
|
80
|
+
end
|
81
|
+
|
82
|
+
# 处理最后一列
|
83
|
+
if (row_index === height - 1)
|
84
|
+
@header_seq_to_axios[header.seq] ||= []
|
85
|
+
@header_seq_to_axios[header.seq].push([col_index, row_index])
|
86
|
+
end
|
87
|
+
|
88
|
+
col_index += 1
|
89
|
+
|
90
|
+
# 下一级继续递归 或 保持自己以合并
|
91
|
+
next_header || header
|
92
|
+
end
|
93
|
+
|
94
|
+
result.push(next_line)
|
95
|
+
end
|
96
|
+
result
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'tallty_import_export/attr/export_header'
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module TalltyImportExport
|
2
2
|
class Export
|
3
|
-
attr_reader :klass
|
3
|
+
attr_reader :klass, :context
|
4
4
|
|
5
5
|
def initialize klass
|
6
6
|
@klass = klass
|
7
|
+
@context = Context.new({})
|
7
8
|
end
|
8
9
|
|
9
10
|
# [
|
@@ -16,6 +17,7 @@ module TalltyImportExport
|
|
16
17
|
# { key: 'user_department_name', name: '考核人部门' },
|
17
18
|
# { key: 'state', name: '考核状态', method: :state_zh },
|
18
19
|
# { key: 'score', name: '考核分', source: true },
|
20
|
+
# { key: 'model_key', name: '关键字', list: [ { key: 'model_key', name: '新属性' } ]},
|
19
21
|
# ]
|
20
22
|
# export_headers_result / headers
|
21
23
|
# key: 属性的英文名,可以支持user.name这样的方式
|
@@ -30,6 +32,7 @@ module TalltyImportExport
|
|
30
32
|
# select: [{ label: '已报备', value: 'submitted'}, ...],需要转换的枚举类型
|
31
33
|
# source: true,如果source为true,代表从association_record 进行属性查询
|
32
34
|
# proc: proc或者lamda,支持call,传入 record 和 context
|
35
|
+
# list: 对于list布局的,进行嵌套,生成子表格
|
33
36
|
|
34
37
|
def export_xlsx records, **options
|
35
38
|
records = with_scope records
|
@@ -38,6 +41,7 @@ module TalltyImportExport
|
|
38
41
|
options = options.with_indifferent_access
|
39
42
|
|
40
43
|
Axlsx::Package.new do |pack|
|
44
|
+
pack.use_shared_strings = true
|
41
45
|
workbook = pack.workbook
|
42
46
|
|
43
47
|
if @group_by.present?
|
@@ -45,20 +49,21 @@ module TalltyImportExport
|
|
45
49
|
records.group_by { |record| record.send(@group_by)}.each do |key, group_records|
|
46
50
|
next unless key.present?
|
47
51
|
@group_key = key
|
48
|
-
export_workbook workbook, group_records
|
52
|
+
export_workbook workbook, group_records, **options
|
49
53
|
end
|
50
54
|
else
|
51
55
|
records.group(@group_by).count.keys.each do |key|
|
52
56
|
next unless key.present?
|
53
57
|
@group_key = key
|
54
|
-
export_workbook workbook, records.ransack("#{@group_where}" => key).result
|
58
|
+
export_workbook workbook, records.ransack("#{@group_where}" => key).result, **options
|
55
59
|
end
|
56
60
|
end
|
57
61
|
else
|
58
|
-
export_workbook workbook, records
|
62
|
+
export_workbook workbook, records, **options
|
59
63
|
end
|
60
64
|
|
61
65
|
file_path = File.join(Rails.root, 'public', 'export')
|
66
|
+
# file_path = File.join('/Users/mushu/', 'export')
|
62
67
|
FileUtils.mkdir_p(file_path) unless Dir.exist?(file_path)
|
63
68
|
file_name = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{@filename}.xlsx"
|
64
69
|
file_path_with_name = File.join(file_path, file_name)
|
@@ -67,14 +72,14 @@ module TalltyImportExport
|
|
67
72
|
end
|
68
73
|
end
|
69
74
|
|
70
|
-
def export_workbook workbook, association_records
|
75
|
+
def export_workbook workbook, association_records, **options
|
71
76
|
# excel导出样式
|
72
77
|
alignment = { vertical: :center, horizontal: :center }
|
73
78
|
border = { color: '969696', style: :thin }
|
74
79
|
title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 12, b: true)
|
75
80
|
title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 12, fg_color: "fffffb")
|
76
81
|
title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 10)
|
77
|
-
headers = export_headers_result
|
82
|
+
headers = export_headers_result **options
|
78
83
|
|
79
84
|
_sheet_name = respond_to?(:sheet_name) ? self.sheet_name : nil
|
80
85
|
|
@@ -93,14 +98,15 @@ module TalltyImportExport
|
|
93
98
|
|
94
99
|
index = 0
|
95
100
|
association_records.each do |association_record|
|
96
|
-
row = []
|
97
101
|
records = @each_method.present? ?
|
98
102
|
(try_method(association_record, @each_method) || [nil]) :
|
99
103
|
[association_record]
|
100
104
|
|
101
105
|
records.each do |record|
|
106
|
+
row = []
|
107
|
+
formats = []
|
108
|
+
index += 1
|
102
109
|
headers.each_with_index do |header, col_index|
|
103
|
-
index += 1
|
104
110
|
_data = header[:source] ?
|
105
111
|
handle_data(association_record, header, index) :
|
106
112
|
handle_data(record, header, index)
|
@@ -116,8 +122,9 @@ module TalltyImportExport
|
|
116
122
|
end
|
117
123
|
end
|
118
124
|
row.push(_data)
|
125
|
+
formats.push(header[:format]&.to_sym || (_data.is_a?(String) ? :string : nil))
|
119
126
|
end
|
120
|
-
sheet.add_row row, style: title3, height: @row_height, types:
|
127
|
+
sheet.add_row row, style: title3, height: @row_height, types: formats
|
121
128
|
last_row = row
|
122
129
|
end
|
123
130
|
end
|
@@ -150,14 +157,26 @@ module TalltyImportExport
|
|
150
157
|
@group_where = "#{@group_by}_eq" if @group_by.present?
|
151
158
|
@headers ||= options.delete(:headers)
|
152
159
|
@each_method ||= options.delete(:each_method)
|
160
|
+
@params = options
|
161
|
+
context.params = @params
|
153
162
|
end
|
154
163
|
|
155
164
|
def with_scope records
|
156
165
|
records
|
157
166
|
end
|
158
167
|
|
159
|
-
def export_headers_result
|
160
|
-
@headers
|
168
|
+
def export_headers_result **options
|
169
|
+
if @headers.present? && @group_key.blank?
|
170
|
+
headers_hash = @headers.to_h { |header| [header.with_indifferent_access[:key], header] }.with_indifferent_access
|
171
|
+
export_headers(**options.symbolize_keys).select do |_header|
|
172
|
+
_header.with_indifferent_access[:key].to_s.in?(headers_hash.keys)
|
173
|
+
end.map do |_header|
|
174
|
+
_header = _header.with_indifferent_access
|
175
|
+
_header.merge(headers_hash[_header[:key]].delete_if { |k, v| v.blank? })
|
176
|
+
end
|
177
|
+
else
|
178
|
+
@headers = export_headers(**options.symbolize_keys)
|
179
|
+
end
|
161
180
|
end
|
162
181
|
|
163
182
|
def export_headers **args
|
@@ -165,38 +184,44 @@ module TalltyImportExport
|
|
165
184
|
end
|
166
185
|
|
167
186
|
# 处理一个记录的数据
|
168
|
-
def handle_data record, header, index=0
|
187
|
+
def handle_data record, header, index=0, **opts
|
169
188
|
data =
|
170
189
|
if header[:key] == '_index'
|
171
|
-
index
|
190
|
+
index
|
172
191
|
elsif header[:method].present?
|
173
192
|
send(header[:method], record, header)
|
174
193
|
elsif header[:chain].present?
|
175
194
|
try_chain(record, header[:chain])
|
176
|
-
elsif header[:json]
|
177
|
-
record.send(header[:json])[header[:key]]
|
178
195
|
elsif header[:proc] && header[:proc].respond_to?(:call)
|
179
|
-
header[:proc].call(record)
|
196
|
+
header[:proc].call(record, context)
|
180
197
|
else
|
181
|
-
try_method(record, header[:key])
|
198
|
+
try_method(record, header[:key], prefix: [header[:prefix], header[:json]].compact.join('.'))
|
182
199
|
end
|
183
|
-
data = handle_format(data, header)
|
184
|
-
data =
|
200
|
+
data = handle_format(data, header, **opts)
|
201
|
+
data = handle_data_type(data, **opts)
|
202
|
+
data = handle_select(data, header, **opts)
|
185
203
|
rescue
|
186
204
|
''
|
187
205
|
end
|
188
206
|
|
189
207
|
def try_chain record, arr
|
190
|
-
arr.reduce(record)
|
208
|
+
arr.reduce(record) do |r, m|
|
209
|
+
if r.is_a?(Array)
|
210
|
+
r.try(m) || r.try(:[], m.to_i)
|
211
|
+
else
|
212
|
+
r.try(:[], m) || r.try(:[], m.to_sym) || r.try(m)
|
213
|
+
end
|
214
|
+
end
|
191
215
|
end
|
192
216
|
|
193
|
-
def try_method record, method
|
217
|
+
def try_method record, method, prefix: nil
|
218
|
+
prefix_arr = prefix.to_s.split(/\./)
|
194
219
|
arr = method.to_s.split(/\./)
|
195
|
-
try_chain record, arr
|
220
|
+
try_chain record, prefix_arr + arr
|
196
221
|
end
|
197
222
|
|
198
223
|
# 根据数据类型 attr_type 进行数据的格式化
|
199
|
-
def handle_format data, header
|
224
|
+
def handle_format data, header, **opts
|
200
225
|
case header[:attr_type].to_s
|
201
226
|
when 'string'
|
202
227
|
data.to_s
|
@@ -209,7 +234,30 @@ module TalltyImportExport
|
|
209
234
|
end
|
210
235
|
end
|
211
236
|
|
212
|
-
def
|
237
|
+
def handle_data_type data, **opts
|
238
|
+
if data.is_a?(Time)
|
239
|
+
data.in_time_zone.strftime('%F %H:%M')
|
240
|
+
elsif data.is_a?(Date)
|
241
|
+
data.in_time_zone.strftime('%F')
|
242
|
+
elsif data.is_a?(TrueClass)
|
243
|
+
'是'
|
244
|
+
elsif data.is_a?(FalseClass)
|
245
|
+
'否'
|
246
|
+
elsif data.is_a?(Array) && !opts[:keep_array]
|
247
|
+
if data.first.is_a?(Hash) && data.first&.dig('url').present?
|
248
|
+
# 文件array
|
249
|
+
data.map do |item|
|
250
|
+
item.dig('url')
|
251
|
+
end.compact.join("\n")
|
252
|
+
else
|
253
|
+
data.map(&:to_s).compact.join("\n")
|
254
|
+
end
|
255
|
+
else
|
256
|
+
data
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def handle_select data, header, **opts
|
213
261
|
if header[:select].present?
|
214
262
|
select_option = header[:select].find { |option| option[:value].to_s == data.to_s }
|
215
263
|
select_option.present? ? select_option[:label] : data
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module TalltyImportExport
|
2
|
+
class ExportForm < TalltyImportExport::Export
|
3
|
+
def initialize *attrs
|
4
|
+
super(*attrs)
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def export_template_xlsx form
|
9
|
+
export_xlsx([], {
|
10
|
+
headers: form_transfer_to_headers(form),
|
11
|
+
header_only: true
|
12
|
+
})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def export_workbook workbook, association_records, **options
|
17
|
+
# excel导出样式
|
18
|
+
alignment = { vertical: :center, horizontal: :center }
|
19
|
+
border = { color: '969696', style: :thin }
|
20
|
+
title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 12, b: true)
|
21
|
+
title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 12, fg_color: "fffffb")
|
22
|
+
title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 10)
|
23
|
+
_sheet_name = (respond_to?(:sheet_name) ? self.sheet_name : nil) || options[:sheet_name]
|
24
|
+
|
25
|
+
header_obj = export_headers_result **options
|
26
|
+
|
27
|
+
workbook.add_worksheet(name: _sheet_name) do |sheet|
|
28
|
+
index = 0
|
29
|
+
|
30
|
+
if respond_to?(:first_header)
|
31
|
+
row_index = Axlsx.col_ref(headers.size - 1)
|
32
|
+
sheet.merge_cells("A1:#{row_index}1")
|
33
|
+
sheet.add_row [first_header], style: title1, height: 30
|
34
|
+
index += 1
|
35
|
+
end
|
36
|
+
|
37
|
+
header_obj.header_lines.each do |header_line|
|
38
|
+
sheet.add_row(header_line.map(&:name), style: title2, height: 25)
|
39
|
+
index += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# 合并相同 header
|
43
|
+
header_obj.header_seq_to_axios.values.each do |axios_ary|
|
44
|
+
if axios_ary.count > 1
|
45
|
+
top_right = [axios_ary.map(&:first).min, axios_ary.map(&:last).min]
|
46
|
+
bottom_left = [axios_ary.map(&:first).max, axios_ary.map(&:last).max]
|
47
|
+
sheet.merge_cells(
|
48
|
+
Axlsx::cell_r(top_right.first, top_right.last) + ':' + Axlsx::cell_r(bottom_left.first, bottom_left.last)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
return if options[:header_only]
|
54
|
+
|
55
|
+
value_seq_to_axios = {}
|
56
|
+
|
57
|
+
formats = []
|
58
|
+
association_records.each do |association_record|
|
59
|
+
records = @each_method.present? ?
|
60
|
+
(try_method(association_record, @each_method) || [nil]) :
|
61
|
+
[association_record]
|
62
|
+
|
63
|
+
records.each do |record|
|
64
|
+
value_living_alone_col_index_to_value_count = {}
|
65
|
+
|
66
|
+
payload = TalltyImportExport::ExportPayload.new(record, header: header_obj) do |payload, header, **opts|
|
67
|
+
_data = header[:source] ?
|
68
|
+
handle_data(association_record, header, index, **opts) :
|
69
|
+
handle_data(payload, header, index, **opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# p '----------------------------------------------------------------'
|
74
|
+
# payload.lines.each { |x| p x.map { |x| x.try(:value) || '________' } }
|
75
|
+
|
76
|
+
payload.lines.each_with_index do |line, row_index|
|
77
|
+
row = []
|
78
|
+
line.each_with_index do |value, col_index|
|
79
|
+
value_living_alone_col_index_to_value_count[col_index] ||= 0
|
80
|
+
|
81
|
+
if (TalltyImportExport::ExportPayload::Value === value)
|
82
|
+
row << value.value
|
83
|
+
value_living_alone_col_index_to_value_count[col_index] += 1
|
84
|
+
unless value_seq_to_axios[value.seq]
|
85
|
+
value_seq_to_axios[value.seq] = []
|
86
|
+
end
|
87
|
+
value_seq_to_axios[value.seq] << [col_index , row_index + index]
|
88
|
+
else
|
89
|
+
row << nil
|
90
|
+
end
|
91
|
+
formats.push(
|
92
|
+
header_obj.flatten_value[col_index].format&.to_sym || (row.last.is_a?(String) ? :string : nil)
|
93
|
+
)
|
94
|
+
end
|
95
|
+
sheet.add_row(row, style: title3, height: @row_height, types: formats)
|
96
|
+
formats = []
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
# 合并仅有一个值的一列中所有格子
|
101
|
+
value_living_alone_col_index_to_value_count.each_pair do |col_index, count|
|
102
|
+
if count == 1
|
103
|
+
sheet.merge_cells(
|
104
|
+
Axlsx::cell_r(col_index, index) + ':' + Axlsx::cell_r(col_index, index + payload.max_matrix_height - 1)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
index += payload.max_matrix_height
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# 合并相同值
|
114
|
+
value_seq_to_axios.values.each do |axios_ary|
|
115
|
+
if axios_ary.count > 1
|
116
|
+
top_right = [axios_ary.map(&:first).min, axios_ary.map(&:last).min]
|
117
|
+
bottom_left = [axios_ary.map(&:first).max, axios_ary.map(&:last).max]
|
118
|
+
sheet.merge_cells(
|
119
|
+
Axlsx::cell_r(top_right.first, top_right.last) + ':' + Axlsx::cell_r(bottom_left.first, bottom_left.last)
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def export_headers_result **options
|
128
|
+
@headers = options.symbolize_keys[:headers]
|
129
|
+
super(**options)
|
130
|
+
@headers = TalltyImportExport::Attr::ExportHeader.new({ items: @headers })
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class TalltyImportExport::ExportPayload::Cell
|
2
|
+
attr_accessor :cell_cluster, :value, :header, :parent_path
|
3
|
+
|
4
|
+
# cell_cluster 是表格纵向 集合
|
5
|
+
def initialize header, value, context, parent_path=[], &value_handler
|
6
|
+
@value = value
|
7
|
+
@header = header
|
8
|
+
@value_handler = value_handler
|
9
|
+
|
10
|
+
# context
|
11
|
+
@context = context
|
12
|
+
|
13
|
+
value.seq_chain.each do |s|
|
14
|
+
# s = seq
|
15
|
+
@context[:header_seq_to_value_seq_to_value][header.seq] ||= {}
|
16
|
+
@context[:header_seq_to_value_seq_to_value][header.seq][s] = value # NOTE: value 对象
|
17
|
+
end
|
18
|
+
|
19
|
+
# parent
|
20
|
+
@parent_path = parent_path
|
21
|
+
|
22
|
+
@context[:parent_cell_seq_to_children_cell_value_array][parent_cell_seq] ||= []
|
23
|
+
@context[:parent_cell_seq_to_children_cell_value_array][parent_cell_seq].push(@value)
|
24
|
+
|
25
|
+
@cell_cluster = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def seq
|
29
|
+
@seq ||= SecureRandom.uuid
|
30
|
+
end
|
31
|
+
|
32
|
+
def parent_cell_seq
|
33
|
+
@parent_path.last&.seq
|
34
|
+
end
|
35
|
+
|
36
|
+
def depth
|
37
|
+
@parent_path.count
|
38
|
+
end
|
39
|
+
|
40
|
+
def header_chain_seqs
|
41
|
+
[*@parent_path, self].map { |cell| cell.header.cluster_seqs }.reduce(:concat)
|
42
|
+
end
|
43
|
+
|
44
|
+
def divise
|
45
|
+
if @header.children.present? && @value.value
|
46
|
+
new_cells = @header.children.map do |child_header|
|
47
|
+
val = @value_handler.call(@value.value, child_header.as_json.symbolize_keys, keep_array: true)
|
48
|
+
if (child_header.children.present? && val.is_a?(Array))
|
49
|
+
val.map do |val|
|
50
|
+
cell = TalltyImportExport::ExportPayload::Cell.new(
|
51
|
+
child_header,
|
52
|
+
TalltyImportExport::ExportPayload::Value.new(val, @value.chain, inherited: true),
|
53
|
+
@context,
|
54
|
+
[*self.parent_path, self],
|
55
|
+
&@value_handler
|
56
|
+
)
|
57
|
+
cell
|
58
|
+
end
|
59
|
+
else
|
60
|
+
val = @value_handler.call(@value.value, child_header.as_json.symbolize_keys)
|
61
|
+
TalltyImportExport::ExportPayload::Cell.new(
|
62
|
+
child_header,
|
63
|
+
TalltyImportExport::ExportPayload::Value.new(val, @value.chain),
|
64
|
+
@context,
|
65
|
+
[*self.parent_path, self],
|
66
|
+
&@value_handler
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end.flatten
|
70
|
+
new_cells.each(&:divise)
|
71
|
+
@cell_cluster.concat(new_cells.map(&:cell_cluster))
|
72
|
+
else
|
73
|
+
@cell_cluster = [self]
|
74
|
+
end
|
75
|
+
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def grow_to_line
|
80
|
+
@context[:flatten_headers].
|
81
|
+
select do |header|
|
82
|
+
header_chain_seqs.include?(header.seq)
|
83
|
+
end.
|
84
|
+
map do |header|
|
85
|
+
target_value = nil
|
86
|
+
|
87
|
+
# 兄弟细胞上先查找,以解决同级兄弟细胞父一样,而导致的值覆盖
|
88
|
+
brother_cell_value_seqs = (@context[:parent_cell_seq_to_children_cell_value_array][parent_cell_seq] || []).map(&:seq)
|
89
|
+
|
90
|
+
[*brother_cell_value_seqs, *@value.seq_chain.uniq.reverse].each do |seq|
|
91
|
+
next if target_value
|
92
|
+
# seq = @value.seq
|
93
|
+
if @context[:header_seq_to_value_seq_to_value][header.seq]&.[](seq)
|
94
|
+
target_value = @context[:header_seq_to_value_seq_to_value][header.seq][seq]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
target_value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# 一个 最大的竖列,分子列前的竖列
|
2
|
+
class TalltyImportExport::ExportPayload::CellColumn
|
3
|
+
attr_reader :header, :payload, :context, :cell_clusters, :flatten_cells, :tallest_cell_cluster
|
4
|
+
|
5
|
+
def initialize header, value, context, &value_handler
|
6
|
+
@header = header
|
7
|
+
@value = value
|
8
|
+
@context = context
|
9
|
+
@cell_clusters = []
|
10
|
+
@value_handler = value_handler
|
11
|
+
|
12
|
+
val = @value_handler.call(@value.value, @header.as_json.symbolize_keys, keep_array: true)
|
13
|
+
if header.children && val.is_a?(Array)
|
14
|
+
@cell_clusters = val.map do |val|
|
15
|
+
TalltyImportExport::ExportPayload::Cell.new(
|
16
|
+
@header,
|
17
|
+
TalltyImportExport::ExportPayload::Value.new(val, @value.chain),
|
18
|
+
@context,
|
19
|
+
&@value_handler
|
20
|
+
)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
@cell_clusters = [
|
24
|
+
TalltyImportExport::ExportPayload::Cell.new(
|
25
|
+
@header,
|
26
|
+
TalltyImportExport::ExportPayload::Value.new(
|
27
|
+
@value_handler.call(@value.value, @header.as_json.symbolize_keys),
|
28
|
+
@value.chain,
|
29
|
+
),
|
30
|
+
@context,
|
31
|
+
&@value_handler
|
32
|
+
)
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
@flatten_cells = []
|
37
|
+
|
38
|
+
cell_divise
|
39
|
+
end
|
40
|
+
|
41
|
+
def cell_divise
|
42
|
+
divised_cell_clusters = @cell_clusters.map do |cell_cluster|
|
43
|
+
cell_cluster.divise.cell_cluster
|
44
|
+
end
|
45
|
+
|
46
|
+
@flatten_cells = divised_cell_clusters.flatten
|
47
|
+
|
48
|
+
depth_to_cells_mapping = @flatten_cells.group_by { |cell| [cell.header.seq] }
|
49
|
+
|
50
|
+
@tallest_cell_cluster = depth_to_cells_mapping.values.max { |a, b| a.count <=> b.count } || []
|
51
|
+
|
52
|
+
self
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class TalltyImportExport::ExportPayload::Value
|
2
|
+
attr_reader :value, :chain, :seq_chain
|
3
|
+
|
4
|
+
def initialize(val, chain=[], inherited: false)
|
5
|
+
@value = val
|
6
|
+
@parent_chain = chain
|
7
|
+
|
8
|
+
@chain = inherited ? @parent_chain : [*@parent_chain, self]
|
9
|
+
@seq_chain = @chain.map(&:seq)
|
10
|
+
end
|
11
|
+
|
12
|
+
def seq
|
13
|
+
@seq ||= SecureRandom.uuid
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
class TalltyImportExport::ExportPayload
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
autoload :Value
|
7
|
+
autoload :CellColumn
|
8
|
+
autoload :Cell
|
9
|
+
|
10
|
+
attr_reader :value, :context, :cell_columns, :max_matrix_height
|
11
|
+
|
12
|
+
def initialize(val, header:, &value_handler)
|
13
|
+
@value = TalltyImportExport::ExportPayload::Value.new(val)
|
14
|
+
@headers = header.items # TalltyImportExport::Attr::ExportHeaderItem array
|
15
|
+
@flatten_headers = header.flatten_value
|
16
|
+
@value_handler = value_handler
|
17
|
+
@context = {
|
18
|
+
header_seq_to_value_seq_to_value: {},
|
19
|
+
parent_cell_seq_to_children_cell_value_array: {},
|
20
|
+
flatten_headers: @flatten_headers,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def lines
|
25
|
+
@cell_columns = @headers.map do |header|
|
26
|
+
TalltyImportExport::ExportPayload::CellColumn.new(
|
27
|
+
header,
|
28
|
+
self.value,
|
29
|
+
@context,
|
30
|
+
&@value_handler
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# 分离的,每个初始 header 为一个矩阵
|
35
|
+
matrixes = @cell_columns.map do |cell_column|
|
36
|
+
cell_column.tallest_cell_cluster.map(&:grow_to_line)
|
37
|
+
end
|
38
|
+
|
39
|
+
# 计算所有矩阵中最高高度,用于填充
|
40
|
+
@max_matrix_height = matrixes.map(&:count).max
|
41
|
+
|
42
|
+
# 填充各个矩阵至等高
|
43
|
+
detached_matrixes = matrixes.map do |ragged_col|
|
44
|
+
col = ragged_col.uniq
|
45
|
+
while col.count < @max_matrix_height
|
46
|
+
col.push((col[0].try(:count) || 1).times.to_a)
|
47
|
+
end
|
48
|
+
Matrix[*col]
|
49
|
+
end
|
50
|
+
|
51
|
+
# 合并矩阵
|
52
|
+
result = detached_matrixes.reduce do |out, matrix|
|
53
|
+
Matrix.hstack(out, matrix)
|
54
|
+
end.to_a
|
55
|
+
# .select do |line|
|
56
|
+
# line.select { |value| TalltyImportExport::ExportPayload::Value === value }.count > 0
|
57
|
+
# end
|
58
|
+
|
59
|
+
# result.map { |x| p x.map { |xx| xx.try(:value)}}
|
60
|
+
result
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module TalltyImportExport
|
2
|
+
module ExportableForm
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Common
|
5
|
+
|
6
|
+
included do |base|
|
7
|
+
# base.include(Common)
|
8
|
+
base.const_set('Export', Class.new(TalltyImportExport::ExportForm))
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def export_instance
|
13
|
+
const_get('Export').new(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def export_xlsx records, **options
|
17
|
+
export_instance.export_xlsx(records, **options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -21,13 +21,35 @@ module TalltyImportExport
|
|
21
21
|
|
22
22
|
# xlsx_file 为 file path or file object or TalltyImportExport::Excel.new
|
23
23
|
def import_xlsx xlsx_file, associations, **options
|
24
|
+
process_xlsx_line_info(xlsx_file, associations, **options) do |line_info, associations|
|
25
|
+
process_line_info(line_info, associations)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def exchange_to_ids xlsx_file, associations, **options
|
30
|
+
errors = []
|
31
|
+
ids = []
|
32
|
+
process_xlsx_line_info(xlsx_file, associations, **options) do |line_info, associations|
|
33
|
+
ids << exchange_line_info_to_id(line_info, associations, errors)
|
34
|
+
end
|
35
|
+
|
36
|
+
if errors.any?
|
37
|
+
error_msg = errors.map do |line_info|
|
38
|
+
"【#{@primary_keys.map { |key| line_info[key] }.join(' - ')}】"
|
39
|
+
end.join(' ')
|
40
|
+
raise RecordNotFountError.new("以下内容未找到: #{error_msg}")
|
41
|
+
end
|
42
|
+
return ids.compact.uniq
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_xlsx_line_info xlsx_file, associations, **options
|
24
46
|
@associations = associations
|
25
47
|
# 先处理获取出来Excel每行的数据, line_info
|
26
48
|
process_options(options)
|
27
49
|
|
28
50
|
if TalltyImportExport::Excel === xlsx_file
|
29
51
|
xlsx_file.rows.each_with_excel_hash(@excel_hash) do |line_info|
|
30
|
-
|
52
|
+
yield line_info.with_indifferent_access, associations
|
31
53
|
end
|
32
54
|
else
|
33
55
|
file_path = xlsx_file.is_a?(String) ? xlsx_file : xlsx_file.path
|
@@ -35,7 +57,7 @@ module TalltyImportExport
|
|
35
57
|
xlsx.each_with_pagename do |_sheetname, sheet|
|
36
58
|
sheet.each(**@excel_hash).with_index do |line_info, index|
|
37
59
|
next if index == 0
|
38
|
-
|
60
|
+
yield line_info.with_indifferent_access, associations
|
39
61
|
end
|
40
62
|
end
|
41
63
|
end
|
@@ -44,7 +66,7 @@ module TalltyImportExport
|
|
44
66
|
def import_data data, associations, **options
|
45
67
|
process_options(options)
|
46
68
|
TalltyImportExport::Excel::Rows.new(data).each_with_excel_hash(@excel_hash) do |line_info|
|
47
|
-
process_line_info(line_info, associations)
|
69
|
+
process_line_info(line_info.with_indifferent_access, associations)
|
48
70
|
end
|
49
71
|
end
|
50
72
|
|
@@ -83,13 +105,13 @@ module TalltyImportExport
|
|
83
105
|
end
|
84
106
|
|
85
107
|
def convert_data line_info
|
86
|
-
line_info.with_indifferent_access
|
87
|
-
|
88
|
-
|
89
|
-
|
108
|
+
info = line_info.with_indifferent_access
|
109
|
+
import_headers_result.reduce({}) do |h, header|
|
110
|
+
k = header[:key]
|
111
|
+
v = info[k]
|
90
112
|
# header[:convert] = handle_xxx
|
91
113
|
# handle_xxx(val, processing_line_info, raw_line_info)
|
92
|
-
val = header[:convert] ? send(header[:convert], v, h,
|
114
|
+
val = header[:convert] ? send(header[:convert], v, h, info) : v
|
93
115
|
if header[:json]
|
94
116
|
h[header[:json]] ||= {}
|
95
117
|
h[header[:json]][k] = val
|
@@ -113,13 +135,16 @@ module TalltyImportExport
|
|
113
135
|
# TODO: 这里,对于import_headers,后面还是不要传参数了
|
114
136
|
# 需要合并proc,前端没有办法把proc传过来
|
115
137
|
def import_headers_result
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
138
|
+
if @headers.present?
|
139
|
+
headers_hash = @headers.to_h { |header| [header.with_indifferent_access[:key], header] }.with_indifferent_access
|
140
|
+
import_headers(**context.params.symbolize_keys).select do |_header|
|
141
|
+
_header.with_indifferent_access[:key].to_s.in?(headers_hash.keys)
|
142
|
+
end.map do |_header|
|
143
|
+
_header = _header.with_indifferent_access
|
144
|
+
_header.merge(headers_hash[_header[:key]].delete_if { |k, v| v.blank? })
|
121
145
|
end
|
122
|
-
|
146
|
+
else
|
147
|
+
@headers = import_headers(**options.symbolize_keys)
|
123
148
|
end
|
124
149
|
rescue
|
125
150
|
@headers
|
@@ -170,5 +195,27 @@ module TalltyImportExport
|
|
170
195
|
associations.create!(line_info.clone.except!(*@skip_keys))
|
171
196
|
end
|
172
197
|
end
|
198
|
+
|
199
|
+
def exchange_line_info_to_id line_info, associations, errors
|
200
|
+
# 去除空行内容
|
201
|
+
return unless line_info.values.any?(&:present?)
|
202
|
+
context.line_info = line_info
|
203
|
+
|
204
|
+
return unless primary_keys.present?
|
205
|
+
ids = associations.where(line_info.clone.extract!(*primary_keys)).pluck(:id)
|
206
|
+
|
207
|
+
context.last_line_info = line_info
|
208
|
+
|
209
|
+
errors << line_info unless ids[0]
|
210
|
+
return ids[0]
|
211
|
+
end
|
212
|
+
|
213
|
+
class RecordNotFountError < StandardError
|
214
|
+
attr_accessor :message
|
215
|
+
def initialize message
|
216
|
+
@message = message
|
217
|
+
super()
|
218
|
+
end
|
219
|
+
end
|
173
220
|
end
|
174
221
|
end
|
data/lib/tallty_import_export.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
|
+
require 'active_support'
|
1
2
|
require "active_support/core_ext/hash"
|
3
|
+
require "active_support/core_ext/object"
|
2
4
|
require "tallty_import_export/version"
|
3
5
|
require 'axlsx'
|
4
6
|
require 'redis'
|
5
|
-
require 'tallty_form'
|
7
|
+
# require 'tallty_form'
|
8
|
+
require 'attr_json'
|
6
9
|
|
7
10
|
module TalltyImportExport
|
8
11
|
extend ActiveSupport::Autoload
|
9
12
|
|
10
13
|
autoload :Common
|
11
14
|
autoload :Exportable
|
15
|
+
autoload :ExportableForm
|
12
16
|
autoload :Export
|
13
17
|
autoload :Importable
|
14
18
|
autoload :Import
|
15
19
|
autoload :Context
|
16
20
|
autoload :FormConvert
|
17
21
|
autoload :Excel
|
22
|
+
autoload :Attr
|
23
|
+
autoload :ExportPayload
|
24
|
+
autoload :ExportForm
|
18
25
|
|
19
26
|
class Error < StandardError; end
|
20
27
|
|
@@ -14,8 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
|
15
15
|
spec.platform = Gem::Platform::RUBY
|
16
16
|
|
17
|
-
# Specify which files should be added to the gem when it is released.
|
18
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
# Specify which files should be added to the gem when it is released. The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
18
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
19
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
20
|
end
|
@@ -31,4 +30,5 @@ Gem::Specification.new do |spec|
|
|
31
30
|
spec.add_dependency('activesupport')
|
32
31
|
spec.add_dependency('redis')
|
33
32
|
spec.add_dependency('redis-objects')
|
33
|
+
spec.add_dependency 'attr_json'
|
34
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tallty_import_export
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- liyijie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zip-zip
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: attr_json
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: " import & export for active_record with simple_controller "
|
126
140
|
email:
|
127
141
|
- liyijie825@gmail.com
|
@@ -140,11 +154,19 @@ files:
|
|
140
154
|
- bin/console
|
141
155
|
- bin/setup
|
142
156
|
- lib/tallty_import_export.rb
|
157
|
+
- lib/tallty_import_export/attr.rb
|
158
|
+
- lib/tallty_import_export/attr/export_header.rb
|
143
159
|
- lib/tallty_import_export/common.rb
|
144
160
|
- lib/tallty_import_export/context.rb
|
145
161
|
- lib/tallty_import_export/excel.rb
|
146
162
|
- lib/tallty_import_export/export.rb
|
163
|
+
- lib/tallty_import_export/export_form.rb
|
164
|
+
- lib/tallty_import_export/export_payload.rb
|
165
|
+
- lib/tallty_import_export/export_payload/cell.rb
|
166
|
+
- lib/tallty_import_export/export_payload/cell_column.rb
|
167
|
+
- lib/tallty_import_export/export_payload/value.rb
|
147
168
|
- lib/tallty_import_export/exportable.rb
|
169
|
+
- lib/tallty_import_export/exportable_form.rb
|
148
170
|
- lib/tallty_import_export/form_convert.rb
|
149
171
|
- lib/tallty_import_export/import.rb
|
150
172
|
- lib/tallty_import_export/importable.rb
|