ooor 2.0.3 → 2.0.4

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.
@@ -6,23 +6,29 @@ module Ooor
6
6
  module ReflectionOoor # :nodoc:
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ def column_for_attribute(name)
10
+ self.class.columns_hash[name.to_s]
11
+ end
12
+
9
13
  module ClassMethods
10
- def set_columns_hash(view_fields={})
11
- reload_fields_definition()
12
- @t.columns_hash ||= {}
13
- @t.fields.each do |k, field|
14
- unless @t.associations_keys.index(k)
15
- @t.columns_hash[k] = field.merge({type: to_rails_type(view_fields[k] && view_fields[k]['type'] || field['type'])})
14
+ def columns_hash(view_fields=nil)
15
+ if view_fields || !@t.columns_hash
16
+ view_fields ||= {}
17
+ reload_fields_definition()
18
+ @t.columns_hash ||= {}
19
+ @t.fields.each do |k, field|
20
+ unless @t.associations_keys.index(k)
21
+ @t.columns_hash[k] = field.merge({type: to_rails_type(view_fields[k] && view_fields[k]['type'] || field['type'])})
22
+ end
16
23
  end
24
+ @t.columns_hash
25
+ else
26
+ @t.columns_hash
17
27
  end
18
- @t.columns_hash
19
- end
20
-
21
- def column_for_attribute(name)
22
- columns_hash[name.to_s]
23
28
  end
24
29
 
25
30
  def create_reflection(name)
31
+ reload_fields_definition()
26
32
  options = {}
27
33
  if many2one_associations.keys.include?(name)
28
34
  macro = :belongs_to
@@ -86,6 +92,25 @@ module Ooor
86
92
  @klass ||= connection.class_name_from_model_key(class_name).constantize
87
93
  end
88
94
 
95
+ def initialize(macro, name, options, active_record)
96
+ super
97
+ @collection = macro.in?([:has_many, :has_and_belongs_to_many])
98
+ end
99
+
100
+ # Returns a new, unsaved instance of the associated class. +options+ will
101
+ # be passed to the class's constructor.
102
+ def build_association(*options, &block)
103
+ klass.new(*options, &block)
104
+ end
105
+
106
+ # Returns whether or not this association reflection is for a collection
107
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
108
+ # +has_and_belongs_to_many+, +false+ otherwise.
109
+ def collection?
110
+ @collection
111
+ end
112
+
113
+
89
114
  end
90
115
 
91
116
  end
@@ -14,6 +14,7 @@ module Ooor
14
14
  :select_values, :group_values, :order_values, :reorder_flag, :joins_values, :where_values, :having_values,
15
15
  :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value, :page_value, :per_value
16
16
  alias :loaded? :loaded
17
+ alias :model :klass
17
18
 
18
19
  def build_where(opts, other = [])#TODO OpenERP domain is more than just the intersection of restrictions
19
20
  case opts
@@ -54,14 +55,14 @@ module Ooor
54
55
 
55
56
  def order(*args)
56
57
  relation = clone
57
- relation.order_values += args.flatten unless args.blank?
58
+ relation.order_values += args.flatten unless args.blank? || args[0] == false
58
59
  relation
59
60
  end
60
61
 
61
- def count(column_name = nil, options = {})
62
- column_name, options = nil, column_name if column_name.is_a?(Hash)
63
- calculate(:count, column_name, options)
64
- end
62
+ # def count(column_name = nil, options = {}) #TODO possible to implement?
63
+ # column_name, options = nil, column_name if column_name.is_a?(Hash)
64
+ # calculate(:count, column_name, options)
65
+ # end
65
66
 
66
67
  def initialize(klass, options={})
67
68
  @klass = klass
@@ -78,6 +79,8 @@ module Ooor
78
79
  @klass.new(*args, &block)
79
80
  end
80
81
 
82
+ alias build new
83
+
81
84
  def reload
82
85
  reset
83
86
  to_a # force reload
@@ -115,6 +118,14 @@ module Ooor
115
118
  args.any? ? apply_finder_options(args.first).to_a : to_a
116
119
  end
117
120
 
121
+ def first(*args)
122
+ limit(1).order('id').all(*args).first
123
+ end
124
+
125
+ def last(*args)
126
+ limit(1).order('id DESC').all(*args).first
127
+ end
128
+
118
129
  def to_a
119
130
  if loaded?
