fruit_to_lime 2.5.3 → 2.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/lib/fruit_to_lime.rb +17 -17
  2. data/lib/fruit_to_lime/csv_helper.rb +47 -47
  3. data/lib/fruit_to_lime/email_helper.rb +10 -10
  4. data/lib/fruit_to_lime/errors.rb +16 -16
  5. data/lib/fruit_to_lime/excel_helper.rb +10 -10
  6. data/lib/fruit_to_lime/global_phone.json +6571 -6571
  7. data/lib/fruit_to_lime/model/address.rb +60 -60
  8. data/lib/fruit_to_lime/model/class_settings.rb +50 -50
  9. data/lib/fruit_to_lime/model/coworker.rb +76 -76
  10. data/lib/fruit_to_lime/model/coworker_reference.rb +33 -33
  11. data/lib/fruit_to_lime/model/customfield.rb +87 -87
  12. data/lib/fruit_to_lime/model/deal.rb +141 -145
  13. data/lib/fruit_to_lime/model/deal_status.rb +12 -12
  14. data/lib/fruit_to_lime/model/note.rb +79 -79
  15. data/lib/fruit_to_lime/model/organization.rb +203 -197
  16. data/lib/fruit_to_lime/model/person.rb +151 -151
  17. data/lib/fruit_to_lime/model/referencetosource.rb +45 -45
  18. data/lib/fruit_to_lime/model/relation.rb +23 -23
  19. data/lib/fruit_to_lime/model/rootmodel.rb +338 -330
  20. data/lib/fruit_to_lime/model/settings.rb +60 -60
  21. data/lib/fruit_to_lime/model/tag.rb +35 -35
  22. data/lib/fruit_to_lime/model_helpers.rb +54 -54
  23. data/lib/fruit_to_lime/phone_helper.rb +74 -74
  24. data/lib/fruit_to_lime/roo_helper.rb +72 -72
  25. data/lib/fruit_to_lime/serialize_helper.rb +186 -186
  26. data/lib/fruit_to_lime/templating.rb +52 -52
  27. data/spec/address_spec.rb +48 -48
  28. data/spec/class_settings_spec.rb +37 -37
  29. data/spec/coworker_spec.rb +94 -94
  30. data/spec/custom_field_spec.rb +22 -22
  31. data/spec/deal_spec.rb +101 -102
  32. data/spec/helpers/csv_helper_spec.rb +29 -29
  33. data/spec/helpers/email_helper_spec.rb +32 -32
  34. data/spec/helpers/phone_helper_spec.rb +97 -97
  35. data/spec/helpers/serialize_helper_spec.rb +249 -249
  36. data/spec/helpers/xsd_validate_spec.rb +58 -58
  37. data/spec/note_spec.rb +98 -67
  38. data/spec/organization_spec.rb +103 -89
  39. data/spec/person_spec.rb +134 -134
  40. data/spec/rootmodel_spec.rb +277 -230
  41. data/spec/templating_spec.rb +11 -11
  42. data/templates/csv/lib/tomodel.rb +230 -230
  43. data/templates/csv/spec/exporter_spec.rb +17 -17
  44. data/templates/csv/spec/sample_data/coworkers.csv +2 -2
  45. data/templates/csv/spec/sample_data/deals.csv +2 -2
  46. data/templates/csv/spec/sample_data/organizations.csv +2 -2
  47. data/templates/csv/spec/sample_data/persons.csv +2 -2
  48. data/templates/easy/Gemfile +5 -0
  49. data/templates/easy/Rakefile.rb +7 -0
  50. data/templates/easy/convert.rb +3 -0
  51. data/templates/easy/lib/tomodel.rb +330 -0
  52. data/templates/easy/spec/exporter_spec.rb +10 -0
  53. data/templates/easy/spec/sample_data/Company.txt +649 -0
  54. data/templates/easy/spec/spec_helper.rb +24 -0
  55. data/templates/excel/lib/tomodel.rb +207 -207
  56. data/templates/sqlserver/lib/tomodel.rb +79 -79
  57. metadata +10 -3
