a5ef98102f489ffe65ff8f20b1a1d8a4bcbef390
[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         if not checkout.get('country_id'):
410             country_code = request.session['geoip'].get('country_code')
411             if country_code:
412                 country_ids = request.registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context)
413                 if country_ids:
414                     checkout['country_id'] = country_ids[0]
415
416         values = {
417             'countries': countries,
418             'states': states,
419             'checkout': checkout,
420             'shipping_id': partner.id != shipping_id and shipping_id or 0,
421             'shippings': shippings,
422             'error': {},
423             'has_check_vat': hasattr(registry['res.partner'], 'check_vat')
424         }
425
426         return values
427
428     mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id", "zip"]
429     optional_billing_fields = ["street", "state_id", "vat"]
430     mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id", "zip"]
431     optional_shipping_fields = ["state_id"]
432
433     def checkout_parse(self, address_type, data, remove_prefix=False):
434         """ data is a dict OR a partner browse record
435         """
436         # set mandatory and optional fields
437         assert address_type in ('billing', 'shipping')
438         if address_type == 'billing':
439             all_fields = self.mandatory_billing_fields + self.optional_billing_fields
440             prefix = ''
441         else:
442             all_fields = self.mandatory_shipping_fields + self.optional_shipping_fields
443             prefix = 'shipping_'
444
445         # set data
446         if isinstance(data, dict):
447             query = dict((prefix + field_name, data[prefix + field_name])
448                 for field_name in all_fields if data.get(prefix + field_name))
449         else:
450             query = dict((prefix + field_name, getattr(data, field_name))
451                 for field_name in all_fields if getattr(data, field_name))
452             if data.parent_id:
453                 query[prefix + 'street'] = data.parent_id.name
454
455         if query.get(prefix + 'state_id'):
456             query[prefix + 'state_id'] = int(query[prefix + 'state_id'])
457         if query.get(prefix + 'country_id'):
458             query[prefix + 'country_id'] = int(query[prefix + 'country_id'])
459
460         if not remove_prefix:
461             return query
462
463         return dict((field_name, data[prefix + field_name]) for field_name in all_fields if data.get(prefix + field_name))
464
465     def checkout_form_validate(self, data):
466         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
467
468         # Validation
469         error = dict()
470         for field_name in self.mandatory_billing_fields:
471             if not data.get(field_name):
472                 error[field_name] = 'missing'
473
474         if data.get("vat") and hasattr(registry["res.partner"], "check_vat"):
475             if request.website.company_id.vat_check_vies:
476                 # force full VIES online check
477                 check_func = registry["res.partner"].vies_vat_check
478             else:
479                 # quick and partial off-line checksum validation
480                 check_func = registry["res.partner"].simple_vat_check
481             vat_country, vat_number = registry["res.partner"]._split_vat(data.get("vat"))
482             if not check_func(cr, uid, vat_country, vat_number, context=None): # simple_vat_check
483                 error["vat"] = 'error'
484
485         if data.get("shipping_id") == -1:
486             for field_name in self.mandatory_shipping_fields:
487                 field_name = 'shipping_' + field_name
488                 if not data.get(field_name):
489                     error[field_name] = 'missing'
490
491         return error
492
493     def checkout_form_save(self, checkout):
494         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
495
496         order = request.website.sale_get_order(force_create=1, context=context)
497
498         orm_partner = registry.get('res.partner')
499         orm_user = registry.get('res.users')
500         order_obj = request.registry.get('sale.order')
501
502         billing_info = self.checkout_parse('billing', checkout, True)
503
504         # set partner_id
505         partner_id = None
506         if request.uid != request.website.user_id.id:
507             partner_id = orm_user.browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
508         elif order.partner_id:
509             user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID,
510                 [("partner_id", "=", order.partner_id.id)], context=dict(context or {}, active_test=False))
511             if not user_ids or request.website.user_id.id not in user_ids:
512                 partner_id = order.partner_id.id
513
514         # save partner informations
515         if partner_id and request.website.partner_id.id != partner_id:
516             orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context)
517         else:
518             # create partner
519             partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context)
520
521         # create a new shipping partner
522         if checkout.get('shipping_id') == -1:
523             shipping_info = self.checkout_parse('shipping', checkout, True)
524             shipping_info['type'] = 'delivery'
525             shipping_info['parent_id'] = partner_id
526             checkout['shipping_id'] = orm_partner.create(cr, SUPERUSER_ID, shipping_info, context)
527
528         order_info = {
529             'partner_id': partner_id,
530             'message_follower_ids': [(4, partner_id), (3, request.website.partner_id.id)],
531             'partner_invoice_id': partner_id,
532         }
533         order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
534         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'])
535
536         order_info.pop('user_id')
537         order_info.update(partner_shipping_id=checkout.get('shipping_id') or partner_id)
538
539         order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context)
540
541     @http.route(['/shop/checkout'], type='http', auth="public", website=True)
542     def checkout(self, **post):
543         cr, uid, context = request.cr, request.uid, request.context
544
545         order = request.website.sale_get_order(force_create=1, context=context)
546
547         redirection = self.checkout_redirection(order)
548         if redirection:
549             return redirection
550
551         values = self.checkout_values()
552
553         return request.website.render("website_sale.checkout", values)
554
555     @http.route(['/shop/confirm_order'], type='http', auth="public", website=True)
556     def confirm_order(self, **post):
557         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
558
559         order = request.website.sale_get_order(context=context)
560         if not order:
561             return request.redirect("/shop")
562
563         redirection = self.checkout_redirection(order)
564         if redirection:
565             return redirection
566
567         values = self.checkout_values(post)
568
569         values["error"] = self.checkout_form_validate(values["checkout"])
570         if values["error"]:
571             return request.website.render("website_sale.checkout", values)
572
573         self.checkout_form_save(values["checkout"])
574
575         request.session['sale_last_order_id'] = order.id
576
577         request.website.sale_get_order(update_pricelist=True, context=context)
578
579         return request.redirect("/shop/payment")
580
581     #------------------------------------------------------
582     # Payment
583     #------------------------------------------------------
584
585     @http.route(['/shop/payment'], type='http', auth="public", website=True)
586     def payment(self, **post):
587         """ Payment step. This page proposes several payment means based on available
588         payment.acquirer. State at this point :
589
590          - a draft sale order with lines; otherwise, clean context / session and
591            back to the shop
592          - no transaction in context / session, or only a draft one, if the customer
593            did go to a payment.acquirer website but closed the tab without
594            paying / canceling
595         """
596         cr, uid, context = request.cr, request.uid, request.context
597         payment_obj = request.registry.get('payment.acquirer')
598
599         order = request.website.sale_get_order(context=context)
600
601         redirection = self.checkout_redirection(order)
602         if redirection:
603             return redirection
604
605         shipping_partner_id = False
606         if order:
607             if order.partner_shipping_id.id:
608                 shipping_partner_id = order.partner_shipping_id.id
609             else:
610                 shipping_partner_id = order.partner_invoice_id.id
611
612         values = {
613             'order': request.registry['sale.order'].browse(cr, SUPERUSER_ID, order.id, context=context)
614         }
615         values.update(request.registry.get('sale.order')._get_website_data(cr, uid, order, context))
616
617         # fetch all registered payment means
618         # if tx:
619         #     acquirer_ids = [tx.acquirer_id.id]
620         # else:
621         acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True), ('company_id', '=', order.company_id.id)], context=context)
622         values['acquirers'] = list(payment_obj.browse(cr, uid, acquirer_ids, context=context))
623         render_ctx = dict(context, submit_class='btn btn-primary', submit_txt=_('Pay Now'))
624         for acquirer in values['acquirers']:
625             acquirer.button = payment_obj.render(
626                 cr, SUPERUSER_ID, acquirer.id,
627                 order.name,
628                 order.amount_total,
629                 order.pricelist_id.currency_id.id,
630                 partner_id=shipping_partner_id,
631                 tx_values={
632                     'return_url': '/shop/payment/validate',
633                 },
634                 context=render_ctx)
635
636         return request.website.render("website_sale.payment", values)
637
638     @http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
639     def payment_transaction(self, acquirer_id):
640         """ Json method that creates a payment.transaction, used to create a
641         transaction when the user clicks on 'pay now' button. After having
642         created the transaction, the event continues and the user is redirected
643         to the acquirer website.
644
645         :param int acquirer_id: id of a payment.acquirer record. If not set the
646                                 user is redirected to the checkout page
647         """
648         cr, uid, context = request.cr, request.uid, request.context
649         transaction_obj = request.registry.get('payment.transaction')
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         request.registry['sale.order'].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
685         return tx_id
686
687     @http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
688     def payment_get_status(self, sale_order_id, **post):
689         cr, uid, context = request.cr, request.uid, request.context
690
691         order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
692         assert order.id == request.session.get('sale_last_order_id')
693
694         if not order:
695             return {
696                 'state': 'error',
697                 'message': '<p>%s</p>' % _('There seems to be an error with your request.'),
698             }
699
700         tx_ids = request.registry['payment.transaction'].search(
701             cr, SUPERUSER_ID, [
702                 '|', ('sale_order_id', '=', order.id), ('reference', '=', order.name)
703             ], context=context)
704
705         if not tx_ids:
706             if order.amount_total:
707                 return {
708                     'state': 'error',
709                     'message': '<p>%s</p>' % _('There seems to be an error with your request.'),
710                 }
711             else:
712                 state = 'done'
713                 message = ""
714                 validation = None
715         else:
716             tx = request.registry['payment.transaction'].browse(cr, SUPERUSER_ID, tx_ids[0], context=context)
717             state = tx.state
718             if state == 'done':
719                 message = '<p>%s</p>' % _('Your payment has been received.')
720             elif state == 'cancel':
721                 message = '<p>%s</p>' % _('The payment seems to have been canceled.')
722             elif state == 'pending' and tx.acquirer_id.validation == 'manual':
723                 message = '<p>%s</p>' % _('Your transaction is waiting confirmation.')
724                 if tx.acquirer_id.post_msg:
725                     message += tx.acquirer_id.post_msg
726             else:
727                 message = '<p>%s</p>' % _('Your transaction is waiting confirmation.')
728             validation = tx.acquirer_id.validation
729
730         return {
731             'state': state,
732             'message': message,
733             'validation': validation
734         }
735
736     @http.route('/shop/payment/validate', type='http', auth="public", website=True)
737     def payment_validate(self, transaction_id=None, sale_order_id=None, **post):
738         """ Method that should be called by the server when receiving an update
739         for a transaction. State at this point :
740
741          - UDPATE ME
742         """
743         cr, uid, context = request.cr, request.uid, request.context
744         email_act = None
745         sale_order_obj = request.registry['sale.order']
746
747         if transaction_id is None:
748             tx = request.website.sale_get_transaction()
749         else:
750             tx = request.registry['payment.transaction'].browse(cr, uid, transaction_id, context=context)
751
752         if sale_order_id is None:
753             order = request.website.sale_get_order(context=context)
754         else:
755             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
756             assert order.id == request.session.get('sale_last_order_id')
757
758         if not order or (order.amount_total and not tx):
759             return request.redirect('/shop')
760
761         if (not order.amount_total and not tx) or tx.state in ['pending', 'done']:
762             if (not order.amount_total and not tx):
763                 # Orders are confirmed by payment transactions, but there is none for free orders,
764                 # (e.g. free events), so confirm immediately
765                 order.action_button_confirm()
766             # send by email
767             email_act = sale_order_obj.action_quotation_send(cr, SUPERUSER_ID, [order.id], context=request.context)
768         elif tx and tx.state == 'cancel':
769             # cancel the quotation
770             sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context)
771
772         # send the email
773         if email_act and email_act.get('context'):
774             composer_values = {}
775             email_ctx = email_act['context']
776             public_id = request.website.user_id.id
777             if uid == public_id:
778                 composer_values['email_from'] = request.website.user_id.company_id.email
779             composer_id = request.registry['mail.compose.message'].create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
780             request.registry['mail.compose.message'].send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
781
782         # clean context and session, then redirect to the confirmation page
783         request.website.sale_reset(context=context)
784
785         return request.redirect('/shop/confirmation')
786
787     @http.route(['/shop/confirmation'], type='http', auth="public", website=True)
788     def payment_confirmation(self, **post):
789         """ End of checkout process controller. Confirmation is basically seing
790         the status of a sale.order. State at this point :
791
792          - should not have any context / session info: clean them
793          - take a sale.order id, because we request a sale.order and are not
794            session dependant anymore
795         """
796         cr, uid, context = request.cr, request.uid, request.context
797
798         sale_order_id = request.session.get('sale_last_order_id')
799         if sale_order_id:
800             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
801         else:
802             return request.redirect('/shop')
803
804         return request.website.render("website_sale.confirmation", {'order': order})
805
806     #------------------------------------------------------
807     # Edit
808     #------------------------------------------------------
809
810     @http.route(['/shop/add_product'], type='http', auth="user", methods=['POST'], website=True)
811     def add_product(self, name=None, category=0, **post):
812         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
813         if not name:
814             name = _("New Product")
815         product_obj = request.registry.get('product.product')
816         product_id = product_obj.create(cr, uid, { 'name': name, 'public_categ_ids': category }, context=context)
817         product = product_obj.browse(cr, uid, product_id, context=context)
818
819         return request.redirect("/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id))
820
821     @http.route(['/shop/change_styles'], type='json', auth="public")
822     def change_styles(self, id, style_id):
823         product_obj = request.registry.get('product.template')
824         product = product_obj.browse(request.cr, request.uid, id, context=request.context)
825
826         remove = []
827         active = False
828         for style in product.website_style_ids:
829             if style.id == style_id:
830                 remove.append(style.id)
831                 active = True
832                 break
833
834         style = request.registry.get('product.style').browse(request.cr, request.uid, style_id, context=request.context)
835
836         if remove:
837             product.write({'website_style_ids': [(3, rid) for rid in remove]})
838         if not active:
839             product.write({'website_style_ids': [(4, style.id)]})
840
841         return not active
842
843     @http.route(['/shop/change_sequence'], type='json', auth="public")
844     def change_sequence(self, id, sequence):
845         product_obj = request.registry.get('product.template')
846         if sequence == "top":
847             product_obj.set_sequence_top(request.cr, request.uid, [id], context=request.context)
848         elif sequence == "bottom":
849             product_obj.set_sequence_bottom(request.cr, request.uid, [id], context=request.context)
850         elif sequence == "up":
851             product_obj.set_sequence_up(request.cr, request.uid, [id], context=request.context)
852         elif sequence == "down":
853             product_obj.set_sequence_down(request.cr, request.uid, [id], context=request.context)
854
855     @http.route(['/shop/change_size'], type='json', auth="public")
856     def change_size(self, id, x, y):
857         product_obj = request.registry.get('product.template')
858         product = product_obj.browse(request.cr, request.uid, id, context=request.context)
859         return product.write({'website_size_x': x, 'website_size_y': y})
860
861     def order_lines_2_google_api(self, order_lines):
862         """ Transforms a list of order lines into a dict for google analytics """
863         ret = []
864         for line in order_lines:
865             ret.append({
866                 'id': line.order_id and line.order_id.id,
867                 'name': line.product_id.categ_id and line.product_id.categ_id.name or '-',
868                 'sku': line.product_id.id,
869                 'quantity': line.product_uom_qty,
870                 'price': line.price_unit,
871             })
872         return ret
873
874     @http.route(['/shop/tracking_last_order'], type='json', auth="public")
875     def tracking_cart(self, **post):
876         """ return data about order in JSON needed for google analytics"""
877         cr, uid, context = request.cr, request.uid, request.context
878         ret = {}
879         sale_order_id = request.session.get('sale_last_order_id')
880         if sale_order_id:
881             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
882             ret['transaction'] = {
883                 'id': sale_order_id,
884                 'affiliation': order.company_id.name,
885                 'revenue': order.amount_total,
886                 'currency': order.currency_id.name
887             }
888             ret['lines'] = self.order_lines_2_google_api(order.order_line)
889         return ret
890
891 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: