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