[IMP] website_sale, base: use invoice address at checkout

at checkout use the invoice address as billing address instead of
main address if the invoice one is defined. Add the functionality to
add new billings addresses at checkout.

task-3258835

Part-of: odoo/odoo#118428
This commit is contained in:
Valeriya(vchu)
2023-10-06 13:08:53 +00:00
committed by Victor Feyens
parent 4015f90bde
commit d9c7ff69a4
11 changed files with 288 additions and 136 deletions
+116 -62
View File
@@ -959,12 +959,11 @@ class WebsiteSale(payment_portal.PaymentPortal):
# ------------------------------------------------------
def checkout_check_address(self, order):
billing_fields_required = self._get_mandatory_fields_billing(order.partner_id.country_id.id)
if not all(order.partner_id.read(billing_fields_required)[0].values()):
return request.redirect('/shop/address?partner_id=%d' % order.partner_id.id)
partner_invoice = order.partner_invoice_id
if not self._check_billing_partner_mandatory_fields(partner_invoice):
return request.redirect('/shop/address?partner_id=%d&is_invoice=1' % partner_invoice.id)
shipping_fields_required = self._get_mandatory_fields_shipping(order.partner_shipping_id.country_id.id)
if not all(order.partner_shipping_id.read(shipping_fields_required)[0].values()):
if not self._check_shipping_partner_mandatory_fields(order.partner_shipping_id):
return request.redirect('/shop/address?partner_id=%d' % order.partner_shipping_id.id)
def checkout_redirection(self, order):
@@ -987,29 +986,49 @@ class WebsiteSale(payment_portal.PaymentPortal):
def checkout_values(self, **kw):
order = request.website.sale_get_order(force_create=True)
shippings = []
bill_partners = []
ship_partners = []
if order.partner_id != request.website.user_id.sudo().partner_id:
Partner = order.partner_id.with_context(show_address=1).sudo()
shippings = Partner.search([
("id", "child_of", order.partner_id.commercial_partner_id.ids),
'|', ("type", "in", ["delivery", "other"]), ("id", "=", order.partner_id.commercial_partner_id.id)
commercial_partner = order.partner_id.commercial_partner_id
bill_partners = Partner.search([
("id", "child_of", commercial_partner.ids),
'|', ("type", "in", ["invoice", "other"]), ("id", "=", commercial_partner.id)
], order='id desc')
if shippings:
if kw.get('partner_id') or 'use_billing' in kw:
if 'use_billing' in kw:
partner_id = order.partner_id.id
else:
partner_id = int(kw.get('partner_id'))
if partner_id in shippings.mapped('id'):
order.partner_shipping_id = partner_id
ship_partners = Partner.search([
("id", "child_of", commercial_partner.ids),
'|', ("type", "in", ["delivery", "other"]), ("id", "=", commercial_partner.id)
], order='id desc')
# do not show commercial_partner_id if its mandatory fields are not complete to children
# as children can not edit (fill) the commercial_partner_id
if commercial_partner != order.partner_id:
if not self._check_billing_partner_mandatory_fields(commercial_partner):
bill_partners = bill_partners.filtered(lambda p: p.id != commercial_partner.id)
if not self._check_shipping_partner_mandatory_fields(commercial_partner):
ship_partners = ship_partners.filtered(lambda p: p.id != commercial_partner.id)
partner_id = int(kw.get('partner_id', 0))
if partner_id:
if 'is_invoice' in kw and bill_partners and partner_id in bill_partners.mapped('id'):
order.partner_invoice_id = partner_id
elif ship_partners and partner_id in ship_partners.mapped('id'):
order.partner_shipping_id = partner_id
values = {
'order': order,
'shippings': shippings,
'shippings': ship_partners,
'billings': bill_partners,
'only_services': order and order.only_services or False
}
return values
def _check_billing_partner_mandatory_fields(self, partner_id):
''' return True if all mandatory fields for billing address are complete '''
billing_fields_required = self._get_mandatory_fields_billing(partner_id.country_id.id)
return all(partner_id.read(billing_fields_required)[0].values())
def _get_mandatory_fields_billing(self, country_id=False):
req = ["name", "email", "street", "city", "country_id"]
if country_id:
@@ -1020,6 +1039,11 @@ class WebsiteSale(payment_portal.PaymentPortal):
req += ['zip']
return req
def _check_shipping_partner_mandatory_fields(self, partner_id):
''' return True if all mandatory fields for shipping address are complete '''
shipping_fields_required = self._get_mandatory_fields_shipping(partner_id.country_id.id)
return all(partner_id.read(shipping_fields_required)[0].values())
def _get_mandatory_fields_shipping(self, country_id=False):
req = ["name", "street", "city", "country_id"]
if country_id:
@@ -1037,13 +1061,16 @@ class WebsiteSale(payment_portal.PaymentPortal):
error = dict()
error_message = []
if data.get('partner_id'):
if mode == ('edit', 'billing') and data.get('partner_id'):
partner_su = request.env['res.partner'].sudo().browse(int(data['partner_id'])).exists()
name_change = partner_su and 'name' in data and data['name'] != partner_su.name
email_change = partner_su and 'email' in data and data['email'] != partner_su.email
name_change = partner_su and partner_su.name and 'name' in data and data['name'] != partner_su.name
email_change = partner_su and 'email' in data and data['email'] != partner_su.email and partner_su.email
invoices = request.env['account.move'].sudo().search(
[('partner_id', '=', partner_su.id)], limit=1
) if partner_su else False
# Prevent changing the billing partner name if invoices have been issued.
if mode[1] == 'billing' and name_change and not partner_su.can_edit_vat():
if name_change and not partner_su.can_edit_vat() and invoices:
error['name'] = 'error'
error_message.append(_(
"Changing your name is not allowed once documents have been issued for your"
@@ -1151,20 +1178,23 @@ class WebsiteSale(payment_portal.PaymentPortal):
if request.website.specific_user_account:
new_values['website_id'] = request.website.id
commercial_partner_id = order.partner_id.commercial_partner_id
if mode[0] == 'new':
lang = request.lang.code if request.lang.code in request.website.mapped('language_ids.code') else None
if lang:
new_values['lang'] = lang
new_values['company_id'] = request.website.company_id.id
new_values['team_id'] = request.website.salesteam_id and request.website.salesteam_id.id
new_values['user_id'] = request.website.salesperson_id.id
lang = request.lang.code if request.lang.code in request.website.mapped('language_ids.code') else None
if lang:
new_values['lang'] = lang
if mode == ('edit', 'billing') and order.partner_id.type == 'contact':
new_values['type'] = 'other'
if mode[1] == 'shipping':
new_values['parent_id'] = order.partner_id.commercial_partner_id.id
new_values['type'] = 'delivery'
# define type of the new partner
if mode[1] == 'billing':
new_values['type'] = 'other' if 'use_same' in values else 'invoice'
# for public user avoid linking to default archived 'Public user' partner
if commercial_partner_id.active:
new_values['parent_id'] = commercial_partner_id.id
elif mode[1] == 'shipping':
new_values['type'] = 'delivery'
new_values['parent_id'] = commercial_partner_id.id
return new_values, errors, error_msg
@http.route(['/shop/address'], type='http', methods=['GET', 'POST'], auth="public", website=True, sitemap=False)
@@ -1176,35 +1206,36 @@ class WebsiteSale(payment_portal.PaymentPortal):
if redirection:
return redirection
mode = (False, False)
can_edit_vat = False
values, errors = {}, {}
partner_id = int(kw.get('partner_id', -1))
# IF PUBLIC ORDER
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
if order._is_public_order():
mode = ('new', 'billing')
can_edit_vat = True
# IF ORDER LINKED TO A PARTNER
else:
else: # IF ORDER LINKED TO A PARTNER
if partner_id > 0:
if partner_id == order.partner_id.id:
mode = ('edit', 'billing')
can_edit_vat = order.partner_id.can_edit_vat()
partners = Partner.search(
[('id', 'child_of', order.partner_id.commercial_partner_id.ids)]
)
if 'is_invoice' in kw:
bill_partners_ids = partners.filtered(lambda p: p.type != 'delivery').ids
if partner_id in bill_partners_ids or order.partner_id.id == partner_id:
mode = ('edit', 'billing')
can_edit_vat = order.partner_id.can_edit_vat()
else:
return Forbidden()
else:
shippings = Partner.search([('id', 'child_of', order.partner_id.commercial_partner_id.ids)])
if order.partner_id.commercial_partner_id.id == partner_id:
mode = ('new', 'shipping')
partner_id = -1
elif partner_id in shippings.mapped('id'):
ship_partners_ids = partners.filtered(lambda p: p.type != 'invoice').ids
if partner_id in ship_partners_ids:
mode = ('edit', 'shipping')
else:
return Forbidden()
if mode and partner_id != -1:
values = Partner.browse(partner_id)
elif partner_id == -1:
mode = ('new', 'shipping')
mode = ('new', 'billing') if 'is_invoice' in kw else ('new', 'shipping')
else: # no mode - refresh without post?
return request.redirect('/shop/checkout')
@@ -1218,29 +1249,53 @@ class WebsiteSale(payment_portal.PaymentPortal):
errors['error_message'] = error_msg
values = kw
else:
update_mode, address_mode = mode
partner_id = self._checkout_form_save(mode, post, kw)
# We need to validate _checkout_form_save return, because when partner_id not in shippings
# it returns Forbidden() instead the partner_id
if isinstance(partner_id, Forbidden):
return partner_id
fpos_before = order.fiscal_position_id
if mode[1] == 'billing':
order.partner_id = partner_id
# This is the *only* thing that the front end user will see/edit anyway when choosing billing address
order.partner_invoice_id = partner_id
if not kw.get('use_same'):
kw['callback'] = kw.get('callback') or \
(not order.only_services and (mode[0] == 'edit' and '/shop/checkout' or '/shop/address'))
# We need to update the pricelist(by the one selected by the customer), because onchange_partner reset it
# We only need to update the pricelist when it is not redirected to /confirm_order
if kw.get('callback', False) != '/shop/confirm_order':
request.website.sale_get_order(update_pricelist=True)
elif mode[1] == 'shipping':
order.partner_shipping_id = partner_id
update_values = {}
if update_mode == 'new': # New address
if order._is_public_order():
update_values['partner_id'] = partner_id
if address_mode == 'billing':
update_values['partner_invoice_id'] = partner_id
if kw.get('use_same'):
update_values['partner_shipping_id'] = partner_id
elif not kw.get('callback') and not order.only_services:
# Now that the billing is set, if shipping is necessary
# request the user to fill the shipping address
kw['callback'] = '/shop/address'
elif address_mode == 'shipping':
update_values['partner_shipping_id'] = partner_id
elif update_mode == 'edit': # Updating an existing address
if order.partner_id.id == partner_id:
# Editing the main partner of the SO --> also trigger a partner update to
# recompute fpos & any partner-related fields
update_values['partner_id'] = partner_id
if address_mode == 'billing':
update_values['partner_invoice_id'] = partner_id
if not kw.get('callback') and not order.only_services:
kw['callback'] = '/shop/checkout'
elif address_mode == 'shipping':
update_values['partner_shipping_id'] = partner_id
order.write(update_values)
if order.fiscal_position_id != fpos_before:
# Recompute taxes on fpos change
# TODO recompute all prices too to correctly manage price_include taxes ?
order._recompute_taxes()
if 'partner_id' in update_values:
# Force recomputation of pricelist on main customer address update
request.website.sale_get_order(update_pricelist=True)
# TDE FIXME: don't ever do this
# -> TDE: you are the guy that did what we should never do in commit e6f038a
order.message_partner_ids = [(4, partner_id), (3, request.website.partner_id.id)]
@@ -1438,8 +1493,7 @@ class WebsiteSale(payment_portal.PaymentPortal):
order = render_values['website_sale_order']
def_country_id = order.partner_id.country_id
# IF PUBLIC ORDER
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
if order._is_public_order():
if request.geoip.country_code:
def_country_id = request.env['res.country'].search([('code', '=', request.geoip.country_code)], limit=1)
else:
@@ -1463,7 +1517,7 @@ class WebsiteSale(payment_portal.PaymentPortal):
if redirection:
return redirection
if order.partner_id.id == request.website.user_id.sudo().partner_id.id:
if order._is_public_order():
return request.redirect('/shop/address')
redirection = self.checkout_check_address(order)
+6
View File
@@ -622,3 +622,9 @@ class SaleOrder(models.Model):
]).available_carriers(
self.partner_shipping_id
).filtered(_is_carrier_available)
#=== TOOLING ===#
def _is_public_order(self):
self.ensure_one()
return self.partner_id.id == request.website.user_id.sudo().partner_id.id
+7 -5
View File
@@ -393,7 +393,6 @@ class Website(models.Model):
# change the partner, and trigger the computes (fpos)
sale_order_sudo.write({
'partner_id': partner_sudo.id,
'partner_invoice_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,
@@ -418,7 +417,7 @@ class Website(models.Model):
def _prepare_sale_order_values(self, partner_sudo):
self.ensure_one()
addr = partner_sudo.address_get(['delivery'])
addr = partner_sudo.address_get(['delivery', 'invoice'])
if not request.website.is_public_user():
# FIXME VFE why not use last_website_so_id field ?
last_sale_order = self.env['sale.order'].sudo().search(
@@ -426,8 +425,11 @@ class Website(models.Model):
limit=1,
order="date_order desc, id desc",
)
if last_sale_order and last_sale_order.partner_shipping_id.active: # first = me
addr['delivery'] = last_sale_order.partner_shipping_id.id
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()
@@ -439,7 +441,7 @@ class Website(models.Model):
'fiscal_position_id': self.fiscal_position_id.id,
'partner_id': partner_sudo.id,
'partner_invoice_id': partner_sudo.id,
'partner_invoice_id': addr['invoice'],
'partner_shipping_id': addr['delivery'],
'pricelist_id': self.pricelist_id.id,
@@ -796,6 +796,7 @@ publicWidget.registry.WebsiteSaleLayout = publicWidget.Widget.extend({
publicWidget.registry.websiteSaleCart = publicWidget.Widget.extend({
selector: '.oe_website_sale .oe_cart',
events: {
'click .js_change_billing': '_onClickChangeBilling',
'click .js_change_shipping': '_onClickChangeShipping',
'click .js_edit_address': '_onClickEditAddress',
'click .js_delete_product': '_onClickDeleteProduct',
@@ -805,19 +806,33 @@ publicWidget.registry.websiteSaleCart = publicWidget.Widget.extend({
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onClickChangeBilling: function (ev) {
this._onClickChangeAddress(ev, 'all_billing', 'js_change_billing');
},
/**
* @private
* @param {Event} ev
*/
_onClickChangeShipping: function (ev) {
var $old = $('.all_shipping').find('.card.border.border-primary');
$old.find('.btn-ship').toggle();
$old.addClass('js_change_shipping');
this._onClickChangeAddress(ev, 'all_shipping', 'js_change_shipping');
},
/**
* @private
* @param {Event} ev
*/
_onClickChangeAddress: function (ev, rowAddrClass, cardClass) {
var $old = $(`.${rowAddrClass}`).find('.card.border.border-primary');
$old.find('.btn-addr').toggle();
$old.addClass(cardClass);
$old.removeClass('border border-primary');
var $new = $(ev.currentTarget).parent('div.one_kanban').find('.card');
$new.find('.btn-ship').toggle();
$new.removeClass('js_change_shipping');
$new.find('.btn-addr').toggle();
$new.removeClass(cardClass);
$new.addClass('border border-primary');
var $form = $(ev.currentTarget).parent('div.one_kanban').find('form.d-none');
@@ -464,7 +464,7 @@ tr#empty {
display: none;
}
.js_change_shipping {
.js_change_shipping, .js_change_billing {
cursor: pointer;
}
@@ -239,7 +239,7 @@
},
{
content: "Add new shipping address",
trigger: '.one_kanban form[action^="/shop/address"] .btn',
trigger: '.all_shipping .one_kanban form[action^="/shop/address"] .btn',
},
{
content: "Fulfill shipping address form",
+57 -5
View File
@@ -146,6 +146,7 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
'name': 'a res.partner address', 'email': 'email@email.email', 'street': 'ooo',
'city': 'ooo', 'zip': '1200', 'country_id': self.country_id, 'submitted': 1,
}
self.default_billing_address_values = self.default_address_values | {'is_invoice': 1}
def _create_so(self, partner_id=None, company_id=None):
values = {
@@ -165,7 +166,7 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
return self.env['sale.order'].create(values)
def _get_last_address(self, partner):
''' Useful to retrieve the last created shipping address '''
""" Useful to retrieve the last created address (shipping or billing) """
return partner.child_ids.sorted('id', reverse=True)[0]
# TEST WEBSITE
@@ -233,7 +234,7 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
# 2. Logged in user/internal user, should not edit name or email address of billing
self.default_address_values['partner_id'] = self.demo_partner.id
self.WebsiteSaleController.address(**self.default_address_values)
self.WebsiteSaleController.address(**self.default_billing_address_values)
self.assertEqual(self.demo_partner.company_id, self.company_c, "Logged in user edited billing (the partner itself) should not get its company modified.")
self.assertNotEqual(self.demo_partner.name, self.default_address_values['name'], "Employee cannot change their name during the checkout process.")
self.assertNotEqual(self.demo_partner.email, self.default_address_values['email'], "Employee cannot change their email during the checkout process.")
@@ -257,7 +258,7 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
# 2. Public user, edit billing
self.default_address_values['partner_id'] = new_partner.id
self.WebsiteSaleController.address(**self.default_address_values)
self.WebsiteSaleController.address(**self.default_billing_address_values)
self.assertEqual(new_partner.company_id, self.website.company_id, "Public user edited billing (the partner itself) should not get its company modified.")
def test_04_apply_empty_pl(self):
@@ -353,7 +354,7 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
# 2. Portal user, edit billing
self.default_address_values['partner_id'] = self.portal_partner.id
self.WebsiteSaleController.address(**self.default_address_values)
self.WebsiteSaleController.address(**self.default_billing_address_values)
# Name cannot be changed if there are issued invoices
self.assertNotEqual(self.portal_partner.name, self.default_address_values['name'], "Portal User should not be able to change the name if they have invoices under their name.")
@@ -383,7 +384,7 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
{'name': 'Tax 20% excl', 'amount': 20, 'price_include': False},
{'name': 'Tax 15% incl', 'amount': 15, 'price_include': True},
])
self.env['account.fiscal.position'].create([
fpos_be, fpos_nl = self.env['account.fiscal.position'].create([
{
'sequence': 1,
'name': 'BE',
@@ -426,13 +427,64 @@ class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo, HttpCaseWithUs
req.httprequest.method = "POST"
self.WebsiteSaleController.address(**be_address_POST)
self.assertEqual(
so.fiscal_position_id,
fpos_be,
)
self.assertEqual(
[so.amount_untaxed, so.amount_tax, so.amount_total],
[90.91, 18.18, 109.09] # (100 : (1 + 10%)) * (1 + 20%) = 109.09
)
self.WebsiteSaleController.address(**nl_address_POST)
self.assertEqual(
so.fiscal_position_id,
fpos_nl,
)
self.assertEqual(
[so.amount_untaxed, so.amount_tax, so.amount_total],
[90.91, 13.64, 104.55] # (100 : (1 + 10%)) * (1 + 15%) = 104.55
)
def test_08_new_user_address_state(self):
''' Test the billing and shipping addresses creation values. '''
self._setUp_multicompany_env()
so = self._create_so(self.demo_partner.id)
env = api.Environment(self.env.cr, self.demo_user.id, {})
# change also website env for `sale_get_order` to not change order partner_id
with MockRequest(env, website=self.website.with_env(env), sale_order_id=so.id) as req:
req.httprequest.method = "POST"
# check the default values
self.assertEqual(self.demo_partner, so.partner_invoice_id)
self.assertEqual(self.demo_partner, so.partner_shipping_id)
# 1. Logged-in user, new shipping
self.WebsiteSaleController.address(**self.default_address_values)
new_shipping = self._get_last_address(self.demo_partner)
msg = "New shipping address should have its type set as 'delivery'"
self.assertTrue(new_shipping.type == 'delivery', msg)
# 2. Logged-in user, new billing
self.WebsiteSaleController.address(**self.default_billing_address_values)
new_billing = self._get_last_address(self.demo_partner)
msg = "New billing should have its type set as 'invoice'"
self.assertTrue(new_billing.type == 'invoice', msg)
self.assertNotEqual(new_billing, self.demo_partner)
# 3. Check that invoice/shipping address of so changed
self.assertEqual(new_billing, so.partner_invoice_id)
self.assertEqual(new_shipping, so.partner_shipping_id)
# 4. Logged-in user, new billing use same
use_same = self.default_billing_address_values | {'use_same': 1}
self.WebsiteSaleController.address(**use_same)
new_billing_use_same = self._get_last_address(self.demo_partner)
msg = "New billing use same should have its type set as 'other'"
self.assertTrue(new_billing_use_same.type == 'other', msg)
self.assertNotEqual(new_billing, self.demo_partner)
# 5. Check that invoice/shipping address of so changed
self.assertEqual(new_billing_use_same, so.partner_invoice_id)
self.assertEqual(new_billing_use_same, so.partner_shipping_id)
+76 -53
View File
@@ -1735,7 +1735,7 @@
<t t-set="no_footer" t-value="1"/>
<div id="wrap">
<div class="container oe_website_sale py-2">
<t t-set="same_shipping" t-value="bool(order.partner_shipping_id==order.partner_id or only_services)" />
<t t-set="same_shipping" t-value="bool(order.partner_shipping_id==order.partner_invoice_id or only_services)" />
<div class="row">
<div class="col-12">
<t t-call="website_sale.wizard_checkout">
@@ -1752,47 +1752,23 @@
<div class="col-lg-12">
<h3 class="o_page_header mt8">Billing Address</h3>
</div>
<div class="col-lg-6 one_kanban">
<t t-call="website_sale.address_kanban">
<t t-set='contact' t-value="order.partner_id"/>
<t t-set='selected' t-value="1"/>
<t t-set='readonly' t-value="1"/>
<t t-set='allow_edit' t-value="1"/>
</t>
</div>
</div>
<t t-call="website_sale.row_addresses">
<t t-set="order" t-value="order"/>
<t t-set="is_invoice" t-value="1"/>
<t t-set="addresses" t-value="billings"/>
</t>
<t t-if="not only_services" groups="account.group_delivery_invoice_address">
<div class="row">
<div class="col-lg-12">
<h3 class="o_page_header mt16 mb4">Shipping Address</h3>
</div>
</div>
<div class="row all_shipping">
<div class="col-lg-12">
<div class="row mt8">
<div class="col-md-12 col-lg-12 one_kanban">
<form action="/shop/address" method="post" class=''>
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" t-nocache="The csrf token must always be up to date."/>
<a role="button" href="#" class='a-submit btn btn-secondary mb-2 btn-block'>
<i class="fa fa-plus-square"/>
<span>Add an address</span>
</a>
</form>
</div>
<t t-foreach="shippings" t-as="ship">
<div class="col-md-12 col-lg-6 one_kanban mb-2">
<t t-call="website_sale.address_kanban">
<t t-set='contact' t-value="ship"/>
<t t-set='selected' t-value="order.partner_shipping_id==ship"/>
<t t-set='readonly' t-value="bool(len(shippings)==1)"/>
<t t-set='edit_billing' t-value="bool(ship==order.partner_id)"/>
<t t-set="allow_edit" t-value="not order.partner_id or (ship.id in order.partner_id.child_ids.ids)" />
</t>
</div>
</t>
</div>
</div>
</div>
<t t-call="website_sale.row_addresses">
<t t-set="order" t-value="order"/>
<t t-set="is_invoice" t-value="0"/>
<t t-set="addresses" t-value="shippings"/>
</t>
</t>
<div class="d-flex justify-content-between mt-3">
<a role="button" href="/shop/cart" class="btn btn-secondary mb32">
@@ -1810,29 +1786,71 @@
</div>
</t>
</template>
<template id="row_addresses">
<div t-attf-class="row {{'all_billing' if is_invoice else 'all_shipping'}}">
<div class="col-lg-12">
<div class="row mt8">
<div class="col-md-12 col-lg-12 one_kanban">
<form action="/shop/address" method="post" class="">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" t-nocache="The csrf token must always be up to date."/>
<t t-if="is_invoice">
<input type="hidden" name="is_invoice" t-att-value="is_invoice" />
<input type="hidden" name="callback" value="/shop/checkout" />
</t>
<a role="button" href="#" class="a-submit btn btn-secondary mb-2 btn-block">
<i class="fa fa-plus-square"/>
<span>Add an address</span>
</a>
</form>
</div>
<div t-foreach="addresses" t-as="addr"
class="col-md-12 col-lg-6 one_kanban mb-2">
<t t-call="website_sale.address_kanban">
<t t-set="is_invoice" t-value="is_invoice"/>
<t t-set="contact" t-value="addr"/>
<t t-set="order" t-value="order"/>
<t t-if="not is_invoice" t-set="selected" t-value="order.partner_shipping_id==addr or len(addresses)==1"/>
<t t-if="is_invoice" t-set="selected" t-value="order.partner_invoice_id==addr or len(addresses)==1"/>
<t t-set="readonly" t-value="bool(len(addresses)==1)"/>
<t t-set="allow_edit"
t-value="(website.is_public_user() or order.partner_id.id == addr.id or (addr.id in order.partner_id.commercial_partner_id.child_ids.ids))
and is_invoice or addr.type=='delivery'"
/>
</t>
</div>
</div>
</div>
</div>
</template>
<template id="address_kanban" name="Kanban address">
<form action="/shop/checkout" method="POST" class="d-none">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" t-nocache="The csrf token must always be up to date."/>
<input type="hidden" name="partner_id" t-att-value="contact.id" />
<t t-if='edit_billing'>
<input type="hidden" name="callback" value="/shop/checkout?use_billing" />
<t t-if="is_invoice">
<input type="hidden" name="is_invoice" t-att-value="is_invoice"/>
<input type="hidden" name="callback" value="/shop/checkout"/>
</t>
<input type='submit'/>
<input type="submit"/>
</form>
<div t-attf-class="card #{selected and 'border border-primary' or 'js_change_shipping'}">
<div class='card-body' style='min-height: 130px;'>
<div t-attf-class="card #{selected and 'border border-primary' or is_invoice and 'js_change_billing' or 'js_change_shipping'}">
<div class="card-body" style="min-height: 130px;">
<a t-if="allow_edit" href="#" class="btn btn-link float-end p-0 js_edit_address no-decoration" role="button" title="Edit this address" aria-label="Edit this address"><i class='fa fa-edit'/></a>
<t t-esc="contact" t-options="dict(widget='contact', fields=['name', 'address'], no_marker=True)"/>
</div>
<div class='card-footer' t-if='not readonly'>
<span class='btn-ship' t-att-style="'' if selected else 'display:none;'">
<a role="button" href='#' class="btn btn-block btn-primary">
<i class='fa fa-check'></i> Ship to this address
<div class="card-footer" t-if="not readonly">
<span class="btn-addr" t-att-style="'' if selected else 'display:none;'">
<a role="button" href="#" class="btn btn-block btn-primary">
<i class="fa fa-check"/>
<t t-if="is_invoice">
Bill to this address
</t>
<t t-else="">
Ship to this address
</t>
</a>
</span>
<span class='btn-ship' t-att-style="'' if not selected else 'display:none;'">
<a role="button" href='#' class="btn btn-block btn-secondary">
<span class="btn-addr" t-att-style="'' if not selected else 'display:none;'">
<a role="button" href="#" class="btn btn-block btn-secondary">
Select this address
</a>
</span>
@@ -1861,14 +1879,14 @@
</div>
<div class="col-12 col-xl order-xl-1 oe_cart">
<div>
<t t-if="mode == ('new', 'billing')">
<t t-if="mode == ('new', 'billing') and is_public_user">
<h2 class="o_page_header mt8">
<span t-if="account_on_checkout != 'mandatory'">Fill in your address</span>
<small class="text-muted" t-if="account_on_checkout == 'optional'"> or </small>
<a t-if="account_on_checkout != 'disabled'" role="button" href='/web/login?redirect=/shop/checkout' style="margin-top: -11px"> Sign in</a>
</h2>
</t>
<t t-if="mode == ('edit', 'billing')">
<t t-elif="mode == ('edit', 'billing') or mode[1] == 'billing' and not is_public_user">
<h2 class="o_page_header mt8">Your Address</h2>
</t>
<t t-if="mode[1] == 'shipping'">
@@ -1975,8 +1993,10 @@
<div class="col-lg-12">
<div class="checkbox">
<label>
<input type="checkbox" id='shipping_use_same' class="mr8" name='use_same' value="1" checked='checked'/>Ship to the same address
<span class='ship_to_other text-muted' style="display: none">&amp;nbsp;(<i>Your shipping address will be requested later) </i></span>
<input type="checkbox" id='shipping_use_same' class="mr8" name='use_same' value="1" t-att-checked='website.is_public_user()'/>Ship to the same address
<t t-if='website.is_public_user()'>
<span class='ship_to_other text-muted' style="display: none">&amp;nbsp;(<i>Your shipping address will be requested later) </i></span>
</t>
</label>
</div>
</div>
@@ -1987,6 +2007,9 @@
<input type="hidden" name="submitted" value="1" />
<input type="hidden" name="partner_id" t-att-value="partner_id or '0'" />
<input type="hidden" name="callback" t-att-value="callback" />
<t t-if="mode[1] == 'billing'">
<input type="hidden" name="is_invoice" value="1" />
</t>
<!-- Example -->
<input type="hidden" name="field_required" t-att-value="'phone,name'" />
@@ -2015,10 +2038,10 @@
<div class="card">
<div class="card-body" id="shipping_and_billing">
<a class="float-end no-decoration" href="/shop/checkout"><i class="fa fa-edit"/> Edit</a>
<t t-set="same_shipping" t-value="bool(order.partner_shipping_id==order.partner_id or only_services)" />
<t t-set="same_shipping" t-value="bool(order.partner_shipping_id==order.partner_invoice_id or only_services)" />
<div>
<b>Billing<t t-if="same_shipping and not only_services"> &amp; Shipping</t>: </b>
<span t-esc="order.partner_id" t-options="dict(widget='contact', fields=['address'], no_marker=True, separator=', ')" class="address-inline"/>
<span t-esc="order.partner_invoice_id" t-options="dict(widget='contact', fields=['address'], no_marker=True, separator=', ')" class="address-inline"/>
</div>
<div t-if="not same_shipping and not only_services" groups="account.group_delivery_invoice_address">
<b>Shipping: </b>
@@ -8,7 +8,7 @@
</xpath>
</template>
<template id="website_sale_mondialrelay_checkout" inherit_id="website_sale.checkout">
<template id="website_sale_mondialrelay_checkout" inherit_id="website_sale.row_addresses">
<xpath expr="//t[@t-set='allow_edit']" position="after">
<t t-set="allow_edit" t-value="allow_edit and not contact.is_mondialrelay"/>
</xpath>
+2 -2
View File
@@ -1760,12 +1760,12 @@ actual arch.
pass
elif node.tag == 'input' and node.get('type') in ('button', 'submit', 'reset'):
pass
elif any(klass in classes for klass in ('btn-group', 'btn-toolbar', 'btn-ship')):
elif any(klass in classes for klass in ('btn-group', 'btn-toolbar', 'btn-addr')):
pass
else:
msg = ("A simili button must be in tag a/button/select or tag `input` "
"with type button/submit/reset or have class in "
"btn-group/btn-toolbar/btn-ship")
"btn-group/btn-toolbar/btn-addr")
self._log_view_warning(msg, node)
def _validate_fa_class_accessibility(self, node, description):
+1 -1
View File
@@ -3040,7 +3040,7 @@ class TestViews(ViewCase):
self.assertValid('<form><input type="reset" class="btn" role="button"/></form>')
self.assertValid('<form><div type="reset" class="btn btn-group" role="button"/></form>')
self.assertValid('<form><div type="reset" class="btn btn-toolbar" role="button"/></form>')
self.assertValid('<form><div type="reset" class="btn btn-ship" role="button"/></form>')
self.assertValid('<form><div type="reset" class="btn btn-addr" role="button"/></form>')
self.assertWarning('<form><div class="btn" role="button"/></form>')
self.assertWarning('<form><input type="email" class="btn" role="button"/></form>')