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