120
131
  @records
@@ -128,26 +139,10 @@ module Ooor
128
139
  if @options && @options[:name_search]
129
140
  name_search = @klass.name_search(@options[:name_search], where_values, 'ilike', @options[:context], @limit_value)
130
141
  @records = name_search.map do |tuple|
131
- r = @klass.new({name: tuple[1]}, [])
132
- r.id = tuple[0]
133
- r #TODO load the fields optionally
142
+ @klass.new({name: tuple[1]}, []).tap { |r| r.id = tuple[0] } #TODO load the fields optionally
134
143
  end
135
144
  else
136
- if @per_value && @page_value
137
- offset = @per_value * @page_value
138
- limit = @per_value
139
- else
140
- offset = @offset_value
141
- limit = @limit_value || false
142
- end
143
- @loaded = true
144
- opts = @options.merge({
145
- domain: where_values,
146
- offset: offset,
147
- limit: limit,
148
- order: search_order,
149
- })
150
- @records = @klass.find(:all, opts)
145
+ load_records_page(search_order)
151
146
  end
152
147
  end
153
148
  end
@@ -156,8 +151,38 @@ module Ooor
156
151
  false
157
152
  end
158
153
 
154
+ def inspect
155
+ entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
156
+ entries[10] = '...' if entries.size == 11
157
+
158
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
159
+ end
160
+
159
161
  protected
160
162
 
163
+ def load_records_page(search_order)
164
+ if @per_value && @page_value
165
+ offset = @per_value * @page_value
166
+ limit = @per_value
167
+ else
168
+ offset = @offset_value
169
+ limit = @limit_value || false
170
+ end
171
+ @loaded = true
172
+ opts = @options.merge({
173
+ domain: where_values,
174
+ offset: offset,
175
+ limit: limit,
176
+ order: search_order,
177
+ })
178
+ scope = @options.delete(:ids) || :all
179
+ if scope == []
180
+ @records = []
181
+ else
182
+ @records = @klass.find(scope, opts)
183
+ end
184
+ end
185
+
161
186
  def method_missing(method, *args, &block)
162
187
  if Array.method_defined?(method)
163
188
  to_a.send(method, *args, &block)
@@ -1,5 +1,5 @@
1
1
  # OOOR: OpenObject On Ruby
2
- # Copyright (C) 2009-2012 Akretion LTDA (<http://www.akretion.com>).
2
+ # Copyright (C) 2009-2014 Akretion LTDA (<http://www.akretion.com>).
3
3
  # Author: Raphaël Valyi
4
4
  # Licensed under the MIT license, see MIT-LICENSE file
5
5
 
@@ -8,6 +8,7 @@ module Ooor
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  OPERATORS = ["=", "!=", "<=", "<", ">", ">=", "=?", "=like", "=ilike", "like", "not like", "ilike", "not ilike", "in", "not in", "child_of"]
11
+ BLACKLIST = %w[id write_date create_date write_ui create_ui]
11
12
 
12
13
  module ClassMethods
13
14
 
@@ -68,28 +69,8 @@ module Ooor
68
69
  elsif request.is_a?(Hash)
69
70
  request2 = {}
70
71
  request.each do |k, v|
71
-
72
- if k.to_s.end_with?("_attributes")
73
- attrs = []
74
- if v.is_a?(Hash)
75
- v.each do |key, val|
76
- if !val["_destroy"].empty?
77
- attrs << [2, val[:id] || val['id']]
78
- elsif val[:id] || val['id']
79
- attrs << [1, val[:id] || val['id'], cast_request_to_openerp(val)]
80
- else
81
- attrs << [0, 0, cast_request_to_openerp(val)]
82
- end
83
- end
84
- end
85
-
86
- request2[k.to_s.gsub("_attributes", "")] = attrs
87
- else
88
- request2[k] = cast_request_to_openerp(v)
89
- end
72
+ request2[k] = cast_request_to_openerp(v)
90
73
  end
91
- request2
92
-
93
74
  else
94
75
  value_to_openerp(request)
95
76
  end
@@ -120,72 +101,124 @@ module Ooor
120
101
 
121
102
  end
122
103
 
