[MERGE] forward port of branch saas-4 up to 5087612
[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.addons.web import http
6 from openerp.addons.web.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     @http.route(['/shop',
114         '/shop/page/<int:page>',
115         '/shop/category/<model("product.public.category"):category>',
116         '/shop/category/<model("product.public.category"):category>/page/<int:page>'
117     ], type='http', auth="public", website=True)
118     def shop(self, page=0, category=None, search='', **post):
119         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
120
121         domain = request.website.sale_product_domain()
122         if search:
123             domain += ['|', '|', '|', ('name', 'ilike', search), ('description', 'ilike', search),
124                 ('description_sale', 'ilike', search), ('product_variant_ids.default_code', 'ilike', search)]
125         if category:
126             domain += [('product_variant_ids.public_categ_ids', 'child_of', int(category))]
127
128         attrib_values = [map(int,v.split(",")) for v in request.httprequest.args.getlist('attrib') if v]
129         if attrib_values:
130             attrib = None
131             ids = []
132             for value in attrib_values:
133                 if not attrib:
134                     attrib = value[0]
135                     ids.append(value[1])
136                 elif value[0] == attrib:
137                     ids.append(value[1])
138                 else:
139                     domain += [('attribute_line_ids.value_ids', 'in', ids)]
140                     attrib = value[0]
141                     ids = [value[1]]
142             if attrib:
143                 domain += [('attribute_line_ids.value_ids', 'in', ids)]
144
145         attrib_set = set([v[1] for v in attrib_values])
146         keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_set)
147
148         if not context.get('pricelist'):
149             context['pricelist'] = int(self.get_pricelist())
150         product_obj = pool.get('product.template')
151
152         product_count = product_obj.search_count(cr, uid, domain, context=context)
153         pager = request.website.pager(url="/shop", total=product_count, page=page, step=PPG, scope=7, url_args=post)
154         product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
155         products = product_obj.browse(cr, uid, product_ids, context=context)
156
157         style_obj = pool['product.style']
158         style_ids = style_obj.search(cr, uid, [], context=context)
159         styles = style_obj.browse(cr, uid, style_ids, context=context)
160
161         category_obj = pool['product.public.category']
162         category_ids = category_obj.search(cr, uid, [], context=context)
163         categories = category_obj.browse(cr, uid, category_ids, context=context)
164         categs = filter(lambda x: not x.parent_id, categories)
165
166         attributes_obj = request.registry['product.attribute']
167         attributes_ids = attributes_obj.search(cr, uid, [], context=request.context)
168         attributes = attributes_obj.browse(cr, uid, attributes_ids, context=request.context)
169
170         values = {
171             'search': search,
172             'category': category,
173             'attrib_values': attrib_values,
174             'attrib_set': attrib_set,
175             'pager': pager,
176             'pricelist': self.get_pricelist(),
177             'products': products,
178             'bins': table_compute().process(products),
179             'rows': PPR,
180             'styles': styles,
181             'categories': categs,
182             'attributes': attributes,
183             'keep': keep,
184             'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
185             'attrib_encode': lambda attribs: werkzeug.url_encode([('attrib',i) for i in attribs]),
186         }
187
188         return request.website.render("website_sale.products", values)
189
190     @http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
191     def product(self, product, category='', search='', **kwargs):
192         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
193         category_obj = pool['product.public.category']
194
195         context.update(active_id=product.id)
196
197         if category:
198             category = category_obj.browse(request.cr, request.uid, int(category), context=request.context)
199
200         attrib_values = [map(int,v.split(",")) for v in request.httprequest.args.getlist('attrib') if v]
201         attrib_set = set([v[1] for v in attrib_values])
202
203         keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_set)
204
205         category_ids = category_obj.search(cr, uid, [], context=context)
206         category_list = category_obj.name_get(cr, uid, category_ids, context=context)
207         category_list = sorted(category_list, key=lambda category: category[1])
208
209         if not context.get('pricelist'):
210             context['pricelist'] = int(self.get_pricelist())
211             product = request.registry.get('product.template').browse(request.cr, request.uid, int(product), context=context)
212
213         variants = [[p.id, map(int, p.attribute_value_ids), p.price] for p in product.product_variant_ids]
214
215         values = {
216             'search': search,
217             'category': category,
218             'pricelist': self.get_pricelist(),
219             'attrib_values': attrib_values,
220             'attrib_set': attrib_set,
221             'keep': keep,
222             'category_list': category_list,
223             'main_object': product,
224             'product': product,
225             'variants': variants,
226         }
227         return request.website.render("website_sale.product", values)
228
229     @http.route(['/shop/product/comment/<int:product_template_id>'], type='http', auth="public", methods=['POST'], website=True)
230     def product_comment(self, product_template_id, **post):
231         cr, uid, context = request.cr, request.uid, request.context
232         if post.get('comment'):
233             request.registry['product.template'].message_post(
234                 cr, uid, product_template_id,
235                 body=post.get('comment'),
236                 type='comment',
237                 subtype='mt_comment',
238                 context=dict(context, mail_create_nosubcribe=True))
239         return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
240
241     @http.route(['/shop/cart'], type='http', auth="public", website=True)
242     def cart(self, **post):
243         order = request.website.sale_get_order()
244         values = {
245             'order': order,
246             'suggested_products': [],
247         }
248         if order:
249             if not request.context.get('pricelist'):
250                 request.context['pricelist'] = order.pricelist_id.id
251             values['suggested_products'] = order._cart_accessories(context=request.context)
252         return request.website.render("website_sale.cart", values)
253
254     @http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True)
255     def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
256         cr, uid, context = request.cr, request.uid, request.context
257         request.website.sale_get_order(force_create=1)._cart_update(product_id=int(product_id), add_qty=add_qty, set_qty=set_qty)
258         return request.redirect("/shop/cart")
259
260     @http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True)
261     def cart_update_json(self, product_id, line_id, add_qty=None, set_qty=None):
262         order = request.website.sale_get_order(force_create=1)
263         quantity = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty)
264         return {
265             'quantity': quantity,
266             'cart_quantity': order.cart_quantity,
267             'website_sale.total': request.website._render("website_sale.total", {
268                     'website_sale_order': request.website.sale_get_order()
269                 })
270         }
271
272     #------------------------------------------------------
273     # Checkout
274     #------------------------------------------------------
275
276     def checkout_redirection(self, order):
277         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
278
279         # must have a draft sale order with lines at this point, otherwise reset
280         if not order or order.state != 'draft':
281             request.session['sale_order_id'] = None
282             request.session['sale_transaction_id'] = None
283             return request.redirect('/shop')
284
285         # if transaction pending / done: redirect to confirmation
286         tx = context.get('website_sale_transaction')
287         if tx and tx.state != 'draft':
288             return request.redirect('/shop/payment/confirmation/%s' % order.id)
289
290     def checkout_values(self, data=None):
291         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
292         orm_partner = registry.get('res.partner')
293         orm_user = registry.get('res.users')
294         orm_country = registry.get('res.country')
295         state_orm = registry.get('res.country.state')
296
297         country_ids = orm_country.search(cr, SUPERUSER_ID, [], context=context)
298         countries = orm_country.browse(cr, SUPERUSER_ID, country_ids, context)
299         states_ids = state_orm.search(cr, SUPERUSER_ID, [], context=context)
300         states = state_orm.browse(cr, SUPERUSER_ID, states_ids, context)
301
302         checkout = {}
303         if not data:
304             if request.uid != request.website.user_id.id:
305                 partner = orm_user.browse(cr, SUPERUSER_ID, request.uid, context).partner_id
306                 checkout.update( self.checkout_parse("billing", partner) )
307
308                 shipping_ids = orm_partner.search(cr, SUPERUSER_ID, [("parent_id", "=", partner.id), ('type', "=", 'delivery')], limit=1, context=context)
309                 if shipping_ids:
310                     shipping = orm_user.browse(cr, SUPERUSER_ID, request.uid, context)
311                     checkout.update( self.checkout_parse("shipping", shipping) )
312                     checkout['shipping_different'] = True
313             else:
314                 order = request.website.sale_get_order(force_create=1, context=context)
315                 if order.partner_id:
316                     domain = [("active", "=", False), ("partner_id", "=", order.partner_id.id)]
317                     user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID, domain, context=context)
318                     if not user_ids or request.website.user_id.id not in user_ids:
319                         checkout.update( self.checkout_parse("billing", order.partner_id) )
320         else:
321             checkout = self.checkout_parse('billing', data)
322             if data.get("shipping_different"):
323                 checkout.update(self.checkout_parse('shipping', data))
324                 checkout["shipping_different"] = True
325
326         values = {
327             'countries': countries,
328             'states': states,
329             'checkout': checkout,
330             'shipping_different': checkout.get('shipping_different'),
331             'error': {},
332         }
333         return values
334
335     mandatory_billing_fields = ["name", "phone", "email", "street", "city", "country_id", "zip"]
336     optional_billing_fields = ["street2", "state_id", "vat"]
337     mandatory_shipping_fields = ["name", "phone", "street", "city", "country_id", "zip"]
338     optional_shipping_fields = ["state_id"]
339
340     def checkout_parse(self, address_type, data, remove_prefix=False):
341         """ data is a dict OR a partner browse record
342         """
343         # set mandatory and optional fields
344         assert address_type in ('billing', 'shipping')
345         if address_type == 'billing':
346             all_fields = self.mandatory_billing_fields + self.optional_billing_fields
347             prefix = ''
348         else:
349             all_fields = self.mandatory_shipping_fields + self.optional_shipping_fields
350             prefix = 'shipping_'
351
352         # set data
353         if isinstance(data, dict):
354             query = dict((prefix + field_name, data[prefix + field_name])
355                 for field_name in all_fields if data.get(prefix + field_name))
356         else:
357             query = dict((prefix + field_name, getattr(data, field_name))
358                 for field_name in all_fields if field_name != "street2" and getattr(data, field_name))
359             if data.parent_id:
360                 query[prefix + 'street2'] = data.parent_id.name
361
362         if query.get(prefix + 'state_id'):
363             query[prefix + 'state_id'] = int(query[prefix + 'state_id'])
364         if query.get(prefix + 'country_id'):
365             query[prefix + 'country_id'] = int(query[prefix + 'country_id'])
366
367         if not remove_prefix:
368             return query
369
370         return dict((field_name, data[prefix + field_name]) for field_name in all_fields if data.get(prefix + field_name))
371
372     def checkout_form_validate(self, data):
373         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
374
375         # Validation
376         error = dict()
377         for field_name in self.mandatory_billing_fields:
378             if not data.get(field_name):
379                 error[field_name] = 'missing'
380
381         if data.get("vat") and hasattr(registry["res.partner"], "check_vat"):
382             if request.website.company_id.vat_check_vies:
383                 # force full VIES online check
384                 check_func = registry["res.partner"].vies_vat_check
385             else:
386                 # quick and partial off-line checksum validation
387                 check_func = registry["res.partner"].simple_vat_check
388             vat_country, vat_number = registry["res.partner"]._split_vat(data.get("vat"))
389             if not check_func(cr, uid, vat_country, vat_number, context=None): # simple_vat_check
390                 error["vat"] = 'error'
391
392         if data.get("shipping_different"):
393             for field_name in self.mandatory_shipping_fields:
394                 field_name = 'shipping_' + field_name
395                 if not data.get(field_name):
396                     error[field_name] = 'missing'
397
398         return error
399
400     def checkout_form_save(self, checkout):
401         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
402
403         order = request.website.sale_get_order(force_create=1, context=context)
404
405         orm_partner = registry.get('res.partner')
406         orm_user = registry.get('res.users')
407         order_obj = request.registry.get('sale.order')
408
409         billing_info = self.checkout_parse('billing', checkout, True)
410
411         # set partner_id
412         partner_id = None
413         if request.uid != request.website.user_id.id:
414             partner_id = orm_user.browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
415         elif order.partner_id:
416             user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID,
417                 [("partner_id", "=", order.partner_id.id)], context=dict(context or {}, active_test=False))
418             if not user_ids or request.website.user_id.id not in user_ids:
419                 partner_id = order.partner_id.id
420
421         # save partner informations
422         if partner_id and request.website.partner_id.id != partner_id:
423             orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context)
424         else:
425             partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context)
426
427         # create a new shipping partner
428         shipping_id = None
429         if checkout.get('shipping_different'):
430             shipping_info = self.checkout_parse('shipping', checkout, True)
431             shipping_info['type'] = 'delivery'
432             shipping_info['parent_id'] = partner_id
433             shipping_id = orm_partner.create(cr, SUPERUSER_ID, shipping_info, context)
434
435         order_info = {
436             'partner_id': partner_id,
437             'message_follower_ids': [(4, partner_id)],
438             'partner_invoice_id': partner_id,
439             'partner_shipping_id': shipping_id or partner_id
440         }
441         order_info.update(registry.get('sale.order').onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
442         order_info.pop('user_id')
443
444         order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context)
445
446     @http.route(['/shop/checkout'], type='http', auth="public", website=True)
447     def checkout(self, **post):
448         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
449
450         order = request.website.sale_get_order(force_create=1, context=context)
451
452         redirection = self.checkout_redirection(order)
453         if redirection:
454             return redirection
455
456         values = self.checkout_values()
457
458         return request.website.render("website_sale.checkout", values)
459
460     @http.route(['/shop/confirm_order'], type='http', auth="public", website=True)
461     def confirm_order(self, **post):
462         cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
463
464         order = request.website.sale_get_order(context=context)
465         if not order:
466             return request.redirect("/shop")
467
468         redirection = self.checkout_redirection(order)
469         if redirection:
470             return redirection
471
472         values = self.checkout_values(post)
473
474         values["error"] = self.checkout_form_validate(values["checkout"])
475         if values["error"]:
476             return request.website.render("website_sale.checkout", values)
477
478         self.checkout_form_save(values["checkout"])
479         request.session['sale_last_order_id'] = order.id
480
481         return request.redirect("/shop/payment")
482
483     #------------------------------------------------------
484     # Payment
485     #------------------------------------------------------
486
487     @http.route(['/shop/payment'], type='http', auth="public", website=True)
488     def payment(self, **post):
489         """ Payment step. This page proposes several payment means based on available
490         payment.acquirer. State at this point :
491
492          - a draft sale order with lines; otherwise, clean context / session and
493            back to the shop
494          - no transaction in context / session, or only a draft one, if the customer
495            did go to a payment.acquirer website but closed the tab without
496            paying / canceling
497         """
498         cr, uid, context = request.cr, request.uid, request.context
499         payment_obj = request.registry.get('payment.acquirer')
500
501         order = request.website.sale_get_order(context=context)
502
503         redirection = self.checkout_redirection(order)
504         if redirection:
505             return redirection
506
507         shipping_partner_id = False
508         if order:
509             if order.partner_shipping_id.id:
510                 shipping_partner_id = order.partner_shipping_id.id
511             else:
512                 shipping_partner_id = order.partner_invoice_id.id
513
514         values = {
515             'order': request.registry['sale.order'].browse(cr, SUPERUSER_ID, order.id, context=context)
516         }
517         values.update(request.registry.get('sale.order')._get_website_data(cr, uid, order, context))
518
519         # fetch all registered payment means
520         # if tx:
521         #     acquirer_ids = [tx.acquirer_id.id]
522         # else:
523         acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True)], context=context)
524         values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context)
525         render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now')
526         for acquirer in values['acquirers']:
527             acquirer.button = payment_obj.render(
528                 cr, SUPERUSER_ID, acquirer.id,
529                 order.name,
530                 order.amount_total,
531                 order.pricelist_id.currency_id.id,
532                 partner_id=shipping_partner_id,
533                 tx_values={
534                     'return_url': '/shop/payment/validate',
535                 },
536                 context=render_ctx)
537
538         return request.website.render("website_sale.payment", values)
539
540     @http.route(['/shop/payment/transaction/<int:acquirer_id>'], type='json', auth="public", website=True)
541     def payment_transaction(self, acquirer_id):
542         """ Json method that creates a payment.transaction, used to create a
543         transaction when the user clicks on 'pay now' button. After having
544         created the transaction, the event continues and the user is redirected
545         to the acquirer website.
546
547         :param int acquirer_id: id of a payment.acquirer record. If not set the
548                                 user is redirected to the checkout page
549         """
550         cr, uid, context = request.cr, request.uid, request.context
551         transaction_obj = request.registry.get('payment.transaction')
552         sale_order_obj = request.registry['sale.order']
553         order = request.website.sale_get_order(context=context)
554
555         if not order or not order.order_line or acquirer_id is None:
556             return request.redirect("/shop/checkout")
557
558         assert order.partner_id.id != request.website.partner_id.id
559
560         # find an already existing transaction
561         tx = request.website.sale_get_transaction()
562         if tx:
563             if tx.state == 'draft':  # button cliked but no more info -> rewrite on tx or create a new one ?
564                 tx.write({
565                     'acquirer_id': acquirer_id,
566                 })
567             tx_id = tx.id
568         else:
569             tx_id = transaction_obj.create(cr, SUPERUSER_ID, {
570                 'acquirer_id': acquirer_id,
571                 'type': 'form',
572                 'amount': order.amount_total,
573                 'currency_id': order.pricelist_id.currency_id.id,
574                 'partner_id': order.partner_id.id,
575                 'partner_country_id': order.partner_id.country_id.id,
576                 'reference': order.name,
577                 'sale_order_id': order.id,
578             }, context=context)
579             request.session['sale_transaction_id'] = tx_id
580
581         # update quotation
582         sale_order_obj.write(
583             cr, SUPERUSER_ID, [order.id], {
584                 'payment_acquirer_id': acquirer_id,
585                 'payment_tx_id': request.session['sale_transaction_id']
586             }, context=context)
587         # confirm the quotation
588         sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
589
590         return tx_id
591
592     @http.route('/shop/payment/get_status/<int:sale_order_id>', type='json', auth="public", website=True)
593     def payment_get_status(self, sale_order_id, **post):
594         cr, uid, context = request.cr, request.uid, request.context
595
596         order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
597         assert order.id == request.session.get('sale_last_order_id')
598
599         if not order:
600             return {
601                 'state': 'error',
602                 'message': '<p>There seems to be an error with your request.</p>',
603             }
604
605         tx_ids = request.registry['payment.transaction'].search(
606             cr, uid, [
607                 '|', ('sale_order_id', '=', order.id), ('reference', '=', order.name)
608             ], context=context)
609
610         if not tx_ids:
611             if order.amount_total:
612                 return {
613                     'state': 'error',
614                     'message': '<p>There seems to be an error with your request.</p>',
615                 }
616             else:
617                 state = 'done'
618                 message = ""
619                 validation = None
620         else:
621             tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
622             state = tx.state
623             if state == 'done':
624                 message = '<p>Your payment has been received.</p>'
625             elif state == 'cancel':
626                 message = '<p>The payment seems to have been canceled.</p>'
627             elif state == 'pending' and tx.acquirer_id.validation == 'manual':
628                 message = '<p>Your transaction is waiting confirmation.</p>'
629                 if tx.acquirer_id.post_msg:
630                     message += tx.acquirer_id.post_msg
631             else:
632                 message = '<p>Your transaction is waiting confirmation.</p>'
633             validation = tx.acquirer_id.validation
634
635         return {
636             'state': state,
637             'message': message,
638             'validation': validation
639         }
640
641     @http.route('/shop/payment/validate', type='http', auth="public", website=True)
642     def payment_validate(self, transaction_id=None, sale_order_id=None, **post):
643         """ Method that should be called by the server when receiving an update
644         for a transaction. State at this point :
645
646          - UDPATE ME
647         """
648         cr, uid, context = request.cr, request.uid, request.context
649         email_act = None
650         sale_order_obj = request.registry['sale.order']
651
652         if transaction_id is None:
653             tx = request.website.sale_get_transaction()
654         else:
655             tx = request.registry['payment.transaction'].browse(cr, uid, transaction_id, context=context)
656
657         if sale_order_id is None:
658             order = request.website.sale_get_order(context=context)
659         else:
660             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
661             assert order.id == request.session.get('sale_last_order_id')
662
663         if not tx or not order:
664             return request.redirect('/shop')
665
666         if not order.amount_total or tx.state in ['pending', 'done']:
667             # send by email
668             email_act = sale_order_obj.action_quotation_send(cr, SUPERUSER_ID, [order.id], context=request.context)
669         elif tx.state == 'cancel':
670             # cancel the quotation
671             sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context)
672
673         # send the email
674         if email_act and email_act.get('context'):
675             composer_values = {}
676             email_ctx = email_act['context']
677             public_id = request.website.user_id.id
678             if uid == public_id:
679                 composer_values['email_from'] = request.website.user_id.company_id.email
680             composer_id = request.registry['mail.compose.message'].create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
681             request.registry['mail.compose.message'].send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
682
683         # clean context and session, then redirect to the confirmation page
684         request.website.sale_reset(context=context)
685
686         return request.redirect('/shop/confirmation')
687
688     @http.route(['/shop/confirmation'], type='http', auth="public", website=True)
689     def payment_confirmation(self, **post):
690         """ End of checkout process controller. Confirmation is basically seing
691         the status of a sale.order. State at this point :
692
693          - should not have any context / session info: clean them
694          - take a sale.order id, because we request a sale.order and are not
695            session dependant anymore
696         """
697         cr, uid, context = request.cr, request.uid, request.context
698
699         sale_order_id = request.session.get('sale_last_order_id')
700         if sale_order_id:
701             order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
702         else:
703             return request.redirect('/shop')
704
705         return request.website.render("website_sale.confirmation", {'order': order})
706
707     #------------------------------------------------------
708     # Edit
709     #------------------------------------------------------
710
711     @http.route(['/shop/add_product'], type='http', auth="user", methods=['POST'], website=True)
712     def add_product(self, name=None, category=0, **post):
713         cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
714         if not name:
715             name = _("New Product")
716         product_obj = request.registry.get('product.product')
717         product_id = product_obj.create(cr, uid, { 'name': name, 'public_categ_ids': category }, context=context)
718         product = product_obj.browse(cr, uid, product_id, context=context)
719
720         return request.redirect("/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id))
721
722     @http.route(['/shop/change_styles'], type='json', auth="public")
723     def change_styles(self, id, style_id):
724         product_obj = request.registry.get('product.template')
725         product = product_obj.browse(request.cr, request.uid, id, context=request.context)
726
727         remove = []
728         active = False
729         for style in product.website_style_ids:
730             if style.id == style_id:
731                 remove.append(style.id)
732                 active = True
733                 break
734
735         style = request.registry.get('product.style').browse(request.cr, request.uid, style_id, context=request.context)
736
737         if remove:
738             product.write({'website_style_ids': [(3, rid) for rid in remove]})
739         if not active:
740             product.write({'website_style_ids': [(4, style.id)]})
741
742         return not active
743
744     @http.route(['/shop/change_sequence'], type='json', auth="public")
745     def change_sequence(self, id, sequence):
746         product_obj = request.registry.get('product.template')
747         if sequence == "top":
748             product_obj.set_sequence_top(request.cr, request.uid, [id], context=request.context)
749         elif sequence == "bottom":
750             product_obj.set_sequence_bottom(request.cr, request.uid, [id], context=request.context)
751         elif sequence == "up":
752             product_obj.set_sequence_up(request.cr, request.uid, [id], context=request.context)
753         elif sequence == "down":
754             product_obj.set_sequence_down(request.cr, request.uid, [id], context=request.context)
755
756     @http.route(['/shop/change_size'], type='json', auth="public")
757     def change_size(self, id, x, y):
758         product_obj = request.registry.get('product.template')
759         product = product_obj.browse(request.cr, request.uid, id, context=request.context)
760         return product.write({'website_size_x': x, 'website_size_y': y})
761
762
763 # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: