[FIX] website_event_booth_sale: display price correction
Steps to reproduce: - Create a price list with different currency and discount with "show price and discount to the customer" - On the website select this pricelist and try to select the booth Issue: The displayed price will not be the correct one Note: This is an issue discovered during the correction of https://github.com/odoo/odoo/pull/101375 (forward-port of https://github.com/odoo/odoo/pull/85640) It allows to have the correct price depending of the currency of the pricelist applied. Now the unlink of the rate makes the new rate directlt effective. There is no need of having a `new_company` anymore. Summary: - view modification in `website_event_booth_sale` -> price of selected booth, simplification of comparison for the `<del>` - view modification in `website_event_sale` : simplification of comparison for the `<del>` - backend test modification in `website_event_[booth_]sale` common: addapt the rate; take out useless `new_env`; simplified pricelists creation - tour test addition: added the tour for essential use cases in event and event_booth; simplified the command so it is more readable related ticket: opw-2766997 closes odoo/odoo#106593 closes odoo/odoo#107768 X-original-commit: 43c9d9892f593a41c7fe139dada0ce3f79f6f287 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com> Signed-off-by: Nicolas Lempereur (nle) <nle@odoo.com> Signed-off-by: Yolann Sabaux (yosa) <yosa@odoo.com>
This commit is contained in:
@@ -18,7 +18,7 @@ Use the e-commerce to sell your event booths.
|
||||
'/website_event_booth_sale/static/src/js/booth_register.js',
|
||||
],
|
||||
'web.assets_tests': [
|
||||
'/website_event_booth_sale/static/tests/tours/website_event_booth.js',
|
||||
'/website_event_booth_sale/static/tests/tours/**/**.js'
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
odoo.define('website_event_booth_sale.tour.WebsiteEventBoothSaleTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
const { changePricelist, checkPriceCart } = require('website_event_sale.tour.WebsiteEventSaleTourMethods');
|
||||
|
||||
function checkPriceBooth(eventName, price, priceSelected) {
|
||||
return [
|
||||
{
|
||||
content: "Go to page Event",
|
||||
trigger: '.nav-link:contains("Event")',
|
||||
},
|
||||
{
|
||||
content: 'Open "Test Event Booths" event',
|
||||
trigger: `h5.card-title span:contains(${eventName})`,
|
||||
},
|
||||
{
|
||||
content: 'Go to "Get A Booth" page',
|
||||
trigger: 'li.nav-item a:has(span:contains("Get A Booth"))',
|
||||
},
|
||||
{
|
||||
content: 'Select the booth',
|
||||
trigger: '.o_wbooth_booths input[name="event_booth_ids"]',
|
||||
run: function () {
|
||||
$('.o_wbooth_booths input[name="event_booth_ids"]:lt(1)').click();
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Verify Price displayed",
|
||||
trigger: `.oe_currency_value:contains(${price})`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
{
|
||||
content: "Verify Price of selected booth",
|
||||
trigger: `div.o_wbooth_booth_total_price span.oe_currency_value:contains(${priceSelected})`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
}
|
||||
function checkPriceDiscountBooth(eventName, price, priceSelected, discount) {
|
||||
return [
|
||||
...checkPriceBooth(eventName, price, priceSelected),
|
||||
{
|
||||
content: "Verify Price before discount",
|
||||
trigger: `del:contains(${discount})`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
}
|
||||
const getPriceListChecksSteps = function ({pricelistName, eventName, price, priceSelected, priceCart, priceBeforeDiscount=false}) {
|
||||
const checkPriceSteps = priceBeforeDiscount ? checkPriceDiscountBooth(eventName, price, priceSelected, priceBeforeDiscount) : checkPriceBooth(eventName, price, priceSelected);
|
||||
return [
|
||||
...changePricelist(pricelistName),
|
||||
...checkPriceSteps,
|
||||
...checkPriceCart(priceCart),
|
||||
]
|
||||
}
|
||||
return { getPriceListChecksSteps }
|
||||
});
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
import { getPriceListChecksSteps } from 'website_event_booth_sale.tour.WebsiteEventBoothSaleTourMethods';
|
||||
|
||||
tour.register('event_booth_sale_pricelists_different_currencies', {
|
||||
test: true,
|
||||
url: '/event',
|
||||
}, [
|
||||
// Init: registering the booth
|
||||
{
|
||||
content: 'Open "Test Event Booths" event',
|
||||
trigger: 'h5.card-title span:contains("Test Event Booths")',
|
||||
},
|
||||
{
|
||||
content: 'Go to "Get A Booth" page',
|
||||
trigger: 'li.nav-item a:has(span:contains("Get A Booth"))',
|
||||
},
|
||||
{
|
||||
content: 'Select the booth',
|
||||
trigger: '.o_wbooth_booths input[name="event_booth_ids"]',
|
||||
run: function () {
|
||||
$('.o_wbooth_booths input[name="event_booth_ids"]:lt(1)').click();
|
||||
},
|
||||
},
|
||||
{
|
||||
content: 'Confirm the booth by clicking the submit button',
|
||||
trigger: 'button.o_wbooth_registration_submit',
|
||||
},
|
||||
{
|
||||
content: 'Fill in your contact information',
|
||||
trigger: 'input[name="contact_name"]',
|
||||
run: function () {
|
||||
$('input[name="contact_name"]').val('John Doe');
|
||||
$('input[name="contact_email"]').val('jdoe@example.com');
|
||||
},
|
||||
},
|
||||
{
|
||||
content: 'Submit your informations',
|
||||
trigger: 'button[type="submit"]',
|
||||
},
|
||||
{
|
||||
content: 'Confirm Order',
|
||||
extra_trigger: 'h3:contains("Billing Address")',
|
||||
trigger: '.btn:contains("Confirm")',
|
||||
run: function () {},
|
||||
},
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EUR With Discount Included",
|
||||
eventName: "Test Event Booths",
|
||||
price: "90.00",
|
||||
priceSelected: "90",
|
||||
priceCart: "99.00",
|
||||
}),
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EUR Without Discount Included",
|
||||
eventName: "Test Event Booths",
|
||||
price: "90.00",
|
||||
priceSelected: "90",
|
||||
priceCart: "99.00",
|
||||
priceBeforeDiscount: "100.00",
|
||||
}),
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EX With Discount Included",
|
||||
eventName: "Test Event Booths",
|
||||
price: "900.00",
|
||||
priceSelected: "900",
|
||||
priceCart: "990.00",
|
||||
}),
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EX Without Discount Included",
|
||||
eventName: "Test Event Booths",
|
||||
price: "900.00",
|
||||
priceSelected: "900",
|
||||
priceCart: "990.00",
|
||||
priceBeforeDiscount: "1,000.00",
|
||||
}),
|
||||
]);
|
||||
@@ -4,12 +4,14 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.addons.website_event_sale.tests.common import TestWebsiteEventSaleCommon
|
||||
from odoo.tests import HttpCase
|
||||
from odoo.tests.common import tagged
|
||||
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestWebsiteEventBoothSale(HttpCase):
|
||||
class TestWebsiteEventBoothSale(HttpCase, TestWebsiteEventSaleCommon):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -61,3 +63,6 @@ class TestWebsiteEventBoothSale(HttpCase):
|
||||
|
||||
def test_tour(self):
|
||||
self.start_tour('/event', 'website_event_booth_tour', login='portal')
|
||||
|
||||
def test_booth_pricelists_different_currencies(self):
|
||||
self.start_tour("/web", 'event_booth_sale_pricelists_different_currencies', login='admin')
|
||||
|
||||
@@ -38,7 +38,7 @@ class TestWebsiteBoothPriceList(TestEventBoothSaleCommon, TestWebsiteEventSaleCo
|
||||
})
|
||||
# set pricelist to 0 - currency: company
|
||||
self.pricelist.write({
|
||||
'currency_id': self.new_company.currency_id.id,
|
||||
'currency_id': self.env.company.currency_id.id,
|
||||
'discount_policy': 'with_discount',
|
||||
'item_ids': [(5, 0, 0), (0, 0, {
|
||||
'applied_on': '3_global',
|
||||
@@ -50,7 +50,7 @@ class TestWebsiteBoothPriceList(TestEventBoothSaleCommon, TestWebsiteEventSaleCo
|
||||
with MockRequest(self.env, sale_order_id=self.so.id, website=self.current_website):
|
||||
self.WebsiteSaleController.pricelist(promo=None)
|
||||
self.so._cart_update(line_id=so_line.id, product_id=self.event_booth_product.id, set_qty=1)
|
||||
self.assertEqual(so_line.price_reduce, 40)
|
||||
self.assertEqual(so_line.price_reduce_taxexcl, 40)
|
||||
|
||||
# set pricelist to 10% - without discount
|
||||
self.pricelist.write({
|
||||
@@ -66,7 +66,7 @@ class TestWebsiteBoothPriceList(TestEventBoothSaleCommon, TestWebsiteEventSaleCo
|
||||
with MockRequest(self.env, sale_order_id=self.so.id, website=self.current_website):
|
||||
self.WebsiteSaleController.pricelist(promo=None)
|
||||
self.so._cart_update(line_id=so_line.id, product_id=self.event_booth_product.id, set_qty=1)
|
||||
self.assertEqual(so_line.price_reduce, 360, 'Incorrect amount based on the pricelist "Without Discount" and its currency.')
|
||||
self.assertEqual(so_line.price_reduce_taxexcl, 360, 'Incorrect amount based on the pricelist "Without Discount" and its currency.')
|
||||
|
||||
# set pricelist to 10% - with discount
|
||||
self.pricelist.write({
|
||||
@@ -76,4 +76,4 @@ class TestWebsiteBoothPriceList(TestEventBoothSaleCommon, TestWebsiteEventSaleCo
|
||||
with MockRequest(self.env, sale_order_id=self.so.id, website=self.current_website):
|
||||
self.WebsiteSaleController.pricelist(promo=None)
|
||||
self.so._cart_update(line_id=so_line.id, product_id=self.event_booth_product.id, set_qty=1)
|
||||
self.assertEqual(so_line.price_reduce, 360, 'Incorrect amount based on the pricelist "With Discount" and its currency.')
|
||||
self.assertEqual(so_line.price_reduce_taxexcl, 360, 'Incorrect amount based on the pricelist "With Discount" and its currency.')
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<xpath expr="//input[@name='booth_category_id']" position="attributes">
|
||||
<attribute name="t-att-data-price">
|
||||
event.company_id.sudo().currency_id._convert(
|
||||
booth_category.price_reduce_taxeinc if env.user.has_group('account.group_show_line_subtotals_tax_included') else booth_category.price_reduce,
|
||||
booth_category.price_reduce_taxinc if env.user.has_group('account.group_show_line_subtotals_tax_included') else booth_category.price_reduce,
|
||||
website.get_current_pricelist().currency_id,
|
||||
event.company_id,
|
||||
datetime.date.today()
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
odoo.define('website_event_sale.tour.WebsiteEventSaleTourMethods', function (require) {
|
||||
'use strict';
|
||||
|
||||
function changePricelist(pricelistName) {
|
||||
return [
|
||||
{
|
||||
content: "Go to page Shop",
|
||||
trigger: '.nav-link:contains("Shop")',
|
||||
},
|
||||
{
|
||||
content: "Toggle Pricelist",
|
||||
trigger: '.o_pricelist_dropdown > .dropdown-toggle',
|
||||
run: 'click',
|
||||
},
|
||||
{
|
||||
content: `Activate Pricelist ${pricelistName}`,
|
||||
trigger: `.dropdown-item:contains(${pricelistName})`,
|
||||
run: 'click',
|
||||
},
|
||||
{
|
||||
content: 'Wait for pricelist to load',
|
||||
trigger: `.dropdown-toggle:contains(${pricelistName})`,
|
||||
run: function () {},
|
||||
},
|
||||
];
|
||||
}
|
||||
function checkPriceEvent(eventName, price) {
|
||||
return [
|
||||
{
|
||||
content: "Go to page Event",
|
||||
trigger: '.nav-link:contains("Event")',
|
||||
},
|
||||
{
|
||||
content: "Open the Pycon event",
|
||||
trigger: `.o_wevent_events_list a:contains(${eventName})`,
|
||||
},
|
||||
{
|
||||
content: "Verify Price",
|
||||
trigger: `.oe_currency_value:contains(${price})`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
}
|
||||
function checkPriceDiscountEvent(eventName, price, discount) {
|
||||
return [
|
||||
...checkPriceEvent(eventName, price),
|
||||
{
|
||||
content: "Verify Price before discount",
|
||||
trigger: `del:contains(${discount})`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
}
|
||||
function checkPriceCart(price) {
|
||||
return [
|
||||
{
|
||||
content: "Go to page Cart",
|
||||
trigger: '.fa-shopping-cart',
|
||||
},
|
||||
{
|
||||
content: "Verify Price",
|
||||
trigger: `[id=order_total] .oe_currency_value:contains(${price})`,
|
||||
run: function () {}, // it's a check
|
||||
},
|
||||
]
|
||||
}
|
||||
const getPriceListChecksSteps = function ({pricelistName, eventName, price, priceBeforeDiscount=false}) {
|
||||
const checkPriceSteps = priceBeforeDiscount ? checkPriceDiscountEvent(eventName, price, priceBeforeDiscount) : checkPriceEvent(eventName, price);
|
||||
return [
|
||||
...changePricelist(pricelistName),
|
||||
...checkPriceSteps,
|
||||
...checkPriceCart(price),
|
||||
]
|
||||
}
|
||||
return { getPriceListChecksSteps, changePricelist, checkPriceCart }
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
odoo.define('website_event_sale.tour.event_sale_pricelists_different_currencies', function (require) {
|
||||
'use strict';
|
||||
|
||||
const tour = require('web_tour.tour');
|
||||
const { getPriceListChecksSteps } = require('website_event_sale.tour.WebsiteEventSaleTourMethods');
|
||||
|
||||
tour.register('event_sale_pricelists_different_currencies', {
|
||||
test: true,
|
||||
url: '/event',
|
||||
},[
|
||||
// Register for tickets
|
||||
{
|
||||
content: "Open the Pycon event",
|
||||
trigger: '.o_wevent_events_list a:contains("Pycon")',
|
||||
},
|
||||
{
|
||||
content: "Register",
|
||||
trigger: '.btn-primary:contains("Register")',
|
||||
},
|
||||
{
|
||||
content: "Fill attendees details",
|
||||
trigger: 'form[id="attendee_registration"] .btn:contains("Continue")',
|
||||
run: function () {
|
||||
$("input[name='1-name']").val("Great Name");
|
||||
$("input[name='1-phone']").val("111 111");
|
||||
$("input[name='1-email']").val("great@name.com");
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Validate attendees details",
|
||||
extra_trigger: "input[name='1-name'], input[name='2-name']",
|
||||
trigger: 'button:contains("Continue")',
|
||||
},
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EUR With Discount Included",
|
||||
eventName: "Pycon",
|
||||
price: "90.00",
|
||||
}),
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EUR Without Discount Included",
|
||||
eventName: "Pycon",
|
||||
price: "90.00",
|
||||
priceBeforeDiscount: "100.00",
|
||||
}),
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EX With Discount Included",
|
||||
eventName: "Pycon",
|
||||
price: "900.00",
|
||||
}),
|
||||
...getPriceListChecksSteps({
|
||||
pricelistName: "EX Without Discount Included",
|
||||
eventName: "Pycon",
|
||||
price: "900.00",
|
||||
priceBeforeDiscount: "1,000.00",
|
||||
}),
|
||||
]);
|
||||
});
|
||||
@@ -13,33 +13,28 @@ class TestWebsiteEventSaleCommon(TransactionCase):
|
||||
def setUpClass(cls):
|
||||
super(TestWebsiteEventSaleCommon, cls).setUpClass()
|
||||
|
||||
cls.env.ref('base.USD').write({'active': False})
|
||||
cls.currency_test = cls.env['res.currency'].create({
|
||||
'name': 'eventX',
|
||||
'rate': 10,
|
||||
'rounding': 0.01,
|
||||
'symbol': 'EX',
|
||||
})
|
||||
|
||||
cls.partner = cls.env['res.partner'].create({'name': 'test'})
|
||||
cls.new_company = cls.env['res.company'].create({
|
||||
'currency_id': cls.env.ref('base.EUR').id,
|
||||
'name': 'Great Company EUR',
|
||||
'partner_id': cls.partner.id,
|
||||
})
|
||||
cls.env['res.currency.rate'].create({
|
||||
'company_id': cls.new_company.id,
|
||||
cls.env['res.currency.rate'].search([]).unlink()
|
||||
cls.rate = cls.env['res.currency.rate'].create({
|
||||
'company_id': cls.env.company.id,
|
||||
'currency_id': cls.currency_test.id,
|
||||
'name': '2022-01-01',
|
||||
'rate': 10,
|
||||
})
|
||||
|
||||
cls.zero_tax = cls.env['account.tax'].sudo().create({
|
||||
'name': 'Tax 0',
|
||||
'amount': 0,
|
||||
})
|
||||
cls.product_event = cls.env['product.product'].create({
|
||||
'company_id': cls.new_company.id,
|
||||
'currency_id': cls.env.ref('base.EUR').id,
|
||||
'detailed_type': 'event',
|
||||
'list_price': 100,
|
||||
'name': 'Event Registration No Company Assigned',
|
||||
'taxes_id': [(6, 0, cls.zero_tax.ids)],
|
||||
})
|
||||
|
||||
cls.event = cls.env['event.event'].create({
|
||||
@@ -57,11 +52,28 @@ class TestWebsiteEventSaleCommon(TransactionCase):
|
||||
}])
|
||||
|
||||
cls.current_website = cls.env['website'].get_current_website()
|
||||
cls.current_website.company_id = cls.new_company
|
||||
cls.pricelist = cls.current_website.get_current_pricelist()
|
||||
|
||||
cls.so = cls.env['sale.order'].create({
|
||||
'company_id': cls.new_company.id,
|
||||
'company_id': cls.env.company.id,
|
||||
'partner_id': cls.partner.id,
|
||||
'pricelist_id': cls.pricelist.id,
|
||||
})
|
||||
|
||||
def create_pricelist(currency, name, policy):
|
||||
return cls.env['product.pricelist'].create({
|
||||
'currency_id': currency.id,
|
||||
'discount_policy': policy,
|
||||
'item_ids': [(5, 0, 0), (0, 0, {
|
||||
'applied_on': '3_global',
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 10,
|
||||
})],
|
||||
'name': name,
|
||||
'selectable': True,
|
||||
})
|
||||
|
||||
cls.pricelist_with_discount = create_pricelist(currency=cls.env.company.currency_id, name='EUR With Discount Included', policy='with_discount')
|
||||
cls.pricelist_without_discount = create_pricelist(currency=cls.env.company.currency_id, name='EUR Without Discount Included', policy='without_discount')
|
||||
cls.ex_pricelist_with_discount = create_pricelist(currency=cls.currency_test, name='EX With Discount Included', policy='with_discount')
|
||||
cls.ex_pricelist_without_discount = create_pricelist(currency=cls.currency_test, name='EX Without Discount Included', policy='without_discount')
|
||||
|
||||
@@ -6,11 +6,12 @@ import odoo.tests
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
|
||||
from odoo.addons.website_event_sale.tests.common import TestWebsiteEventSaleCommon
|
||||
from odoo.fields import Datetime
|
||||
|
||||
|
||||
@odoo.tests.common.tagged('post_install', '-at_install')
|
||||
class TestUi(HttpCaseWithUserDemo):
|
||||
class TestUi(HttpCaseWithUserDemo, TestWebsiteEventSaleCommon):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -124,4 +125,6 @@ class TestUi(HttpCaseWithUserDemo):
|
||||
|
||||
self.start_tour("/", 'event_buy_last_ticket')
|
||||
|
||||
def test_pricelists_different_currencies(self):
|
||||
self.start_tour("/", 'event_sale_pricelists_different_currencies', login='admin')
|
||||
# TO DO - add public test with new address when convert to web.tour format.
|
||||
|
||||
@@ -28,7 +28,7 @@ class TestWebsiteEventPriceList(TestWebsiteEventSaleCommon):
|
||||
})
|
||||
# set pricelist to 0 - currency: company
|
||||
self.pricelist.write({
|
||||
'currency_id': self.new_company.currency_id.id,
|
||||
'currency_id': self.env.company.currency_id.id,
|
||||
'discount_policy': 'with_discount',
|
||||
'item_ids': [(5, 0, 0), (0, 0, {
|
||||
'applied_on': '3_global',
|
||||
@@ -40,7 +40,7 @@ class TestWebsiteEventPriceList(TestWebsiteEventSaleCommon):
|
||||
with MockRequest(self.env, sale_order_id=self.so.id, website=self.current_website):
|
||||
self.WebsiteSaleController.pricelist(promo=None)
|
||||
self.so._cart_update(line_id=so_line.id, product_id=self.ticket.product_id.id, set_qty=1)
|
||||
self.assertEqual(so_line.price_reduce, 100)
|
||||
self.assertEqual(so_line.price_reduce_taxexcl, 100)
|
||||
|
||||
# set pricelist to 10% - without discount
|
||||
self.pricelist.write({
|
||||
@@ -56,7 +56,7 @@ class TestWebsiteEventPriceList(TestWebsiteEventSaleCommon):
|
||||
with MockRequest(self.env, sale_order_id=self.so.id, website=self.current_website):
|
||||
self.WebsiteSaleController.pricelist(promo=None)
|
||||
self.so._cart_update(line_id=so_line.id, product_id=self.ticket.product_id.id, set_qty=1)
|
||||
self.assertEqual(so_line.price_reduce, 900, 'Incorrect amount based on the pricelist and its currency.')
|
||||
self.assertEqual(so_line.price_reduce_taxexcl, 900, 'Incorrect amount based on the pricelist and its currency.')
|
||||
|
||||
# set pricelist to 10% - with discount
|
||||
self.pricelist.write({
|
||||
@@ -66,4 +66,4 @@ class TestWebsiteEventPriceList(TestWebsiteEventSaleCommon):
|
||||
with MockRequest(self.env, sale_order_id=self.so.id, website=self.current_website):
|
||||
self.WebsiteSaleController.pricelist(promo=None)
|
||||
self.so._cart_update(line_id=so_line.id, product_id=self.ticket.product_id.id, set_qty=1)
|
||||
self.assertEqual(so_line.price_reduce, 900, 'Incorrect amount based on the pricelist and its currency.')
|
||||
self.assertEqual(so_line.price_reduce_taxexcl, 900, 'Incorrect amount based on the pricelist and its currency.')
|
||||
|
||||
Reference in New Issue
Block a user