123
- def to_openerp_hash(attributes=@attributes, associations=@associations)
124
- associations = cast_relations_to_openerp(associations)
125
- blacklist = %w[id write_date create_date write_ui create_ui]
126
- r = {}
127
- attributes.reject {|k, v| blacklist.index(k)}.merge(associations).each do |k, v|
128
- if k.end_with?("_id") && !self.class.associations_keys.index(k) && self.class.associations_keys.index(k.gsub(/_id$/, ""))
129
- r[k.gsub(/_id$/, "")] = v && v.to_i || v
130
- else
131
- r[k] = v
132
- end
104
+ def sanitize_attribute(skey, value)
105
+ type = self.class.fields[skey]['type']
106
+ if type == 'boolean' && value == 1 || value == "1"
107
+ true
108
+ elsif type == 'boolean'&& value == 0 || value == "0"
109
+ false
110
+ elsif value == false and type != 'boolean'
111
+ nil
112
+ else
113
+ value
114
+ end
115
+ end
116
+
117
+ def sanitize_association(skey, value)
118
+ if value.is_a?(Ooor::Base) || value.is_a?(Array) && value.all? {|i| i.is_a?(Ooor::Base)}
119
+ value
120
+ elsif value.is_a?(Array) && !self.class.many2one_associations.keys.index(skey)
121
+ value.reject {|i| i == ''}.map {|i| i.is_a?(String) ? i.to_i : i}
122
+ elsif value.is_a?(String)
123
+ sanitize_associatio_as_string(skey, value)
124
+ else
125
+ value
133
126
  end
134
- r
127
+ end
128
+
129
+ def sanitize_associatio_as_string(skey, value)
130
+ if self.class.polymorphic_m2o_associations.has_key?(skey)
131
+ value
132
+ elsif self.class.many2one_associations.has_key?(skey)
133
+ sanitize_m2o_association(value)
134
+ else
135
+ value.split(",").map {|i| i.to_i}
136
+ end
137
+ end
138
+
139
+ def sanitize_m2o_association(value)
140
+ if value.blank? || value == "0"
141
+ false
142
+ else
143
+ value.to_i
144
+ end
145
+ end
146
+
147
+ def to_openerp_hash
148
+ attributes, associations = get_changed_values
149
+ associations = cast_associations_to_openerp(associations)
150
+ attributes.merge(associations)
135
151
  end
136
152
 
137
- def cast_relations_to_openerp(associations=@associations)
138
- associations2 = {}
139
- associations.each do |k, v|
140
- if k.match(/_ids$/) && !self.class.associations_keys.index(k) && self.class.associations_keys.index(rel = k.gsub(/_ids$/, ""))
141
- if v.is_a? Array
142
- v.reject! {|i| i == ''}.map! {|i| i.to_i}
143
- end
144
- associations2[rel] = v
145
- elsif v.is_a?(Array) && (v.size == 0 or v[1].is_a?(String)) #reject non assigned many2one or empty list
146
- next
147
- else
148
- if k.end_with?("_ids") && v.is_a?(String)
149
- v = v.split(",").map{|i| i.to_i}
150
- end
151
- associations2[k] = v
153
+ def get_changed_values
154
+ attributes = {}
155
+ associations = {}
156
+
157
+ changed.each do |k|
158
+ if self.class.associations_keys.index(k)
159
+ associations[k] = @associations[k]#changes[k][1]
160
+ elsif self.class.fields.has_key?(k)
161
+ attributes[k]= @attributes[k]
162
+ elsif !BLACKLIST.index(k)
163
+ attributes[k] = changes[k][1]
152
164
  end
153
165
  end
166
+ return attributes, associations
167
+ end
154
168
 
155
- associations2.each do |k, v| #see OpenERP awkward associations API
156
- #already casted, possibly before server error!
157
- next if (v.is_a?(Array) && v.size == 1 && v[0].is_a?(Array)) \
158
- || self.class.many2one_associations[k] \
159
- || !v.is_a?(Array)
160
- new_rel = self.cast_relation(k, v, self.class.one2many_associations, self.class.many2many_associations)
161
- if new_rel #matches a known o2m or m2m
162
- associations2[k] = new_rel
163
- else
164
- self.class.many2one_associations.each do |k2, field| #try to cast the association to an inherited o2m or m2m:
165
- linked_class = self.class.const_get(field['relation'])
166
- new_rel = self.cast_relation(k, v, linked_class.one2many_associations, linked_class.many2many_associations)
167
- associations2[k] = new_rel and break if new_rel
168
- end
169
- end
169
+ def cast_associations_to_openerp(associations=@associations)
170
+ associations.each do |k, v|
171
+ associations[k] = self.cast_association(k, v)
172
+ end
173
+ end
174
+
175
+ # talk OpenERP cryptic associations API
176
+ def cast_association(k, v)
177
+ if self.class.one2many_associations[k]
178
+ cast_o2m_assocation(v)
179
+ elsif self.class.many2many_associations[k]
180
+ [[6, false, (v || []).map {|i| i.is_a?(Base) ? i.id : i}]]
181
+ elsif self.class.many2one_associations[k]
182
+ cast_m2o_association(v)
170
183
  end
