-
Notifications
You must be signed in to change notification settings - Fork 23.3k
/
website.py
654 lines (558 loc) · 28.9 KB
/
website.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, _lt, SUPERUSER_ID, api, fields, models, tools
from odoo.http import request
from odoo.osv import expression
from odoo.addons.http_routing.models.ir_http import url_for
class Website(models.Model):
_inherit = 'website'
def _default_salesteam_id(self):
team = self.env.ref('sales_team.salesteam_website_sales', False)
if team and team.active:
return team.id
else:
return None
salesperson_id = fields.Many2one('res.users', string='Salesperson')
salesteam_id = fields.Many2one('crm.team',
string='Sales Team', ondelete="set null",
default=_default_salesteam_id)
show_line_subtotals_tax_selection = fields.Selection(
selection=[
('tax_excluded', "Tax Excluded"),
('tax_included', "Tax Included"),
],
string="Line Subtotals Tax Display",
required=True, default='tax_excluded',
)
fiscal_position_id = fields.Many2one(
'account.fiscal.position', compute='_compute_fiscal_position_id')
pricelist_id = fields.Many2one(
'product.pricelist', compute='_compute_pricelist_id', string="Default Pricelist if any")
currency_id = fields.Many2one(
'res.currency', compute='_compute_currency_id', string="Default Currency")
pricelist_ids = fields.One2many('product.pricelist', compute="_compute_pricelist_ids",
string='Price list available for this Ecommerce/Website')
# Technical: Used to recompute pricelist_ids
all_pricelist_ids = fields.One2many('product.pricelist', 'website_id', string='All pricelists')
def _default_recovery_mail_template(self):
try:
return self.env.ref('website_sale.mail_template_sale_cart_recovery').id
except ValueError:
return False
cart_recovery_mail_template_id = fields.Many2one('mail.template', string='Cart Recovery Email', default=_default_recovery_mail_template, domain="[('model', '=', 'sale.order')]")
cart_abandoned_delay = fields.Float(string="Abandoned Delay", default=10.0)
send_abandoned_cart_email = fields.Boolean(string="Send email to customers who abandoned their cart.")
shop_ppg = fields.Integer(default=20, string="Number of products in the grid on the shop")
shop_ppr = fields.Integer(default=4, string="Number of grid columns on the shop")
@staticmethod
def _get_product_sort_mapping():
return [
('website_sequence asc', _('Featured')),
('create_date desc', _('Newest Arrivals')),
('name asc', _('Name (A-Z)')),
('list_price asc', _('Price - Low to High')),
('list_price desc', _('Price - High to Low')),
]
shop_default_sort = fields.Selection(selection='_get_product_sort_mapping', default='website_sequence asc', required=True)
shop_extra_field_ids = fields.One2many('website.sale.extra.field', 'website_id', string='E-Commerce Extra Fields')
add_to_cart_action = fields.Selection(
selection=[
('stay', 'Stay on Product Page'),
('go_to_cart', 'Go to cart'),
],
default='stay')
auth_signup_uninvited = fields.Selection(default='b2c')
account_on_checkout = fields.Selection(
string="Customer Accounts",
selection=[
('optional', 'Optional'),
('disabled', 'Disabled (buy as guest)'),
('mandatory', 'Mandatory (no guest checkout)'),
],
default='optional')
product_page_image_layout = fields.Selection([
('carousel', 'Carousel'),
('grid', 'Grid'),
], default='carousel', required=True,
)
product_page_grid_columns = fields.Integer(default=2)
product_page_image_width = fields.Selection([
('none', 'Hidden'),
('50_pc', '50 %'),
('66_pc', '66 %'),
('100_pc', '100 %'),
], default='50_pc', required=True,
)
product_page_image_spacing = fields.Selection([
('none', 'None'),
('small', 'Small'),
('medium', 'Medium'),
('big', 'Big'),
], default='small', required=True,
)
prevent_zero_price_sale = fields.Boolean(string="Hide 'Add To Cart' when price = 0")
prevent_zero_price_sale_text = fields.Char(string="Text to show instead of price", translate=True,
default="Not Available For Sale")
contact_us_button_url = fields.Char(string="Contact Us Button URL", translate=True, default="/contactus")
enabled_portal_reorder_button = fields.Boolean(string="Re-order From Portal")
enabled_delivery = fields.Boolean(string="Enable Shipping", compute='_compute_enabled_delivery')
@api.depends('all_pricelist_ids')
def _compute_pricelist_ids(self):
for website in self:
website = website.with_company(website.company_id)
ProductPricelist = website.env['product.pricelist'] # with correct company in env
website.pricelist_ids = ProductPricelist.sudo().search(
ProductPricelist._get_website_pricelists_domain(website)
)
def _compute_pricelist_id(self):
for website in self:
website.pricelist_id = website._get_current_pricelist()
def _compute_fiscal_position_id(self):
for website in self:
website.fiscal_position_id = website._get_current_fiscal_position()
@api.depends('all_pricelist_ids', 'pricelist_id', 'company_id')
def _compute_currency_id(self):
for website in self:
website.currency_id = website.pricelist_id.currency_id or website.company_id.currency_id
def _compute_enabled_delivery(self):
for website in self:
website.enabled_delivery = bool(website.env['delivery.carrier'].sudo().search_count(
[('website_id', 'in', (False, website.id)), ('is_published', '=', True)], limit=1
))
# This method is cached, must not return records! See also #8795
@tools.ormcache(
'country_code', 'show_visible',
'current_pl_id', 'website_pricelist_ids',
'partner_pl_id', 'order_pl_id',
)
def _get_pl_partner_order(
self, country_code, show_visible, current_pl_id, website_pricelist_ids,
partner_pl_id=False, order_pl_id=False
):
""" Return the list of pricelists that can be used on website for the current user.
:param str country_code: code iso or False, If set, we search only price list available for this country
:param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
:param int current_pl_id: The current pricelist used on the website
(If not selectable but currently used anyway, e.g. pricelist with promo code)
:param tuple website_pricelist_ids: List of ids of pricelists available for this website
:param int partner_pl_id: the partner pricelist
:param int order_pl_id: the current cart pricelist
:returns: list of product.pricelist ids
:rtype: list
"""
self.ensure_one()
pricelists = self.env['product.pricelist']
if show_visible:
# Only show selectable or currently used pricelist (cart or session)
check_pricelist = lambda pl: pl.selectable or pl.id in (current_pl_id, order_pl_id)
else:
check_pricelist = lambda _pl: True
# Note: 1. pricelists from all_pl are already website compliant (went through
# `_get_website_pricelists_domain`)
# 2. do not read `property_product_pricelist` here as `_get_pl_partner_order`
# is cached and the result of this method will be impacted by that field value.
# Pass it through `partner_pl_id` parameter instead to invalidate the cache.
# If there is a GeoIP country, find a pricelist for it
if country_code:
pricelists |= self.env['res.country.group'].search(
[('country_ids.code', '=', country_code)]
).pricelist_ids.filtered(
lambda pl: pl._is_available_on_website(self) and check_pricelist(pl)
)
# no GeoIP or no pricelist for this country
if not pricelists:
pricelists = pricelists.browse(website_pricelist_ids).filtered(check_pricelist)
# if logged in, add partner pl (which is `property_product_pricelist`, might not be website compliant)
if not self.env.user._is_public():
# keep partner_pricelist only if website compliant
partner_pricelist = pricelists.browse(partner_pl_id).filtered(
lambda pl:
pl._is_available_on_website(self)
and check_pricelist(pl)
and pl._is_available_in_country(country_code)
)
pricelists |= partner_pricelist
# This method is cached, must not return records! See also #8795
return pricelists.ids
def get_pricelist_available(self, show_visible=False):
""" Return the list of pricelists that can be used on website for the current user.
Country restrictions will be detected with GeoIP (if installed).
:param bool show_visible: if True, we don't display pricelist where selectable is False (Eg: Code promo)
:returns: pricelist recordset
"""
self.ensure_one()
country_code = self._get_geoip_country_code()
website = self.with_company(self.company_id)
partner_sudo = website.env.user.partner_id
is_user_public = self.env.user._is_public()
if not is_user_public:
last_order_pricelist = partner_sudo.last_website_so_id.pricelist_id
partner_pricelist = partner_sudo.property_product_pricelist
else: # public user: do not compute partner pl (not used)
last_order_pricelist = self.env['product.pricelist']
partner_pricelist = self.env['product.pricelist']
website_pricelists = website.sudo().pricelist_ids
current_pricelist_id = self._get_cached_pricelist_id()
pricelist_ids = website._get_pl_partner_order(
country_code,
show_visible,
current_pl_id=current_pricelist_id,
website_pricelist_ids=tuple(website_pricelists.ids),
partner_pl_id=partner_pricelist.id,
order_pl_id=last_order_pricelist.id)
return self.env['product.pricelist'].browse(pricelist_ids)
def is_pricelist_available(self, pl_id):
""" Return a boolean to specify if a specific pricelist can be manually set on the website.
Warning: It check only if pricelist is in the 'selectable' pricelists or the current pricelist.
:param int pl_id: The pricelist id to check
:returns: Boolean, True if valid / available
"""
return pl_id in self.get_pricelist_available(show_visible=False).ids
def _get_geoip_country_code(self):
return request and request.geoip.country_code or False
def _get_cached_pricelist_id(self):
return request and request.session.get('website_sale_current_pl') or None
def _get_current_pricelist(self):
"""
:returns: The current pricelist record
"""
self = self.with_company(self.company_id)
ProductPricelist = self.env['product.pricelist']
pricelist = ProductPricelist
if request and request.session.get('website_sale_current_pl'):
# `website_sale_current_pl` is set only if the user specifically chose it:
# - Either, he chose it from the pricelist selection
# - Either, he entered a coupon code
pricelist = ProductPricelist.browse(request.session['website_sale_current_pl']).exists().sudo()
country_code = self._get_geoip_country_code()
if not pricelist or not pricelist._is_available_on_website(self) or not pricelist._is_available_in_country(country_code):
request.session.pop('website_sale_current_pl')
pricelist = ProductPricelist
if not pricelist:
partner_sudo = self.env.user.partner_id
# If the user has a saved cart, it take the pricelist of this last unconfirmed cart
pricelist = partner_sudo.last_website_so_id.pricelist_id
if not pricelist:
# The pricelist of the user set on its partner form.
# If the user is not signed in, it's the public user pricelist
pricelist = partner_sudo.property_product_pricelist
# The list of available pricelists for this user.
# If the user is signed in, and has a pricelist set different than the public user pricelist
# then this pricelist will always be considered as available
available_pricelists = self.get_pricelist_available()
if available_pricelists and pricelist not in available_pricelists:
# If there is at least one pricelist in the available pricelists
# and the chosen pricelist is not within them
# it then choose the first available pricelist.
# This can only happen when the pricelist is the public user pricelist and this pricelist is not in the available pricelist for this localization
# If the user is signed in, and has a special pricelist (different than the public user pricelist),
# then this special pricelist is amongs these available pricelists, and therefore it won't fall in this case.
pricelist = available_pricelists[0]
return pricelist
def sale_product_domain(self):
website_domain = self.get_current_website().website_domain()
if not self.env.user._is_internal():
website_domain = expression.AND([website_domain, [('is_published', '=', True)]])
return expression.AND([self._product_domain(), website_domain])
def _product_domain(self):
return [('sale_ok', '=', True)]
def sale_get_order(self, force_create=False, update_pricelist=False):
""" Return the current sales order after mofications specified by params.
:param bool force_create: Create sales order if not already existing
:param bool update_pricelist: Force to recompute all the lines from sales order to adapt the price with the current pricelist.
:returns: record for the current sales order (might be empty)
:rtype: `sale.order` recordset
"""
self.ensure_one()
self = self.with_company(self.company_id)
SaleOrder = self.env['sale.order'].sudo()
sale_order_id = request.session.get('sale_order_id')
if sale_order_id:
sale_order_sudo = SaleOrder.browse(sale_order_id).exists()
elif self.env.user and not self.env.user._is_public():
sale_order_sudo = self.env.user.partner_id.last_website_so_id
if sale_order_sudo:
available_pricelists = self.get_pricelist_available()
so_pricelist_sudo = sale_order_sudo.pricelist_id
if so_pricelist_sudo and so_pricelist_sudo not in available_pricelists:
# Do not reload the cart of this user last visit
# if the cart uses a pricelist no longer available.
sale_order_sudo = SaleOrder
else:
# Do not reload the cart of this user last visit
# if the Fiscal Position has changed.
fpos = sale_order_sudo.env['account.fiscal.position'].with_company(
sale_order_sudo.company_id
)._get_fiscal_position(
sale_order_sudo.partner_id,
delivery=sale_order_sudo.partner_shipping_id
)
if fpos.id != sale_order_sudo.fiscal_position_id.id:
sale_order_sudo = SaleOrder
else:
sale_order_sudo = SaleOrder
# Ignore the current order if a payment has been initiated. We don't want to retrieve the
# cart and allow the user to update it when the payment is about to confirm it.
if sale_order_sudo and sale_order_sudo.get_portal_last_transaction().state in (
'pending', 'authorized', 'done'
):
sale_order_sudo = None
if not (sale_order_sudo or force_create):
# Do not create a SO record unless needed
if request.session.get('sale_order_id'):
request.session.pop('sale_order_id')
request.session.pop('website_sale_cart_quantity', None)
return self.env['sale.order']
# Only set when neeeded
pricelist_id = False
partner_sudo = self.env.user.partner_id
# cart creation was requested
if not sale_order_sudo:
so_data = self._prepare_sale_order_values(partner_sudo)
sale_order_sudo = SaleOrder.with_user(SUPERUSER_ID).create(so_data)
request.session['sale_order_id'] = sale_order_sudo.id
request.session['website_sale_cart_quantity'] = sale_order_sudo.cart_quantity
# The order was created with SUPERUSER_ID, revert back to request user.
sale_order_sudo = sale_order_sudo.with_user(self.env.user).sudo()
return sale_order_sudo
# Existing Cart:
# * For logged user
# * In session, for specified partner
# case when user emptied the cart
if not request.session.get('sale_order_id'):
request.session['sale_order_id'] = sale_order_sudo.id
request.session['website_sale_cart_quantity'] = sale_order_sudo.cart_quantity
# check for change of partner_id ie after signup
if sale_order_sudo.partner_id.id != partner_sudo.id and request.website.partner_id.id != partner_sudo.id:
previous_fiscal_position = sale_order_sudo.fiscal_position_id
previous_pricelist = sale_order_sudo.pricelist_id
# Reset the session pricelist according to logged partner pl
request.session.pop('website_sale_current_pl', None)
# Force recomputation of the website pricelist after reset
self.invalidate_recordset(['pricelist_id'])
pricelist_id = self.pricelist_id.id
request.session['website_sale_current_pl'] = pricelist_id
# change the partner, and trigger the computes (fpos)
sale_order_sudo.write({
'partner_id': partner_sudo.id,
'payment_term_id': self.sale_get_payment_term(partner_sudo),
# Must be specified to ensure it is not recomputed when it shouldn't
'pricelist_id': pricelist_id,
})
if sale_order_sudo.fiscal_position_id != previous_fiscal_position:
sale_order_sudo.order_line._compute_tax_id()
if sale_order_sudo.pricelist_id != previous_pricelist:
update_pricelist = True
elif update_pricelist:
# Only compute pricelist if needed
pricelist_id = self.pricelist_id.id
# update the pricelist
if update_pricelist:
request.session['website_sale_current_pl'] = pricelist_id
sale_order_sudo.write({'pricelist_id': pricelist_id})
sale_order_sudo._recompute_prices()
return sale_order_sudo
def _prepare_sale_order_values(self, partner_sudo):
self.ensure_one()
addr = partner_sudo.address_get(['delivery', 'invoice'])
if not request.website.is_public_user():
last_sale_order = self.env['sale.order'].sudo().search(
[('partner_id', '=', partner_sudo.id), ('website_id', '=', self.id)],
limit=1,
order="date_order desc, id desc",
)
if last_sale_order:
if last_sale_order.partner_shipping_id.active: # first = me
addr['delivery'] = last_sale_order.partner_shipping_id.id
if last_sale_order.partner_invoice_id.active:
addr['invoice'] = last_sale_order.partner_invoice_id.id
affiliate_id = request.session.get('affiliate_id')
salesperson_user_sudo = self.env['res.users'].sudo().browse(affiliate_id).exists()
if not salesperson_user_sudo:
salesperson_user_sudo = self.salesperson_id or partner_sudo.parent_id.user_id or partner_sudo.user_id
values = {
'company_id': self.company_id.id,
'fiscal_position_id': self.fiscal_position_id.id,
'partner_id': partner_sudo.id,
'partner_invoice_id': addr['invoice'],
'partner_shipping_id': addr['delivery'],
'pricelist_id': self.pricelist_id.id,
'payment_term_id': self.sale_get_payment_term(partner_sudo),
'team_id': self.salesteam_id.id or partner_sudo.parent_id.team_id.id or partner_sudo.team_id.id,
'user_id': salesperson_user_sudo.id,
'website_id': self.id,
}
return values
@api.model
def sale_get_payment_term(self, partner):
pt = self.env.ref('account.account_payment_term_immediate', False)
if pt:
pt = pt.sudo()
pt = (not pt.company_id.id or self.company_id.id == pt.company_id.id) and pt
return (
partner.property_payment_term_id or
pt or
self.env['account.payment.term'].sudo().search([('company_id', '=', self.company_id.id)], limit=1)
).id
def _get_current_fiscal_position(self):
AccountFiscalPosition = self.env['account.fiscal.position'].sudo()
fpos = AccountFiscalPosition
partner_sudo = self.env.user.partner_id
# If the current user is the website public user, the fiscal position
# is computed according to geolocation.
if request and request.geoip.country_code and self.partner_id.id == partner_sudo.id:
country = self.env['res.country'].search(
[('code', '=', request.geoip.country_code)],
limit=1,
)
fpos = AccountFiscalPosition._get_fpos_by_region(country.id)
if not fpos:
fpos = AccountFiscalPosition._get_fiscal_position(partner_sudo)
return fpos
def sale_reset(self):
request.session.pop('sale_order_id', None)
request.session.pop('website_sale_current_pl', None)
request.session.pop('website_sale_cart_quantity', None)
@api.model
def action_dashboard_redirect(self):
if self.env.user.has_group('sales_team.group_sale_salesman'):
return self.env["ir.actions.actions"]._for_xml_id("website.backend_dashboard")
return super(Website, self).action_dashboard_redirect()
def get_suggested_controllers(self):
suggested_controllers = super(Website, self).get_suggested_controllers()
suggested_controllers.append((_('eCommerce'), url_for('/shop'), 'website_sale'))
return suggested_controllers
def _search_get_details(self, search_type, order, options):
result = super()._search_get_details(search_type, order, options)
if search_type in ['products', 'product_categories_only', 'all']:
result.append(self.env['product.public.category']._search_get_detail(self, order, options))
if search_type in ['products', 'products_only', 'all']:
result.append(self.env['product.template']._search_get_detail(self, order, options))
return result
def _get_product_page_proportions(self):
"""
Returns the number of columns (css) that both the images and the product details should take.
"""
self.ensure_one()
return {
'none': (0, 12),
'50_pc': (6, 6),
'66_pc': (8, 4),
'100_pc': (12, 12),
}.get(self.product_page_image_width)
def _get_product_page_grid_image_classes(self):
spacing_map = {
'none': 'p-0',
'small': 'p-2',
'medium': 'p-3',
'big': 'p-4',
}
columns_map = {
1: 'col-12',
2: 'col-6',
3: 'col-4',
}
return spacing_map.get(self.product_page_image_spacing) + ' ' +\
columns_map.get(self.product_page_grid_columns)
@api.model
def _send_abandoned_cart_email(self):
for website in self.search([]):
if not website.send_abandoned_cart_email:
continue
all_abandoned_carts = self.env['sale.order'].search([
('is_abandoned_cart', '=', True),
('cart_recovery_email_sent', '=', False),
('website_id', '=', website.id),
])
if not all_abandoned_carts:
continue
abandoned_carts = all_abandoned_carts._filter_can_send_abandoned_cart_mail()
# Mark abandoned carts that failed the filter as sent to avoid rechecking them again and again.
(all_abandoned_carts - abandoned_carts).cart_recovery_email_sent = True
for sale_order in abandoned_carts:
template = self.env.ref('website_sale.mail_template_sale_cart_recovery')
template.send_mail(sale_order.id, email_values=dict(email_to=sale_order.partner_id.email))
sale_order.cart_recovery_email_sent = True
def _display_partner_b2b_fields(self):
""" This method is to be inherited by localizations and return
True if localization should always displayed b2b fields """
self.ensure_one()
return self.is_view_active('website_sale.address_b2b')
def _get_checkout_step_list(self):
""" Return an ordered list of steps according to the current template rendered.
:rtype: list
:return: A list with the following structure:
[
[xmlid],
{
'name': str,
'current_href': str,
'main_button': str,
'main_button_href': str,
'back_button': str,
'back_button_href': str
}
]
"""
self.ensure_one()
is_extra_step_active = self.viewref('website_sale.extra_info').active
redirect_to_sign_in = self.account_on_checkout == 'mandatory' and self.is_public_user()
steps = [(['website_sale.cart'], {
'name': _lt("Review Order"),
'current_href': '/shop/cart',
'main_button': _lt("Sign In") if redirect_to_sign_in else _lt("Checkout"),
'main_button_href': f'{"/web/login?redirect=" if redirect_to_sign_in else ""}/shop/checkout?express=1',
'back_button': _lt("Continue shopping"),
'back_button_href': '/shop',
}), (['website_sale.checkout', 'website_sale.address'], {
'name': _lt("Shipping"),
'current_href': '/shop/checkout',
'main_button': _lt("Confirm"),
'main_button_href': f'{"/shop/extra_info" if is_extra_step_active else "/shop/confirm_order"}',
'back_button': _lt("Back to cart"),
'back_button_href': '/shop/cart',
})]
if is_extra_step_active:
steps.append((['website_sale.extra_info'], {
'name': _lt("Extra Info"),
'current_href': '/shop/extra_info',
'main_button': _lt("Continue checkout"),
'main_button_href': '/shop/confirm_order',
'back_button': _lt("Return to shipping"),
'back_button_href': '/shop/checkout',
}))
steps.append((['website_sale.payment'], {
'name': _lt("Payment"),
'current_href': '/shop/payment',
'back_button': _lt("Back to cart"),
'back_button_href': '/shop/cart',
}))
return steps
def _get_checkout_steps(self, current_step=None):
""" Return an ordered list of steps according to the current template rendered.
If `current_step` is provided, returns only the corresponding step.
Note: self.ensure_one()
:param str current_step: The xmlid of the current step, defaults to None.
:rtype: list
:return: A list containing the steps generated by :meth:`_get_checkout_step_list`.
"""
self.ensure_one()
steps = self._get_checkout_step_list()
if current_step:
return next(step for step in steps if current_step in step[0])[1]
else:
return steps
class WebsiteSaleExtraField(models.Model):
_name = 'website.sale.extra.field'
_description = 'E-Commerce Extra Info Shown on product page'
_order = 'sequence'
website_id = fields.Many2one('website')
sequence = fields.Integer(default=10)
field_id = fields.Many2one(
'ir.model.fields',
domain=[('model_id.model', '=', 'product.template'), ('ttype', 'in', ['char', 'binary'])],
required=True,
ondelete='cascade'
)
label = fields.Char(related='field_id.field_description')
name = fields.Char(related='field_id.name')