@@ -1,17 +1,17 @@
1
- require 'spec_helper'
2
- require 'tomodel'
3
-
4
- describe 'Exporter' do
5
- before(:all) do
6
- exporter = Exporter.new
7
- organizations_file = File.join(File.dirname(__FILE__), 'sample_data', 'organizations.csv')
8
- coworkers_file = File.join(File.dirname(__FILE__), 'sample_data', 'coworkers.csv')
9
- persons_file = File.join(File.dirname(__FILE__), 'sample_data', 'persons.csv')
10
- deals_file = File.join(File.dirname(__FILE__), 'sample_data', 'deals.csv')
11
- @model = exporter.to_model(coworkers_file, organizations_file, persons_file, deals_file)
12
- end
13
- it "will find something with a name" do
14
- organization = @model.organizations[0]
15
- organization.name.length.should > 0
16
- end
17
- end
1
+ require 'spec_helper'
2
+ require 'tomodel'
3
+
4
+ describe 'Exporter' do
5
+ before(:all) do
6
+ exporter = Exporter.new
7
+ organizations_file = File.join(File.dirname(__FILE__), 'sample_data', 'organizations.csv')
8
+ coworkers_file = File.join(File.dirname(__FILE__), 'sample_data', 'coworkers.csv')
9
+ persons_file = File.join(File.dirname(__FILE__), 'sample_data', 'persons.csv')
10
+ deals_file = File.join(File.dirname(__FILE__), 'sample_data', 'deals.csv')
11
+ @model = exporter.to_model(coworkers_file, organizations_file, persons_file, deals_file)
12
+ end
13
+ it "will find something with a name" do
14
+ organization = @model.organizations[0]
15
+ organization.name.length.should > 0
16
+ end
17
+ end
@@ -1,2 +1,2 @@
1
- id;first_name;last_name
2
- 666;Evil;Elvis
1
+ id;first_name;last_name
2
+ 666;Evil;Elvis
@@ -1,2 +1,2 @@
1
- id;name;value;responsible_id;customer_id;customer_contact_id
2
- 333;Feta affären;10000;666;6;123
1
+ id;name;value;responsible_id;customer_id;customer_contact_id
2
+ 333;Feta affären;10000;666;6;123
@@ -1,2 +1,2 @@
1
- id;name;responsible_id
2
- 6;Alfs Mjukvaruutveckling;666
1
+ id;name;responsible_id
2
+ 6;Alfs Mjukvaruutveckling;666
@@ -1,2 +1,2 @@
1
- id;employer_id;first_name;last_name
2
- 123;6;Rune;Rebellion
1
+ id;employer_id;first_name;last_name
2
+ 123;6;Rune;Rebellion
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'thor'
4
+ gem 'fruit_to_lime'
5
+ gem 'rspec'
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+ task :test => :spec
@@ -0,0 +1,3 @@
1
+ require "./lib/tomodel"
2
+
3
+ Cli.start(ARGV)
@@ -0,0 +1,330 @@
1
+ # encoding: UTF-8
2
+ require 'fruit_to_lime'
3
+
4
+ class Exporter
5
+ # Turns a user from the User.txt Easy Export file into
6
+ # a fruit_to_lime coworker-model that is used to generate xml
7
+ def to_coworker(row)
8
+ coworker = FruitToLime::Coworker.new
9
+ # integration_id is typically the userId in Easy
10
+ # Must be set to be able to import the same file more
11
+ # than once without creating duplicates
12
+ coworker.integration_id = row['userId']
13
+ coworker.parse_name_to_firstname_lastname_se(row['Name'])
14
+
15
+ return coworker
16
+ end
17
+
18
+ # turns a row from the Easy exported Company.txt file into
19
+ # a fruit_to_lime model that is used to generate xml
20
+ # Uses rootmodel to locate other related stuff such coworker
21
+ def to_organization(row, rootmodel, coworkers)
22
+ organization = FruitToLime::Organization.new
23
+ # integration_id is typically the company Id in Easy
24
+ # Must be set to be able to import the same file more
25
+ # than once without creating duplicates
26
+
27
+ # Easy standard fields
28
+ organization.integration_id = row['companyId']
29
+ organization.name = row['company name']
30
+ organization.central_phone_number = row['Telephone']
31
+
32
+ # Addresses consists of several parts in Go.
33
+ # Lots of other systems have the address all in one
34
+ # line, to be able to match when importing it is
35
+ # way better to split the addresses
36
+ organization.with_postal_address do |address|
37
+ address.street = row['street']
38
+ address.zip_code = row['zip']
39
+ address.city = row['city']
40
+ address.location = row['location']
41
+ end
42
+
43
+ # Easy superfields
44
+ organization.email = row['e-mail']
45
+ organization.web_site = row['website']
46
+
47
+ organization.with_visit_address do |addr|
48
+ addr.street = row['visit street']
49
+ addr.zip_code = row['visit zip']
50
+ addr.city = row['visit city']
51
+ end
52
+
53
+ # Set Bisnode Id if present
54
+ bisnode_id = row['Bisnode-id']
55
+
56
+ if bisnode_id && !bisnode_id.empty?
57
+ organization.with_source do |source|
58
+ source.par_se(bisnode_id)
59
+ end
60
+ end
61
+
62
+ # Only set other Bisnode fields if the Bisnode Id is empty
63
+ if bisnode_id.empty?
64
+ organization.organization_number = row['orgnr']
65
+ end
66
+
67
+ coworker_id = coworkers[row['userIndex - our reference']]
68
+ organization.responsible_coworker = rootmodel.find_coworker_by_integration_id(coworker_id)
69
+
70
+ # relation
71
+
72
+ # Tags are set and defined at the same place
73
+ # Setting a tag: Imported is useful for the user
74
+ organization.set_tag("Imported")
75
+
76
+ # Option fields are normally translated into tags
77
+ # The option field customer category for instance,
78
+ # has the options "A-customer", "B-customer", and "C-customer"
79
+ organization.set_tag(row['customer category'])
80
+
81
+ return organization
82
+ end
83
+
84
+ def to_person(row, rootmodel)
85
+ person = FruitToLime::Person.new
86
+
87
+ # Easy standard fields
88
+ person.integration_id = "#{row['referenceId']}-#{row['companyId']}"
89
+ person.first_name = row['First name']
90
+ person.last_name = row['Last name']
91
+
92
+ # Easy superfields
93
+ person.direct_phone_number = row['Direktnummer']
94
+ person.mobile_phone_number = row['Mobil']
95
+ person.email = row['e-mail']
96
+ person.position = row['position']
97
+
98
+ person.set_custom_value("intrests", row['intrests'])
99
+ person.set_custom_value("shoe_size", row['shoe size'])
100
+
101
+ # Tags
102
+ person.set_tag("Imported")
103
+
104
+ # Xmas card field is a checkbox in Easy
105
+ if row['Xmas card'] == "1"
106
+ person.set_tag("Xmas card")
107
+ end
108
+
109
+ # set employer connection
110
+ employer = rootmodel.find_organization_by_integration_id(row['companyId'])
111
+ if employer
112
+ employer.add_employee person
113
+ end
114
+ end
115
+
116
+ def to_deal(row, rootmodel, includes, coworkers)
117
+ deal = FruitToLime::Deal.new
118
+ # Easy standard fields
119
+ deal.integration_id = row['projectId']
120
+ deal.name = row['Name']
121
+ deal.description = row['Description']
122
+
123
+ # Easy superfields
124
+ deal.order_date = row[' order date']
125
+
126
+ coworker_id = coworkers[row['userIndex']]
127
+ deal.responsible_coworker = rootmodel.find_coworker_by_integration_id coworker_id
128
+
129
+ # should be integer
130
+ # make the currency used in Easy matches the one used in Go
131
+ deal.value = row['value']
132
+
133
+ # should be between 0 - 100
134
+ # remove everything that is not an intiger
135
+ deal.probability = row['probability'].gsub(/[^\d]/,"").to_i
136
+
137
+ deal.status = FruitToLime::DealStatus.new
138
+ deal.status.label = row['Status']
139
+
140
+
141
+ # tags
142
+ deal.set_tag("Imported")
143
+
144
+ # find stuff connected to deal
145
+ if includes
146
+ organization_id = includes[row['projectId']]
147
+ organization = rootmodel.find_organization_by_integration_id(organization_id)
148
+ if organization
149
+ deal.customer = organization
150
+ end
151
+ end
152
+
153
+ return deal
154
+ end
155
+
156
+ def to_organization_note(row, rootmodel, coworkers, people)
157
+ note = FruitToLime::Note.new()
158
+
159
+ organization = rootmodel.find_organization_by_integration_id(row['companyId'])
160
+
161
+ coworker_id = coworkers[row['userIndex']]
162
+ coworker = rootmodel.find_coworker_by_integration_id(coworker_id)
163
+
164
+ if organization && coworker
165
+ note.organization = organization
166
+ note.created_by = coworker
167
+ note.person = organization.find_employee_by_integration_id(people[row['personIndex']])
168
+ note.date = row['Date']
169
+ note.text = "#{row['Category']}: #{row['History']}"
170
+
171
+ rootmodel.add_note(note) unless note.text.empty?
172
+ end
173
+
174
+ return note
175
+ end
176
+
177
+ def to_deal_note(row, rootmodel, coworkers)
178
+ note = FruitToLime::Note.new()
179
+
180
+ deal = rootmodel.find_deal_by_integration_id(row['projectId'])
181
+
182
+ coworker_id = coworkers[row['userIndex']]
183
+ coworker = rootmodel.find_coworker_by_integration_id(coworker_id)
184
+
185
+ if deal && coworker
186
+ note.deal = deal
187
+ note.created_by = coworker
188
+ note.date = row['Date']
189
+ note.text = row['RawHistory']
190
+
191
+ rootmodel.add_note(note) unless note.text.empty?
192
+ end
193
+
194
+ return note
195
+ end
196
+
197
+ def configure(model)
198
+ # add custom field to your model here. Custom fields can be
199
+ # added to organization, deal and person. Valid types are
200
+ # :String and :Link. If no type is specified :String is used
201
+ # as default.
202
+ model.settings.with_person do |person|
203
+ person.set_custom_field( { :integration_id => 'intrests', :title => 'Intrests', :type => :String} )
204
+ person.set_custom_field( { :integration_id => 'shoe_size', :title => 'Shoe size'} )
205
+ end
206
+ end
207
+
208
+ def process_rows(file_name)
209
+ data = File.open(file_name, 'r').read.encode('UTF-8',"ISO-8859-1").strip().gsub('"', '')
210
+ data = '"' + data.gsub("\t", "\"\t\"") + '"'
211
+ data = data.gsub("\n", "\"\n\"")
212
+
213
+ rows = FruitToLime::CsvHelper::text_to_hashes(data, "\t", "\n", '"')
214
+ rows.each do |row|
215
+ yield row
216
+ end
217
+ end
218
+
219
+ def to_model(coworkers_filename, organization_filename, persons_filename, orgnotes_filename, includes_filename, deals_filename, dealnotes_filename)
220
+ # A rootmodel is used to represent all entitite/models
221
+ # that is exported
222
+ rootmodel = FruitToLime::RootModel.new
223
+ coworkers = Hash.new
224
+ includes = Hash.new
225
+ people = Hash.new
226
+
227
+ configure rootmodel
228
+
229
+ # coworkers
230
+ # start with these since they are referenced
231
+ # from everywhere....
232
+ if coworkers_filename && !coworkers_filename.empty?
233
+ process_rows coworkers_filename do |row|
234
+ coworkers[row['userIndex']] = row['userId']
235
+ rootmodel.add_coworker(to_coworker(row))
236
+
237
+ end
238
+ end
239
+
240
+ # organizations
241
+ if organization_filename && !organization_filename.empty?
242
+ process_rows organization_filename do |row|
243
+ rootmodel.add_organization(to_organization(row, rootmodel, coworkers))
244
+ end
245
+ end
246
+
247
+ # persons
248
+ # depends on organizations
249
+ if persons_filename && !persons_filename.empty?
250
+ process_rows persons_filename do |row|
251
+ people[row['personIndex']] = "#{row['referenceId']}-#{row['companyId']}"
252
+ # adds it self to the employer
253
+ to_person(row, rootmodel)
254
+ end
255
+ end
256
+
257
+ if orgnotes_filename && !orgnotes_filename.empty?
258
+ process_rows orgnotes_filename do |row|
259
+ # adds itself if applicable
260
+ to_organization_note(row, rootmodel, coworkers, people)
261
+ end
262
+ end
263
+
264
+ # deals
265
+ # deals can reference coworkers (responsible), organizations
266
+ # and persons (contact)
267
+ if includes_filename && !includes_filename.empty?
268
+ process_rows includes_filename do |row|
269
+ includes[row['projectId']] = row['companyId']
270
+ end
271
+ end
272
+
273
+ if deals_filename && !deals_filename.empty?
274
+ process_rows deals_filename do |row|
275
+ rootmodel.add_deal(to_deal(row, rootmodel, includes, coworkers))
276
+ end
277
+ end
278
+
279
+ if dealnotes_filename && !dealnotes_filename.empty?
280
+ process_rows dealnotes_filename do |row|
281
+ # adds itself if applicable
282
+ to_deal_note(row, rootmodel, coworkers)
283
+ end
284
+ end
285
+
286
+ return rootmodel
287
+ end
288
+
289
+ def save_xml(file)
290
+ File.open(file,'w') do |f|
291
+ f.write(FruitToLime::SerializeHelper::serialize(to_xml_model))
292
+ end
293
+ end
294
+ end
295
+
296
+ require "thor"
297
+ require "fileutils"
298
+ require 'pathname'
299
+
300
+ class Cli < Thor
301
+ desc "to_go", "Generates a Go XML file"
302
+ method_option :output, :desc => "Path to file where xml will be output", :default => "export.xml", :type => :string
303
+ method_option :coworkers, :desc => "Path to coworkers csv file", :type => :string
304
+ method_option :organizations, :desc => "Path to organization csv file", :type => :string
305
+ method_option :persons, :desc => "Path to persons csv file", :type => :string
306
+ method_option :orgnotes, :desc => "Path to organization notes file", :type => :string
307
+ method_option :includes, :desc => "Path to include file", :type => :string
308
+ method_option :deals, :desc => "Path to deals csv file", :type => :string
309
+ method_option :dealnotes, :desc => "Path to deal notes file", :type => :string
310
+ def to_go
311
+ output = options.output
312
+ exporter = Exporter.new()
313
+ model = exporter.to_model(options.coworkers, options.organizations, options.persons, options.orgnotes, options.includes, options.deals, options.dealnotes)
314
+ error = model.sanity_check
315
+ if error.empty?
316
+ validation_errors = model.validate
317
+
318
+ if validation_errors.empty?
319
+ model.serialize_to_file(output)
320
+ puts "Generated Go XML file: '#{output}'."
321
+ else
322
+ puts "Could not generate file due to"
323
+ puts validation_errors
324
+ end
325
+ else
326
+ puts "Could not generate file due to"
327
+ puts error
328
+ end
329
+ end
330
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'tomodel'
3
+
4
+ describe 'Exporter' do
5
+ before(:all) do
6
+ exporter = Exporter.new
7
+ organizations_file = File.join(File.dirname(__FILE__), 'sample_data', 'company.txt')
8
+ @model = exporter.to_model(nil, organizations_file, nil, nil, nil, nil, nil)
9
+ end
10
+ end
@@ -0,0 +1,649 @@
1
+ idCompany PowerSellCompanyID Company name Suffix Address1 Address2 Address3 Address4 street zip city location Telephone Fax visit street visit zip visit city Typ Region, enhet Kategori idString-Kategori Kundprio idString-Kundprio Bisnode-id idUser- our reference Bransch idString-Bransch Branch PAR Ursprung idString-Ursprung Organisationsnummer Antal anst�llda Juridiskt namn Juridisk form Oms�ttning Oms�ttning-kod Oms�ttning start Oms�ttning slut idUser-Created Created date time idUser-Updated Updated date time
2
+ 1 1402 Lundalogik AB
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+
304
+
305
+
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+
441
+
442
+
443
+
444
+
445
+
446
+
447
+
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+
478
+
479
+
480
+
481
+
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+
500
+
501
+
502
+
503
+
504
+
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+
542
+
543
+
544
+
545
+
546
+
547
+
548
+
549
+
550
+
551
+
552
+
553
+
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+
562
+
563
+
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
616
+
617
+
618
+
619
+
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+
631
+
632
+
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+