171
- associations2
172
184
  end
173
185
 
174
- def cast_relation(k, v, one2many_associations, many2many_associations)
175
- if one2many_associations[k]
176
- return v.collect do |value|
177
- if value.is_a?(Base) #on the fly creation as in the GTK client
186
+ def cast_o2m_assocation(v)
187
+ if v.is_a?(Hash)
188
+ cast_o2m_nested_attributes(v)
189
+ else
190
+ v.collect do |value|
191
+ if value.is_a?(Base)
178
192
  [0, 0, value.to_openerp_hash]
193
+ elsif value.is_a?(Hash)
194
+ [0, 0, value]
179
195
  else
180
- if value.is_a?(Hash)
181
- [0, 0, value]
182
- else
183
- [1, value, {}]
184
- end
196
+ [1, value, {}]
185
197
  end
186
198
  end
187
- elsif many2many_associations[k]
188
- return v = [[6, 0, v]]
199
+ end
200
+ end
201
+
202
+ def cast_o2m_nested_attributes(v)
203
+ v.keys.collect do |key|
204
+ val = v[key]
205
+ if !val["_destroy"].blank?
206
+ [2, val[:id].to_i || val['id']]
207
+ elsif val[:id] || val['id']
208
+ [1, val[:id].to_i || val['id'], val]
209
+ else
210
+ [0, 0, val]
211
+ end
212
+ end
213
+ end
214
+
215
+ def cast_m2o_association(v)
216
+ if v.is_a?(Array)
217
+ v[0]
218
+ elsif v.is_a?(Base)
219
+ v.id
220
+ else
221
+ v
189
222
  end
190
223
  end
191
224
 
@@ -1,7 +1,7 @@
1
1
  module Ooor
2
2
  MAJOR = 2
3
3
  MINOR = 0
4
- TINY = 3
4
+ TINY = 4
5
5
  PRE = nil
6
6
 
7
7
  VERSION = [MAJOR, MINOR, TINY].compact.join('.')
@@ -42,15 +42,7 @@ describe Ooor do
42
42
  end
43
43
 
44
44
  it "should be able to load a profile" do
45
- module_ids = IrModuleModule.search(['name','=', 'sale']) + IrModuleModule.search(['name','=', 'account_voucher']) + IrModuleModule.search(['name','=', 'sale_stock'])
46
- module_ids.each do |accounting_module_id|
47
- mod = IrModuleModule.find(accounting_module_id)
48
- unless mod.state == "installed"
49
- mod.button_install
50
- end
51
- end
52
- wizard = BaseModuleUpgrade.create
53
- wizard.upgrade_module
45
+ IrModuleModule.install_modules(['sale', 'account_voucher'])
54
46
  @ooor.load_models
55
47
  @ooor.models.keys.should_not be_empty
56
48
  end
@@ -68,7 +60,7 @@ describe Ooor do
68
60
  describe "Do operations on configured database" do
69
61
  before(:all) do
70
62
  @ooor = Ooor.new(:url => @url, :username => @username, :password => @password, :database => @database,
71
- :models => ['res.user', 'res.partner', 'product.product', 'sale.order', 'account.invoice', 'product.category', 'stock.move', 'ir.ui.menu', 'ir.module.module'])
63
+ :models => ['res.user', 'res.partner', 'product.product', 'sale.order', 'account.invoice', 'product.category', 'ir.cron', 'ir.ui.menu', 'ir.module.module'])
72
64
  end
73
65
 
74
66
  describe "Finders operations" do
@@ -173,8 +165,16 @@ describe Ooor do
173
165
  it "should cast dates properly from OpenERP to Ruby" do
174
166
  o = SaleOrder.find(1)
175
167
  o.date_order.should be_kind_of(Date)
