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