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