176
- m = StockMove.find(1)
177
- m.date.should be_kind_of(DateTime)
168
+ c = IrCron.find(1)
169
+ c.nextcall.should be_kind_of(DateTime)
170
+ end
171
+
172
+ it "should not load false values in empty strings (for HTML forms)" do
173
+ ResPartner.first.phone.should be_nil
174
+ end
175
+
176
+ it "should map OpenERP types to Rails types" do
177
+ (%w[char binary many2one one2many many2many]).each { |t| Ooor::Base.to_rails_type(t).should be_kind_of(Symbol) }
178
178
  end
179
179
 
180
180
  it "should be able to call any Class method" do
@@ -218,6 +218,12 @@ describe Ooor do
218
218
  p.name.should == "testProduct1"
219
219
  end
220
220
 
221
+ it "should properly change value when m2o is set" do
222
+ p = ProductProduct.find(:first)
223
+ p.categ_id = 7
224
+ p.categ_id.id.should == 7
225
+ end
226
+
221
227
  it "should be able to create a product" do
222
228
  p = ProductProduct.create(:name => "testProduct1", :categ_id => 1)
223
229
  ProductProduct.find(p.id).categ_id.id.should == 1
@@ -235,12 +241,12 @@ describe Ooor do
235
241
  u.save
236
242
  u.id.should_not be_nil
237
243
  u.name.should == "joe"
238
- u.destroy
244
+ u.destroy.should be_kind_of(ResUsers)
239
245
  end
240
246
 
241
247
  it "should be able to create an order" do
242
- o = SaleOrder.create(:partner_id => ResPartner.search([['name', 'ilike', 'Agrolait']])[0],
243
- :partner_order_id => 1, :partner_invoice_id => 1, :partner_shipping_id => 1, :pricelist_id => 1)
248
+ p_id = ResPartner.search([['name', 'ilike', 'Agrolait']])[0]
249
+ o = SaleOrder.create(partner_id: p_id, partner_invoice_id: p_id, partner_shipping_id: p_id, pricelist_id: 1)
244
250
  o.id.should be_kind_of(Integer)
245
251
  end
246
252
 
@@ -268,7 +274,7 @@ describe Ooor do
268
274
 
269
275
  it "should use default fields on creation" do
270
276
  p = ProductProduct.new
271
- p.sale_delay.should be_kind_of(Integer)
277
+ p.categ_id.should be_kind_of(ProductCategory)
272
278
  end
273
279
 
274
280
  it "should skipped inherited default fields properly, for instance at product variant creation" do
@@ -335,16 +341,44 @@ describe Ooor do
335
341
  p.taxes_id_ids.should be_kind_of(Array)
336
342
  end
337
343
 
338
- it "should support Rails nested attributes" do
344
+ it "should support Rails nested attributes methods" do
339
345
  so = SaleOrder.find :first
340
346
  so.respond_to?(:order_line_attributes).should be_true
341
347
  so.respond_to?(:order_line_attributes=).should be_true
342
348
  end
343
349
 
350
+ it "should support CRUD on o2m via nested attributes" do
351
+ p = ProductProduct.create(name:'Ooor product with packages')
352
+ p.packaging_attributes = {'1' => {name: 'pack1'}, '2' => {name: 'pack2'}}
353
+ p.save
354
+ p = ProductProduct.find p.id
355
+ pack1 = p.packaging[0]
356
+ pack2 = p.packaging[1]
357
+ pack2.name.should == 'pack2'
358
+ p.packaging_attributes = {'1' => {name: 'pack1', '_destroy'=> true, id: pack1.id}, '2' => {name: 'pack2_modified', id: pack2.id}}
359
+ p.save
360
+ p.packaging.size.should == 1
361
+ p.packaging[0].name.should == 'pack2_modified'
362
+ end
363
+
344
364
  it "should be able to call build upon a o2m association" do
345
365
  so = SaleOrder.find :first
346
366
  so.order_line.build().should be_kind_of(SaleOrderLine)
347
367
  end
368
+
369
+ it "should recast string m2o string id to an integer (it happens in forms)" do
370
+ uom_id = @ooor.const_get('product.uom').search()[0]
371
+ p = ProductProduct.new(name: "z recast id", uom_id: uom_id.to_s)
372
+ p.save
373
+ p.uom_id.id.should == uom_id
374
+ end
375
+
376
+ it "should recast string m2m string ids to an array of integer (it happens in forms)" do
377
+ categ_ids = @ooor.const_get('res.partner.category').search()[0..1]
378
+ p = ResPartner.new(name: "z recast ids", category_id: categ_ids.join(','))
379
+ p.save
380
+ p.category_id.map{|c| c.id}.should == categ_ids
381
+ end
348
382
  end
