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.
- data/lib/fruit_to_lime.rb +17 -17
- data/lib/fruit_to_lime/csv_helper.rb +47 -47
- data/lib/fruit_to_lime/email_helper.rb +10 -10
- data/lib/fruit_to_lime/errors.rb +16 -16
- data/lib/fruit_to_lime/excel_helper.rb +10 -10
- data/lib/fruit_to_lime/global_phone.json +6571 -6571
- data/lib/fruit_to_lime/model/address.rb +60 -60
- data/lib/fruit_to_lime/model/class_settings.rb +50 -50
- data/lib/fruit_to_lime/model/coworker.rb +76 -76
- data/lib/fruit_to_lime/model/coworker_reference.rb +33 -33
- data/lib/fruit_to_lime/model/customfield.rb +87 -87
- data/lib/fruit_to_lime/model/deal.rb +141 -145
- data/lib/fruit_to_lime/model/deal_status.rb +12 -12
- data/lib/fruit_to_lime/model/note.rb +79 -79
- data/lib/fruit_to_lime/model/organization.rb +203 -197
- data/lib/fruit_to_lime/model/person.rb +151 -151
- data/lib/fruit_to_lime/model/referencetosource.rb +45 -45
- data/lib/fruit_to_lime/model/relation.rb +23 -23
- data/lib/fruit_to_lime/model/rootmodel.rb +338 -330
- data/lib/fruit_to_lime/model/settings.rb +60 -60
- data/lib/fruit_to_lime/model/tag.rb +35 -35
- data/lib/fruit_to_lime/model_helpers.rb +54 -54
- data/lib/fruit_to_lime/phone_helper.rb +74 -74
- data/lib/fruit_to_lime/roo_helper.rb +72 -72
- data/lib/fruit_to_lime/serialize_helper.rb +186 -186
- data/lib/fruit_to_lime/templating.rb +52 -52
- data/spec/address_spec.rb +48 -48
- data/spec/class_settings_spec.rb +37 -37
- data/spec/coworker_spec.rb +94 -94
- data/spec/custom_field_spec.rb +22 -22
- data/spec/deal_spec.rb +101 -102
- data/spec/helpers/csv_helper_spec.rb +29 -29
- data/spec/helpers/email_helper_spec.rb +32 -32
- data/spec/helpers/phone_helper_spec.rb +97 -97
- data/spec/helpers/serialize_helper_spec.rb +249 -249
- data/spec/helpers/xsd_validate_spec.rb +58 -58
- data/spec/note_spec.rb +98 -67
- data/spec/organization_spec.rb +103 -89
- data/spec/person_spec.rb +134 -134
- data/spec/rootmodel_spec.rb +277 -230
- data/spec/templating_spec.rb +11 -11
- data/templates/csv/lib/tomodel.rb +230 -230
- data/templates/csv/spec/exporter_spec.rb +17 -17
- data/templates/csv/spec/sample_data/coworkers.csv +2 -2
- data/templates/csv/spec/sample_data/deals.csv +2 -2
- data/templates/csv/spec/sample_data/organizations.csv +2 -2
- data/templates/csv/spec/sample_data/persons.csv +2 -2
- data/templates/easy/Gemfile +5 -0
- data/templates/easy/Rakefile.rb +7 -0
- data/templates/easy/convert.rb +3 -0
- data/templates/easy/lib/tomodel.rb +330 -0
- data/templates/easy/spec/exporter_spec.rb +10 -0
- data/templates/easy/spec/sample_data/Company.txt +649 -0
- data/templates/easy/spec/spec_helper.rb +24 -0
- data/templates/excel/lib/tomodel.rb +207 -207
- data/templates/sqlserver/lib/tomodel.rb +79 -79
- 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,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
|
+
|