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