349
383
 
350
384
  describe "Fields validations" do
@@ -354,6 +388,28 @@ describe Ooor do
354
388
  p.save.should == false
355
389
  p.errors.messages[:ean13].should_not be_nil
356
390
  end
391
+
392
+ it "should list all available fields when you call an invalid field" do
393
+ expect { ProductProduct.find(1).unexisting_field_or_method }.to raise_error(Ooor::UnknownAttributeOrAssociationError, /AVAILABLE FIELDS/)
394
+ end
395
+ end
396
+
397
+ describe "Life cycle Callbacks" do
398
+ include Ooor
399
+
400
+ it "should call customized before_save callback" do
401
+ expect do
402
+ Ooor.xtend('ir.ui.menu') do
403
+ before_save do
404
+ raise 'before_save_called'
405
+ end
406
+ end
407
+
408
+ with_ooor_session username: 'demo', password: 'demo' do |session|
409
+ session['ir.ui.menu'].first.save
410
+ end
411
+ end.to raise_error(RuntimeError, /before_save_called/)
412
+ end
357
413
  end
358
414
 
359
415
  describe "ARel emulation" do
@@ -361,13 +417,37 @@ describe Ooor do
361
417
  ResUsers.all.should be_kind_of(Array)
362
418
  end
363
419
 
420
+ it "should have a 'first' method" do
421
+ ResUsers.first.id.should == 1
422
+ end
423
+
424
+ it "should have a 'last' method" do
425
+ ResUsers.last.id.should == ResUsers.find(:last).id
426
+ end
427
+
364
428
  it "should be ready for Kaminari pagination via ARel scoping" do
365
429
  num = 2
366
430
  default_per_page = 5
367
- collection = ProductProduct.limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1))
431
+ collection = ProductProduct.where(active: true).limit(default_per_page).offset(default_per_page * ([num.to_i, 1].max - 1)).order("categ_id")
368
432
  collection.all(fields:['name']).should be_kind_of(Array)
369
433
  collection.all.size.should == 5
370
434
  end
435
+
436
+ it "should support name_search in ARel (used in association widgets with Ooorest)" do
437
+ Ooor.default_session.const_get('product.category').all(name_search: 'Com')[0].name.should == "All products / Saleable / Components"
438
+ end
439
+
440
+ it "should be possible to invoke batch methods on relations" do
441
+ Ooor.default_session.const_get('product.product').where(type: 'service').write(type: 'service').should == true
442
+ end
443
+
444
+ it "should forward Array methods to the Array" do
445
+ Ooor.default_session.const_get('product.product').where(type: 'service').size.should be_kind_of(Integer)
446
+ end
447
+
448
+ it "should support reloading relation" do
449
+ Ooor.default_session.const_get('product.product').where(type: 'service').reload.all.should be_kind_of(Array)
450
+ end
371
451
  end
372
452
 
373
453
  describe "report support" do
@@ -383,12 +463,12 @@ describe Ooor do
383
463
  inv.state.should == "draft"
384
464
  inv.wkf_action('invoice_open')
385
465
  inv.state.should == "open"
386
- voucher = AccountVoucher.new({:amount=>inv.amount_total, :type=>"receipt", :partner_id => inv.partner_id.id}, {"default_amount"=>inv.amount_total, "invoice_id"=>inv.id})
387
- voucher.on_change("onchange_partner_id", [], :partner_id, inv.partner_id.id, AccountJournal.find('account.bank_journal').id, 0.0, 1, 'receipt', false)
466
+ voucher = @ooor.const_get('account.voucher').new({:amount=>inv.amount_total, :type=>"receipt", :partner_id => inv.partner_id.id}, {"default_amount"=>inv.amount_total, "invoice_id"=>inv.id})
467
+ voucher.on_change("onchange_partner_id", [], :partner_id, inv.partner_id.id, @ooor.const_get('account.journal').find('account.bank_journal').id, 0.0, 1, 'receipt', false)
388
468
  voucher.save
