fruit_to_lime 2.5.3 → 2.5.5

Sign up to get free protection for your applications and to get access to all the features.
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
+