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