389
- voucher.wkf_action 'proforma_voucher'
469
+ # voucher.wkf_action 'proforma_voucher'
390
470
 
391
- inv.reload
471
+ # inv.reload
392
472
  end
393
473
 
394
474
  it "should be possible to call resource actions and workflow actions" do
@@ -443,11 +523,19 @@ describe Ooor do
443
523
  Ooor.session_handler.reset!() # alias isn't part of the connection spec, we don't want connectio reuse here
444
524
  with_ooor_session(:url => @url, :database => @database, :username => @username, :password => @password, :aliases => {en_US: {products: 'product.product'}}, :param_keys => {'product.product' => 'name'}) do |session|
445
525
  session['products'].search().should be_kind_of(Array)
526
+ session['product.product'].alias.should == 'products'
446
527
  end
447
528
  end
448
529
 
449
- it "should find by permalink" do
530
+ it "should have a to_param method" do
450
531
  Ooor.session_handler.reset!() # alias isn't part of the connection spec, we don't want connectio reuse here
532
+ with_ooor_session(:url => @url, :database => @database, :username => @username, :password => @password, :aliases => {en_US: {products: 'product.product'}}, :param_keys => {'product.product' => 'name'}) do |session|
533
+ session['product.product'].find(:first).to_param.should be_kind_of(String)
534
+ end
535
+ end
536
+
537
+ it "should find by permalink" do
538
+ Ooor.session_handler.reset!() # alias isn't part of the connection spec, we don't want connection reuse here
451
539
  with_ooor_session(:url => @url, :database => @database, :username => @username, :password => @password, :aliases => {en_US: {products: 'product.product'}}, :param_keys => {'product.product' => 'name'}) do |session|
452
540
  lang = Ooor::Locale.to_erp_locale('en')
453
541
  session['products'].find_by_permalink('Service', context: {'lang' => lang}, fields: ['name']).should be_kind_of(Ooor::Base)
@@ -455,24 +543,33 @@ describe Ooor do
455
543
  end
456
544
  end
457
545
 
458
- describe "Ative-Record like Reflection" do
546
+ describe "Ative-Record like Reflections" do
459
547
  before(:all) do
460
- @ooor = Ooor.new(:url => @url, :username => @username, :password => @password, :database => @database, :models => ['product.product'], :reload => true)
548
+ @ooor = Ooor.new(:url => @url, :username => @username, :password => @password, :database => @database, :models => ['product.product', 'product.category'], :reload => true)
461
549
  end
462
550
 
463
- it "should test correct class attributes" do
551
+ it "should test correct class attributes of ActiveRecord Reflection" do
464
552
  object = Ooor::Reflection::AssociationReflection.new(:test, :people, {}, nil)
465
553
  object.name.should == :people
466
554
  object.macro.should == :test
467
555
  object.options.should == {}
468
556
  end
469
557
 
470
- it "should test correct class name matching wit class name" do
558
+ it "should test correct class name matching with class name" do
471
559
  object = Ooor::Reflection::AssociationReflection.new(:test, 'product_product', {class_name: 'product.product'}, nil)
472
560
  object.connection = @ooor
473
561
  object.klass.should == ProductProduct
474
562
  end
475
563
 
564
+ it "should reflect on association (used in simple_form, cocoon...)" do
565
+ reflection = ProductProduct.reflect_on_association(:categ_id)
566
+ reflection.should be_kind_of(Ooor::Reflection::AssociationReflection)
567
+ reflection.klass.should == ProductCategory
568
+ end
569
+
570
+ it "should support column_for_attribute (used by simple_form)" do
571
+ @ooor.const_get('ir.cron').find(:first).column_for_attribute('name')[:type].should == :string
572
+ end
476
573
  end
477
574
 
478
575
  describe "Multi-instance and class name scoping" do
@@ -510,6 +607,15 @@ describe Ooor do
510
607
  session['res.users'].search().should be_kind_of(Array)
511
608
  end
512
609
  end
610
+
611
+ it "should recover from expired sessions" do
612
+ with_ooor_session(:url => @url, :username => @username, :password => @password, :database => @database) do |session|
613
+ user_obj = session['res.users']
614
+ user_obj.search().should be_kind_of(Array)
615
+ session.web_session[:session_id] = 'invalid'
616
+ user_obj.search().should be_kind_of(Array)
617
+ end
618
+ end
513
619
  end
514
620
 
515
621