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