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