[FIX] website_forum_doc fixes
[odoo/odoo.git] / addons / website_sale / controllers / main.py
1 # -*- coding: utf-8 -*-
2 import werkzeug
3
4 from openerp import SUPERUSER_ID
5 from openerp import http
6 from openerp.http import request
7 from openerp.tools.translate import _
8 from openerp.addons.website.models.website import slug
9
10 PPG = 20 # Products Per Page
11 PPR = 4  # Products Per Row
12
13 class table_compute(object):
14     def __init__(self):
15         self.table = {}
16
17     def _check_place(self, posx, posy, sizex, sizey):
18         res = True
19         for y in range(sizey):
20             for x in range(sizex):
21                 if posx+x>=PPR:
22                     res = False
23                     break
24                 row = self.table.setdefault(posy+y, {})
25                 if row.setdefault(posx+x) is not None:
26                     res = False
27                     break
28             for x in range(PPR):
29                 self.table[posy+y].setdefault(x, None)
30         return res
31
32     def process(self, products):
33         # Compute products positions on the grid
34         minpos = 0
35         index = 0
36         maxy = 0
37         for p in products:
38             x = min(max(p.website_size_x, 1), PPR)
39             y = min(max(p.website_size_y, 1), PPR)
40             if index>=PPG:
41                 x = y = 1
42
43             pos = minpos
44             while not self._check_place(pos%PPR, pos/PPR, x, y):
45                 pos += 1
46             # if 21st products (index 20) and the last line is full (PPR products in it), break
47             # (pos + 1.0) / PPR is the line where the product would be inserted
48             # maxy is the number of existing lines
49             # + 1.0 is because pos begins at 0, thus pos 20 is actually the 21st block
50             # and to force python to not round the division operation
51             if index >= PPG and ((pos + 1.0) / PPR) > maxy:
52                 break
53
54             if x==1 and y==1:   # simple heuristic for CPU optimization
55                 minpos = pos/PPR
56
57             for y2 in range(y):
58                 for x2 in range(x):
59                     self.table[(pos/PPR)+y2][(pos%PPR)+x2] = False
60             self.table[pos/PPR][pos%PPR] = {
61                 'product': p, 'x':x, 'y': y,
62                 'class': " ".join(map(lambda x: x.html_class or '', p.website_style_ids))
63             }
64             if index<=PPG:
65                 maxy=max(maxy,y+(pos/PPR))
66             index += 1
67
68         # Format table according to HTML needs
69         rows = self.table.items()
70         rows.sort()
71         rows = map(lambda x: x[1], rows)
72         for col in range(len(rows)):
73             cols = rows[col].items()
74             cols.sort()
75             x += len(cols)
76             rows[col] = [c for c in map(lambda x: x[1], cols) if c != False]
77
78         return rows
79
80         # TODO keep with input type hidden
81
82
83 class QueryURL(object):
84     def __init__(self, path='', **args):
85         self.path = path
86         self.args = args
87
88     def __call__(self, path=None, **kw):
89         if not path:
90             path = self.path
91         for k,v in self.args.items():
92             kw.setdefault(k,v)
93         l = []
94         for k,v in kw.items():
95             if v:
96                 if isinstance(v, list) or isinstance(v, set):
97                     l.append(werkzeug.url_encode([(k,i) for i in v]))
98                 else:
99                     l.append(werkzeug.url_encode([(k,v)]))
100         if l:
101             path += '?' + '&'.join(l)
102         return path
103
104
105 def get_pricelist():
106     cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
107     sale_order = context.get('sale_order')
108     if sale_order:
109         pricelist = sale_order.pricelist_id
110     else:
111         partner = pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id
112         pricelist = partner.property_product_pricelist
113     return pricelist
114
115 class website_sale(http.Controller):
116
117     def get_pricelist(self):
118         return get_pricelist()
119
120     def get_attribute_value_ids(self, product):
121         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
122         currency_obj = pool['res.currency']
123         attribute_value_ids = []
124         if request.website.pricelist_id.id != context['pricelist']:
125             website_currency_id = request.website.currency_id.id
126             currency_id = self.get_pricelist().currency_id.id
127             for p in product.product_variant_ids:
128                 price = currency_obj.compute(cr, uid, website_currency_id, currency_id, p.lst_price)
129                 attribute_value_ids.append([p.id, map(int, p.attribute_value_ids), p.price, price])
130         else:
131             attribute_value_ids = [[p.id, map(int, p.attribute_value_ids), p.price, p.lst_price] for p in product.product_variant_ids]
132
133         return attribute_value_ids
134
135     @http.route(['/shop',
136         '/shop/page/<int:page>',
137         '/shop/category/<model("product.public.category"):category>',
138         '/shop/category/<model("product.public.category"):category>/page/<int:page>'
139     ], type='http', auth="public", website=True)
140     def shop(self, page=0, category=None, search='', **post):
141         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
142
143         domain = request.website.sale_product_domain()
144         if search:
145             domain += ['|', '|', '|', ('name', 'ilike', search), ('description', 'ilike', search),
146                 ('description_sale', 'ilike', search), ('product_variant_ids.default_code', 'ilike', search)]
147         if category:
148             domain += [('product_variant_ids.public_categ_ids', 'child_of', int(category))]
149
150         attrib_list = request.httprequest.args.getlist('attrib')
151         attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
152         attrib_set = set([v[1] for v in attrib_values])
153
154         if attrib_values:
155             attrib = None
156             ids = []
157             for value in attrib_values:
158                 if not attrib:
159                     attrib = value[0]
160                     ids.append(value[1])
161                 elif value[0] == attrib:
162                     ids.append(value[1])
163                 else:
164                     domain += [('attribute_line_ids.value_ids', 'in', ids)]
165                     attrib = value[0]
166                     ids = [value[1]]
167             if attrib:
168                 domain += [('attribute_line_ids.value_ids', 'in', ids)]
169
170         keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_list)
171
172         if not context.get('pricelist'):
173             pricelist = self.get_pricelist()
174             context['pricelist'] = int(pricelist)
175         else:
176             pricelist = pool.get('product.pricelist').browse(cr, uid, context['pricelist'], context)
177
178         url = "/shop"
179         if search:
180             post["search"] = search
181         if category:
182             category = pool['product.public.category'].browse(cr, uid, int(category), context=context)
183             url = "/shop/category/%s" % slug(category)
184
185         style_obj = pool['product.style']
186         style_ids = style_obj.search(cr, uid, [], context=context)
187         styles = style_obj.browse(cr, uid, style_ids, context=context)
188
189         category_obj = pool['product.public.category']
190         category_ids = category_obj.search(cr, uid, [], context=context)
191         categories = category_obj.browse(cr, uid, category_ids, context=context)
192         categs = filter(lambda x: not x.parent_id, categories)
193
194         domain += [('public_categ_ids', 'in', category_ids)]
195         product_obj = pool.get('product.template')
196
197         product_count = product_obj.search_count(cr, uid, domain, context=context)
198         pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
199         product_ids = product_obj.search(cr, uid, domain, limit=PPG, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
200         products = product_obj.browse(cr, uid, product_ids, context=context)
201
202         attributes_obj = request.registry['product.attribute']
203         attributes_ids = attributes_obj.search(cr, uid, [], context=context)
204         attributes = attributes_obj.browse(cr, uid, attributes_ids, context=context)
205
206         from_currency = pool.get('product.price.type')._get_field_currency(cr, uid, 'list_price', context)
207         to_currency = pricelist.currency_id
208         compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context)
209
210         values = {
211             'search': search,
212             'category': category,
213             'attrib_values': attrib_values,
214             'attrib_set': attrib_set,
215             'pager': pager,
216             'pricelist': pricelist,
217             'products': products,
218             'bins': table_compute().process(products),
219             'rows': PPR,
220             'styles': styles,
221             'categories': categs,
222             'attributes': attributes,
223             'compute_currency': compute_currency,
224             'keep': keep,
225             'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
226             'attrib_encode': lambda attribs: werkzeug.url_encode([('attrib',i) for i in attribs]),
227         }
228         return request.website.render("website_sale.products", values)
229
230     @http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
231     def product(self, product, category='', search='', **kwargs):
232         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
233         category_obj = pool['product.public.category']
234         template_obj = pool['product.template']
235
236         context.update(active_id=product.id)
237
238         if category:
239             category = category_obj.browse(cr, uid, int(category), context=context)
240
241         attrib_list = request.httprequest.args.getlist('attrib')
242         attrib_values = [map(int,v.split("-")) for v in attrib_list if v]
243         attrib_set = set([v[1] for v in attrib_values])
244
245         keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_list)
246
247         category_ids = category_obj.search(cr, uid, [], context=context)
248         category_list = category_obj.name_get(cr, uid, category_ids, context=context)
249         category_list = sorted(category_list, key=lambda category: category[1])
250
251         pricelist = self.get_pricelist()
252
253         from_currency = pool.get('product.price.type')._get_field_currency(cr, uid, 'list_price', context)
254         to_currency = pricelist.currency_id
255         compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context)
256
257         if not context.get('pricelist'):
258             context['pricelist'] = int(self.get_pricelist())
259             product = template_obj.browse(cr, uid, int(product), context=context)
260
261         values = {
262             'search': search,
263             'category': category,
264             'pricelist': pricelist,
265             'attrib_values': attrib_values,
266             'compute_currency': compute_currency,
267             'attrib_set': attrib_set,
268             'keep': keep,
269             'category_list': category_list,
270             'main_object': product,
271             'product': product,
272             'get_attribute_value_ids': self.get_attribute_value_ids
273         }
274         return request.website.render("website_sale.product", values)
275
276     @http.route(['/shop/product/comment/<int:product_template_id>'], type='http', auth="public", methods=['POST'], website=True)
277     def product_comment(self, product_template_id, **post):
278         cr, uid, context = request.cr, request.uid, request.context
279         if post.get('comment'):
280             request.registry['product.template'].message_post(
281                 cr, uid, product_template_id,
282                 body=post.get('comment'),
283                 type='comment',
284                 subtype='mt_comment',
285                 context=dict(context, mail_create_nosubcribe=True))
286         return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
287
288     @http.route(['/shop/pricelist'], type='http', auth="public", website=True)
289     def pricelist(self, promo, **post):
290         cr, uid, context = request.cr, request.uid, request.context
291         request.website.sale_get_order(code=promo, context=context)
292         return request.redirect("/shop/cart")
293
294     @http.route(['/shop/cart'], type='http', auth="public", website=True)
295     def cart(self, **post):
296         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
297         order = request.website.sale_get_order()
298         if order:
299             from_currency = pool.get('product.price.type')._get_field_currency(cr, uid, 'list_price', context)
300             to_currency = order.pricelist_id.currency_id
301             compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context)
302         else:
303             compute_currency = lambda price: price
304
305         values = {
306             'order': order,
307             'compute_currency': compute_currency,
308             'suggested_products': [],
309         }
310         if order:
311             _order = order
312             if not context.get('pricelist'):
313                 _order = order.with_context(pricelist=order.pricelist_id.id)
314             values['suggested_products'] = _order._cart_accessories()
315
316         return request.website.render("website_sale.cart", values)
317
318     @http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True)
319     def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
320         cr, uid, context = request.cr, request.uid, request.context
321         request.website.sale_get_order(force_create=1)._cart_update(product_id=int(product_id), add_qty=float(add_qty), set_qty=float(set_qty))
322         return request.redirect("/shop/cart")
323
324     @http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True)
325     def cart_update_json(self, product_id, line_id, add_qty=None, set_qty=None, display=True):
326         order = request.website.sale_get_order(force_create=1)
327         value = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty)
328         if not display:
329             return None
330         value['cart_quantity'] = order.cart_quantity
331         value['website_sale.total'] = request.website._render("website_sale.total", {
332                 'website_sale_order': request.website.sale_get_order()
333             })
334         return value
335
336     #------------------------------------------------------
337     # Checkout
338     #------------------------------------------------------
339
340     def checkout_redirection(self, order):
341         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
342
343         # must have a draft sale order with lines at this point, otherwise reset
344         if not order or order.state != 'draft':
345             request.session['sale_order_id'] = None
346             request.session['sale_transaction_id'] = None
347             return request.redirect('/shop')
348
349         # if transaction pending / done: redirect to confirmation
350         tx = context.get('website_sale_transaction')
351         if tx and tx.state != 'draft':
352             return request.redirect('/shop/payment/confirmation/%s' % order.id)
353
354     def checkout_values(self, data=None):
355         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
356         orm_partner = registry.get('res.partner')
357         orm_user = registry.get('res.users')
358         orm_country = registry.get('res.country')
359         state_orm = registry.get('res.country.state')
360
361         country_ids = orm_country.search(cr, SUPERUSER_ID, [], context=context)
362         countries = orm_country.browse(cr, SUPERUSER_ID, country_ids, context)
363         states_ids = state_orm.search(cr, SUPERUSER_ID, [], context=context)
364         states = state_orm.browse(cr, SUPERUSER_ID, states_ids, context)
365         partner = orm_user.browse(cr, SUPERUSER_ID, request.uid, context).partner_id
366
367         order = None
368
369         shipping_id = None
370         shipping_ids = []
371         checkout = {}
372         if not data:
373             if request.uid != request.website.user_id.id:
374                 checkout.update( self.checkout_parse("billing", partner) )
375                 shipping_ids = orm_partner.search(cr, SUPERUSER_ID, [("parent_id", "=", partner.id), ('type', "=", 'delivery')], context=context)
376             else:
377                 order = request.website.sale_get_order(force_create=1, context=context)
378                 if order.partner_id:
379                     domain = [("partner_id", "=", order.partner_id.id)]
380                     user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID, domain, context=dict(context or {}, active_test=False))
381                     if not user_ids or request.website.user_id.id not in user_ids:
382                         checkout.update( self.checkout_parse("billing", order.partner_id) )
383         else:
384             checkout = self.checkout_parse('billing', data)
385             try: 
386                 shipping_id = int(data["shipping_id"])
387             except ValueError:
388                 pass
389             if shipping_id == -1:
390                 checkout.update(self.checkout_parse('shipping', data))
391
392         if shipping_id is None:
393             if not order:
394                 order = request.website.sale_get_order(context=context)
395             if order and order.partner_shipping_id:
396                 shipping_id = order.partner_shipping_id.id
397
398         shipping_ids = list(set(shipping_ids) - set([partner.id]))
399
400         if shipping_id == partner.id:
401             shipping_id = 0
402         elif shipping_id > 0 and shipping_id not in shipping_ids:
403             shipping_ids.append(shipping_id)
404         elif shipping_id is None and shipping_ids:
405             shipping_id = shipping_ids[0]
406
407         ctx = dict(context, show_address=1)
408         shippings = []
409         if shipping_ids:
410             shippings = shipping_ids and orm_partner.browse(cr, SUPERUSER_ID, list(shipping_ids), ctx) or []
411         if shipping_id > 0:
412             shipping = orm_partner.browse(cr, SUPERUSER_ID, shipping_id, ctx)
413             checkout.update( self.checkout_parse("shipping", shipping) )
414
415         checkout['shipping_id'] = shipping_id
416
417         # Default search by user country
418         if not checkout.get('country_id'):
419             country_code = request.session['geoip'].get('country_code')
420             if country_code:
421                 country_ids = request.registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context)
422                 if country_ids:
423                     checkout['country_id'] = country_ids[0]
424
425         values = {
426             'countries': countries,
427             'states': states,
428             'checkout': checkout,
429             'shipping_id': partner.id != shipping_id and shipping_id or 0,
430             'shippings': shippings,
431             'error': {},
432             'has_check_vat': hasattr(registry['res.partner'], 'check_vat')
433         }
434
435         return values
436
437     mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id"]
438     optional_billing_fields = ["street", "state_id", "vat", "vat_subjected", "zip"]
439     mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id"]
440     optional_shipping_fields = ["state_id", "zip"]
441
442     def checkout_parse(self, address_type, data, remove_prefix=False):
443         """ data is a dict OR a partner browse record
444         """
445         # set mandatory and optional fields
446         assert address_type in ('billing', 'shipping')
447         if address_type == 'billing':
448             all_fields = self.mandatory_billing_fields + self.optional_billing_fields
449             prefix = ''
450         else:
451             all_fields = self.mandatory_shipping_fields + self.optional_shipping_fields
452             prefix = 'shipping_'
453
454         # set data
455         if isinstance(data, dict):
456             query = dict((prefix + field_name, data[prefix + field_name])
457                 for field_name in all_fields if data.get(prefix + field_name))
458         else:
459             query = dict((prefix + field_name, getattr(data, field_name))
460                 for field_name in all_fields if getattr(data, field_name))
461             if address_type == 'billing' and data.parent_id:
462                 query[prefix + 'street'] = data.parent_id.name
463
464         if query.get(prefix + 'state_id'):
465             query[prefix + 'state_id'] = int(query[prefix + 'state_id'])
466         if query.get(prefix + 'country_id'):
467             query[prefix + 'country_id'] = int(query[prefix + 'country_id'])
468
469         if query.get(prefix + 'vat'):
470             query[prefix + 'vat_subjected'] = True
471
472         if not remove_prefix:
473             return query
474
475         return dict((field_name, data[prefix + field_name]) for field_name in all_fields if data.get(prefix + field_name))
476
477     def checkout_form_validate(self, data):
478         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
479
480         # Validation
481         error = dict()
482         for field_name in self.mandatory_billing_fields:
483             if not data.get(field_name):
484                 error[field_name] = 'missing'
485
486         if data.get("vat") and hasattr(registry["res.partner"], "check_vat"):
487             if request.website.company_id.vat_check_vies:
488                 # force full VIES online check
489                 check_func = registry["res.partner"].vies_vat_check
490             else:
491                 # quick and partial off-line checksum validation
492                 check_func = registry["res.partner"].simple_vat_check
493             vat_country, vat_number = registry["res.partner"]._split_vat(data.get("vat"))
494             if not check_func(cr, uid, vat_country, vat_number, context=None): # simple_vat_check
495                 error["vat"] = 'error'
496
497         if data.get("shipping_id") == -1:
498             for field_name in self.mandatory_shipping_fields:
499                 field_name = 'shipping_' + field_name
500                 if not data.get(field_name):
501                     error[field_name] = 'missing'
502
503         return error
504
505     def checkout_form_save(self, checkout):
506         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
507
508         order = request.website.sale_get_order(force_create=1, context=context)
509
510         orm_partner = registry.get('res.partner')
511         orm_user = registry.get('res.users')
512         order_obj = request.registry.get('sale.order')
513
514         billing_info = self.checkout_parse('billing', checkout, True)
515
516         # set partner_id
517         partner_id = None
518         if request.uid != request.website.user_id.id:
519             partner_id = orm_user.browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
520         elif order.partner_id:
521             user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID,
522                 [("partner_id", "=", order.partner_id.id)], context=dict(context or {}, active_test=False))
523             if not user_ids or request.website.user_id.id not in user_ids:
524                 partner_id = order.partner_id.id
525
526         # save partner informations
527         if partner_id and request.website.partner_id.id != partner_id:
528             orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context)
529         else:
530             # create partner
531             partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context)
532
533         # create a new shipping partner
534         if checkout.get('shipping_id') == -1:
535             shipping_info = self.checkout_parse('shipping', checkout, True)
536             shipping_info['type'] = 'delivery'
537             shipping_info['parent_id'] = partner_id
538             checkout['shipping_id'] = orm_partner.create(cr, SUPERUSER_ID, shipping_info, context)
539
540         order_info = {
541             'partner_id': partner_id,
542             'message_follower_ids': [(4, partner_id), (3, request.website.partner_id.id)],
543             'partner_invoice_id': partner_id,
544         }
545         order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
546         address_change = order_obj.onchange_delivery_id(cr, SUPERUSER_ID, [], order.company_id.id, partner_id,
547                                                         checkout.get('shipping_id'), None, context=context)['value']
548         order_info.update(address_change)
549         if address_change.get('fiscal_position'):
550             fiscal_update = order_obj.onchange_fiscal_position(cr, SUPERUSER_ID, [], address_change['fiscal_position'],
551                                                                [(4, l.id) for l in order.order_line], context=None)['value']
552             order_info.update(fiscal_update)
553
554         order_info.pop('user_id')
555         order_info.update(partner_shipping_id=checkout.get('shipping_id') or partner_id)
556
557         order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context)
558
559     @http.route(['/shop/checkout'], type='http', auth="public", website=True)
560     def checkout(self, **post):
561         cr, uid, context = request.cr, request.uid, request.context
562
563         order = request.website.sale_get_order(force_create=1, context=context)
564
565         redirection = self.checkout_redirection(order)
566         if redirection:
567             return redirection
568
569         values = self.checkout_values()
570
571         return request.website.render("website_sale.checkout", values)
572
573     @http.route(['/shop/confirm_order'], type='http', auth="public", website=True)
574     def confirm_order(self, **post):
575         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
576
577         order = request.website.sale_get_order(context=context)
578         if not order:
579             return request.redirect("/shop")
580
581         redirection = self.checkout_redirection(order)
582         if redirection:
583             return redirection
584
585         values = self.checkout_values(post)
586
587         values["error"] = self.checkout_form_validate(values["checkout"])
588         if values["error"]:
589             return request.website.render("website_sale.checkout", values)
590
591         self.checkout_form_save(values["checkout"])
592
593         request.session['sale_last_order_id'] = order.id
594
595         request.website.sale_get_order(update_pricelist=True, context=context)
596
597         return request.redirect("/shop/payment")
598
599     #------------------------------------------------------
600     # Payment
601     #------------------------------------------------------
602
603     @http.route(['/shop/payment'], type='http', auth="public", website=True)
604     def payment(self, **post):
605         """ Payment step. This page proposes several payment means based on available
606         payment.acquirer. State at this point :
607
608          - a draft sale order with lines; otherwise, clean context / session and
609            back to the shop
610          - no transaction in context / session, or only a draft one, if the customer
611            did go to a payment.acquirer website but closed the tab without
612            paying / canceling
613         """
614         cr, uid, context = request.cr, request.uid, request.context
615         payment_obj = request.registry.get('payment.acquirer')
616         sale_order_obj = request.registry.get('sale.order')
617
618         order = request.website.sale_get_order(context=context)
619
620         redirection = self.checkout_redirection(order)
621         if redirection:
622             return redirection
623
624         shipping_partner_id = False
625         if order:
626             if order.partner_shipping_id.id:
627                 shipping_partner_id = order.partner_shipping_id.id
628             else:
629                 shipping_partner_id = order.partner_invoice_id.id
630
631         values = {
632             'order': request.registry['sale.order'].browse(cr, SUPERUSER_ID, order.id, context=context)
633         }
634         values['errors'] = sale_order_obj._get_errors(cr, uid, order, context=context)
635         values.update(sale_order_obj._get_website_data(cr, uid, order, context))
636
637         # fetch all registered payment means
638         # if tx:
639         #     acquirer_ids = [tx.acquirer_id.id]
640         # else:
641         if not values['errors']:
642             acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True), ('company_id', '=', order.company_id.id)], context=context)
643             values['acquirers'] = list(payment_obj.browse(cr, uid, acquirer_ids, context=context))
644             render_ctx = dict(context, submit_class='btn btn-primary', submit_txt=_('Pay Now'))
645             for acquirer in values['acquirers']:
646                 acquirer.button = payment_obj.render(
647                     cr, SUPERUSER_ID, acquirer.id,
648                     order.name,
649                     order.amount_total,
650                     order.pricelist_id.currency_id.id,
651                     partner_id=shipping_partner_id,
652                     tx_values={
653                         'return_url': '/shop/payment/validate',
654                     },
655                     context=render_ctx)
656
657         return request.website.render("website_sale.payment", values)
658
659     @http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
660     def payment_transaction(self, acquirer_id):
661         """ Json method that creates a payment.transaction, used to create a
662         transaction when the user clicks on 'pay now' button. After having
663         created the transaction, the event continues and the user is redirected
664         to the acquirer website.
665
666         :param int acquirer_id: id of a payment.acquirer record. If not set the
667                                 user is redirected to the checkout page
668         """
669         cr, uid, context = request.cr, request.uid, request.context
670         transaction_obj = request.registry.get('payment.transaction')
671         order = request.website.sale_get_order(context=context)
672
673         if not order or not order.order_line or acquirer_id is None:
674             return request.redirect("/shop/checkout")
675
676         assert order.partner_id.id != request.website.partner_id.id
677
678         # find an already existing transaction
679         tx = request.website.sale_get_transaction()
680         if tx:
681             if tx.state == 'draft':  # button cliked but no more info -> rewrite on tx or create a new one ?
682                 tx.write({
683                     'acquirer_id': acquirer_id,
684                 })
685             tx_id = tx.id
686         else:
687             tx_id = transaction_obj.create(cr, SUPERUSER_ID, {
688                 'acquirer_id': acquirer_id,
689                 'type': 'form',
690                 'amount': order.amount_total,
691                 'currency_id': order.pricelist_id.currency_id.id,
692                 'partner_id': order.partner_id.id,
693                 'partner_country_id': order.partner_id.country_id.id,
694                 'reference': order.name,
695                 'sale_order_id': order.id,
696             }, context=context)
697             request.session['sale_transaction_id'] = tx_id
698             tx = transaction_obj.browse(cr, SUPERUSER_ID, tx_id, context=context)
699
700         # update quotation
701         request.registry['sale.order'].write(
702             cr, SUPERUSER_ID, [order.id], {
703                 'payment_acquirer_id': acquirer_id,
704                 'payment_tx_id': request.session['sale_transaction_id']
705             }, context=context)
706
707         # confirm the quotation
708         if tx.acquirer_id.auto_confirm == 'at_pay_now':
709             request.registry['sale.order'].action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
710
711         return tx_id
712
713     @http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
714     def payment_get_status(self, sale_order_id, **post):
715         cr, uid, context = request.cr, request.uid, request.context
716
717         order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
718         assert order.id == request.session.get('sale_last_order_id')
719
720         if not order:
721             return {
722                 'state': 'error',
723                 'message': '<p>%s</p>' % _('There seems to be an error with your request.'),
724             }
725
726         tx_ids = request.registry['payment.transaction'].search(
727             cr, SUPERUSER_ID, [
728                 '|', ('sale_order_id', '=', order.id), ('reference', '=', order.name)
729             ], context=context)
730
731         if not tx_ids:
732             if order.amount_total:
733                 return {
734                     'state': 'error',
735                     'message': '<p>%s</p>' % _('There seems to be an error with your request.'),
736                 }
737             else:
738                 state = 'done'
739                 message = '<h2>%s</h2>' % _('Your order has been confirmed, thank you for your loyalty.')
740                 validation = None
741         else:
742             tx = request.registry['payment.transaction'].browse(cr, SUPERUSER_ID, tx_ids[0], context=context)
743             state = tx.state
744             message = '<h2>%s</h2>' % _('Thank you for your order.')
745             if state == 'done':
746                 message += '<p>%s</p>' % _('Your payment has been received.')
747             elif state == 'cancel':
748                 message += '<p>%s</p>' % _('The payment seems to have been canceled.')
749             elif state == 'pending' and tx.acquirer_id.validation == 'manual':
750                 message += '<p>%s</p>' % _('Your transaction is waiting confirmation.')
751                 if tx.acquirer_id.post_msg:
752                     message += tx.acquirer_id.post_msg
753             else:
754                 message += '<p>%s</p>' % _('Your transaction is waiting confirmation.')
755             validation = tx.acquirer_id.validation
756
757         return {
758             'state': state,
759             'message': message,
760             'validation': validation
761         }
762
763     @http.route('/shop/payment/validate', type='http', auth="public", website=True)
764     def payment_validate(self, transaction_id=None, sale_order_id=None, **post):
765         """ Method that should be called by the server when receiving an update
766         for a transaction. State at this point :
767
768          - UDPATE ME
769         """
770         cr, uid, context = request.cr, request.uid, request.context
771         email_act = None
772         sale_order_obj = request.registry['sale.order']
773
774         if transaction_id is None:
775             tx = request.website.sale_get_transaction()
776         else:
777             tx = request.registry['payment.transaction'].browse(cr, uid, transaction_id, context=context)
778
779         if sale_order_id is None:
780             order = request.website.sale_get_order(context=context)
781         else:
782             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
783             assert order.id == request.session.get('sale_last_order_id')
784
785         if not order or (order.amount_total and not tx):
786             return request.redirect('/shop')
787
788         if (not order.amount_total and not tx) or tx.state in ['pending', 'done']:
789             if (not order.amount_total and not tx):
790                 # Orders are confirmed by payment transactions, but there is none for free orders,
791                 # (e.g. free events), so confirm immediately
792                 order.action_button_confirm()
793             # send by email
794             email_act = sale_order_obj.action_quotation_send(cr, SUPERUSER_ID, [order.id], context=request.context)
795         elif tx and tx.state == 'cancel':
796             # cancel the quotation
797             sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context)
798
799         # send the email
800         if email_act and email_act.get('context'):
801             composer_values = {}
802             email_ctx = email_act['context']
803             public_id = request.website.user_id.id
804             if uid == public_id:
805                 composer_values['email_from'] = request.website.user_id.company_id.email
806             composer_id = request.registry['mail.compose.message'].create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
807             request.registry['mail.compose.message'].send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
808
809         # clean context and session, then redirect to the confirmation page
810         request.website.sale_reset(context=context)
811
812         return request.redirect('/shop/confirmation')
813
814     @http.route(['/shop/confirmation'], type='http', auth="public", website=True)
815     def payment_confirmation(self, **post):
816         """ End of checkout process controller. Confirmation is basically seing
817         the status of a sale.order. State at this point :
818
819          - should not have any context / session info: clean them
820          - take a sale.order id, because we request a sale.order and are not
821            session dependant anymore
822         """
823         cr, uid, context = request.cr, request.uid, request.context
824
825         sale_order_id = request.session.get('sale_last_order_id')
826         if sale_order_id:
827             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
828         else:
829             return request.redirect('/shop')
830
831         return request.website.render("website_sale.confirmation", {'order': order})
832
833     #------------------------------------------------------
834     # Edit
835     #------------------------------------------------------
836
837     @http.route(['/shop/add_product'], type='http', auth="user", methods=['POST'], website=True)
838     def add_product(self, name=None, category=0, **post):
839         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
840         if not name:
841             name = _("New Product")
842         product_obj = request.registry.get('product.product')
843         product_id = product_obj.create(cr, uid, { 'name': name, 'public_categ_ids': category }, context=context)
844         product = product_obj.browse(cr, uid, product_id, context=context)
845
846         return request.redirect("/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id))
847
848     @http.route(['/shop/change_styles'], type='json', auth="public")
849     def change_styles(self, id, style_id):
850         product_obj = request.registry.get('product.template')
851         product = product_obj.browse(request.cr, request.uid, id, context=request.context)
852
853         remove = []
854         active = False
855         for style in product.website_style_ids:
856             if style.id == style_id:
857                 remove.append(style.id)
858                 active = True
859                 break
860
861         style = request.registry.get('product.style').browse(request.cr, request.uid, style_id, context=request.context)
862
863         if remove:
864             product.write({'website_style_ids': [(3, rid) for rid in remove]})
865         if not active:
866             product.write({'website_style_ids': [(4, style.id)]})
867
868         return not active
869
870     @http.route(['/shop/change_sequence'], type='json', auth="public")
871     def change_sequence(self, id, sequence):
872         product_obj = request.registry.get('product.template')
873         if sequence == "top":
874             product_obj.set_sequence_top(request.cr, request.uid, [id], context=request.context)
875         elif sequence == "bottom":
876             product_obj.set_sequence_bottom(request.cr, request.uid, [id], context=request.context)
877         elif sequence == "up":
878             product_obj.set_sequence_up(request.cr, request.uid, [id], context=request.context)
879         elif sequence == "down":
880             product_obj.set_sequence_down(request.cr, request.uid, [id], context=request.context)
881
882     @http.route(['/shop/change_size'], type='json', auth="public")
883     def change_size(self, id, x, y):
884         product_obj = request.registry.get('product.template')
885         product = product_obj.browse(request.cr, request.uid, id, context=request.context)
886         return product.write({'website_size_x': x, 'website_size_y': y})
887
888     def order_lines_2_google_api(self, order_lines):
889         """ Transforms a list of order lines into a dict for google analytics """
890         ret = []
891         for line in order_lines:
892             ret.append({
893                 'id': line.order_id and line.order_id.id,
894                 'name': line.product_id.categ_id and line.product_id.categ_id.name or '-',
895                 'sku': line.product_id.id,
896                 'quantity': line.product_uom_qty,
897                 'price': line.price_unit,
898             })
899         return ret
900
901     @http.route(['/shop/tracking_last_order'], type='json', auth="public")
902     def tracking_cart(self, **post):
903         """ return data about order in JSON needed for google analytics"""
904         cr, uid, context = request.cr, request.uid, request.context
905         ret = {}
906         sale_order_id = request.session.get('sale_last_order_id')
907         if sale_order_id:
908             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
909             ret['transaction'] = {
910                 'id': sale_order_id,
911                 'affiliation': order.company_id.name,
912                 'revenue': order.amount_total,
913                 'currency': order.currency_id.name
914             }
915             ret['lines'] = self.order_lines_2_google_api(order.order_line)
916         return ret
917
918 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: