mirror of
https://github.com/OCA/rma.git
synced 2025-02-16 17:11:47 +02:00
Merge pull request #8 from guewen/rma-warranty-onchange
crm_claim_rma: warranty information is computed with on_changes
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2013 Camptocamp
|
||||
# Copyright 2009-2013 Akretion,
|
||||
# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau, Joel Grand-Guillaume
|
||||
# Copyright 2009-2013 Akretion,
|
||||
# Author: Emmanuel Samyn, Raphaël Valyi, Sébastien Beau,
|
||||
# Joel Grand-Guillaume
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
||||
@@ -32,6 +32,16 @@ from openerp.tools.translate import _
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
|
||||
class InvoiceNoDate(Exception):
|
||||
""" Raised when a warranty cannot be computed for a claim line
|
||||
because the invoice has no date. """
|
||||
|
||||
|
||||
class ProductNoSupplier(Exception):
|
||||
""" Raised when a warranty cannot be computed for a claim line
|
||||
because the product has no supplier. """
|
||||
|
||||
|
||||
class substate_substate(orm.Model):
|
||||
""" To precise a state (state=refused; substates= reason 1, 2,...) """
|
||||
_name = "substate.substate"
|
||||
@@ -217,41 +227,57 @@ class claim_line(orm.Model):
|
||||
days = int(days_month * decimal_part)
|
||||
return start + relativedelta(months=months, days=days)
|
||||
|
||||
# Method to calculate warranty limit
|
||||
def set_warranty_limit(self, cr, uid, ids, claim_line, context=None):
|
||||
date_invoice = claim_line.invoice_line_id.invoice_id.date_invoice
|
||||
def _warranty_limit_values(self, cr, uid, ids, invoice,
|
||||
claim_type, product, claim_date,
|
||||
context=None):
|
||||
if not (invoice and claim_type and product and claim_date):
|
||||
return {'guarantee_limit': False, 'warning': False}
|
||||
date_invoice = invoice.date_invoice
|
||||
if not date_invoice:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Cannot find any date for invoice. '
|
||||
'Must be a validated invoice.'))
|
||||
raise InvoiceNoDate
|
||||
warning = _(self.WARRANT_COMMENT['not_define'])
|
||||
date_inv_at_server = datetime.strptime(date_invoice,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
if claim_line.claim_id.claim_type == 'supplier':
|
||||
suppliers = claim_line.product_id.seller_ids
|
||||
date_invoice = datetime.strptime(date_invoice,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
if claim_type == 'supplier':
|
||||
suppliers = product.seller_ids
|
||||
if not suppliers:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('The product has no supplier configured.'))
|
||||
raise ProductNoSupplier
|
||||
supplier = suppliers[0]
|
||||
warranty_duration = supplier.warranty_duration
|
||||
else:
|
||||
warranty_duration = claim_line.product_id.warranty
|
||||
limit = self.warranty_limit(date_inv_at_server, warranty_duration)
|
||||
# If waranty period was defined
|
||||
warranty_duration = product.warranty
|
||||
limit = self.warranty_limit(date_invoice, warranty_duration)
|
||||
if warranty_duration > 0:
|
||||
claim_date = datetime.strptime(claim_line.claim_id.date,
|
||||
claim_date = datetime.strptime(claim_date,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
if limit < claim_date:
|
||||
warning = _(self.WARRANT_COMMENT['expired'])
|
||||
else:
|
||||
warning = _(self.WARRANT_COMMENT['valid'])
|
||||
self.write(
|
||||
cr, uid, ids,
|
||||
{'guarantee_limit': limit.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'warning': warning},
|
||||
context=context)
|
||||
return {'guarantee_limit': limit.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'warning': warning}
|
||||
|
||||
def set_warranty_limit(self, cr, uid, ids, claim_line, context=None):
|
||||
claim = claim_line.claim_id
|
||||
invoice = claim.invoice_id
|
||||
claim_type = claim.claim_type
|
||||
claim_date = claim.date
|
||||
product = claim_line.product_id
|
||||
try:
|
||||
values = self._warranty_limit_values(cr, uid, ids, invoice,
|
||||
claim_type, product,
|
||||
claim_date,
|
||||
context=context)
|
||||
except InvoiceNoDate:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Cannot find any date for invoice. '
|
||||
'Must be a validated invoice.'))
|
||||
except ProductNoSupplier:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('The product has no supplier configured.'))
|
||||
self.write(cr, uid, ids, values, context=context)
|
||||
return True
|
||||
|
||||
def auto_set_warranty(self, cr, uid, ids, context):
|
||||
@@ -283,9 +309,58 @@ class claim_line(orm.Model):
|
||||
location_dest_id = seller.name.property_stock_supplier.id
|
||||
return location_dest_id
|
||||
|
||||
# Method to calculate warranty return address
|
||||
def set_warranty_return_address(self, cr, uid, ids, claim_line,
|
||||
context=None):
|
||||
def onchange_product_id(self, cr, uid, ids, product_id, invoice_line_id,
|
||||
claim_id, company_id, warehouse_id,
|
||||
claim_type, claim_date, context=None):
|
||||
if not claim_id and not (company_id and warehouse_id and
|
||||
claim_type and claim_date):
|
||||
# if we have a claim_id, we get the info from there,
|
||||
# otherwise we get it from the args (on creation typically)
|
||||
return {}
|
||||
if not (product_id and invoice_line_id):
|
||||
return {}
|
||||
product_obj = self.pool['product.product']
|
||||
claim_obj = self.pool['crm.claim']
|
||||
invoice_line_obj = self.pool['account.invoice.line']
|
||||
claim_line_obj = self.pool.get('claim.line')
|
||||
product = product_obj.browse(cr, uid, product_id, context=context)
|
||||
invoice_line = invoice_line_obj.browse(cr, uid, invoice_line_id,
|
||||
context=context)
|
||||
invoice = invoice_line.invoice_id
|
||||
|
||||
if claim_id:
|
||||
claim = claim_obj.browse(cr, uid, claim_id, context=context)
|
||||
company = claim.company_id
|
||||
warehouse = claim.warehouse_id
|
||||
claim_type = claim.claim_type
|
||||
claim_date = claim.date
|
||||
else:
|
||||
warehouse_obj = self.pool['stock.warehouse']
|
||||
company_obj = self.pool['res.company']
|
||||
company = company_obj.browse(cr, uid, company_id, context=context)
|
||||
warehouse = warehouse_obj.browse(cr, uid, warehouse_id,
|
||||
context=context)
|
||||
|
||||
values = {}
|
||||
try:
|
||||
warranty = claim_line_obj._warranty_limit_values(
|
||||
cr, uid, [], invoice,
|
||||
claim_type, product,
|
||||
claim_date, context=context)
|
||||
except (InvoiceNoDate, ProductNoSupplier):
|
||||
# we don't mind at this point if the warranty can't be
|
||||
# computed and we don't want to block the user
|
||||
values.update({'guarantee_limit': False, 'warning': False})
|
||||
else:
|
||||
values.update(warranty)
|
||||
|
||||
warranty_address = claim_line_obj._warranty_return_address_values(
|
||||
cr, uid, [], product, company, warehouse, context=context)
|
||||
values.update(warranty_address)
|
||||
return {'value': values}
|
||||
|
||||
def _warranty_return_address_values(self, cr, uid, ids, product, company,
|
||||
warehouse, context=None):
|
||||
"""Return the partner to be used as return destination and
|
||||
the destination stock location of the line in case of return.
|
||||
|
||||
@@ -295,28 +370,36 @@ class claim_line(orm.Model):
|
||||
- supplier: return to the supplier address
|
||||
|
||||
"""
|
||||
if not (product and company and warehouse):
|
||||
return {'warranty_return_partner': False,
|
||||
'warranty_type': False,
|
||||
'location_dest_id': False}
|
||||
return_address = None
|
||||
seller = claim_line.product_id.seller_info_id
|
||||
seller = product.seller_info_id
|
||||
if seller:
|
||||
return_address_id = seller.warranty_return_address.id
|
||||
return_type = seller.warranty_return_partner
|
||||
else:
|
||||
# when no supplier is configured, returns to the company
|
||||
company = claim_line.claim_id.company_id
|
||||
return_address = (company.crm_return_address_id or
|
||||
company.partner_id)
|
||||
return_address_id = return_address.id
|
||||
return_type = 'company'
|
||||
|
||||
location_dest_id = self.get_destination_location(
|
||||
cr, uid, claim_line.product_id.id,
|
||||
claim_line.claim_id.warehouse_id.id,
|
||||
context=context)
|
||||
self.write(cr, uid, ids,
|
||||
{'warranty_return_partner': return_address_id,
|
||||
'warranty_type': return_type,
|
||||
'location_dest_id': location_dest_id},
|
||||
context=context)
|
||||
cr, uid, product.id, warehouse.id, context=context)
|
||||
return {'warranty_return_partner': return_address_id,
|
||||
'warranty_type': return_type,
|
||||
'location_dest_id': location_dest_id}
|
||||
|
||||
def set_warranty_return_address(self, cr, uid, ids, claim_line,
|
||||
context=None):
|
||||
claim = claim_line.claim_id
|
||||
product = claim_line.product_id
|
||||
company = claim.company_id
|
||||
warehouse = claim.warehouse_id
|
||||
values = self._warranty_return_address_values(
|
||||
cr, uid, ids, product, company, warehouse, context=context)
|
||||
self.write(cr, uid, ids, values, context=context)
|
||||
return True
|
||||
|
||||
def set_warranty(self, cr, uid, ids, context=None):
|
||||
@@ -340,7 +423,8 @@ class crm_claim(orm.Model):
|
||||
|
||||
def init(self, cr):
|
||||
cr.execute("""
|
||||
UPDATE "crm_claim" SET "number"=id::varchar WHERE ("number" is NULL)
|
||||
UPDATE "crm_claim" SET "number"=id::varchar
|
||||
WHERE ("number" is NULL)
|
||||
OR ("number" = '/');
|
||||
""")
|
||||
|
||||
@@ -457,10 +541,14 @@ class crm_claim(orm.Model):
|
||||
return res
|
||||
|
||||
def onchange_invoice_id(self, cr, uid, ids, invoice_id, warehouse_id,
|
||||
context=None):
|
||||
claim_type, claim_date, company_id, lines,
|
||||
create_lines=False, context=None):
|
||||
invoice_line_obj = self.pool.get('account.invoice.line')
|
||||
invoice_obj = self.pool.get('account.invoice')
|
||||
product_obj = self.pool['product.product']
|
||||
claim_line_obj = self.pool.get('claim.line')
|
||||
company_obj = self.pool['res.company']
|
||||
warehouse_obj = self.pool['stock.warehouse']
|
||||
invoice_line_ids = invoice_line_obj.search(
|
||||
cr, uid,
|
||||
[('invoice_id', '=', invoice_id)],
|
||||
@@ -472,21 +560,89 @@ class crm_claim(orm.Model):
|
||||
context=context)
|
||||
invoice_lines = invoice_line_obj.browse(cr, uid, invoice_line_ids,
|
||||
context=context)
|
||||
for invoice_line in invoice_lines:
|
||||
product_id = invoice_line.product_id and invoice_line.product_id.id or False
|
||||
location_dest_id = claim_line_obj.get_destination_location(
|
||||
cr, uid, product_id,
|
||||
warehouse_id, context=context)
|
||||
claim_lines.append({
|
||||
'name': invoice_line.name,
|
||||
'claim_origine': "none",
|
||||
'invoice_line_id': invoice_line.id,
|
||||
'product_id': product_id,
|
||||
'product_returned_quantity': invoice_line.quantity,
|
||||
'unit_sale_price': invoice_line.price_unit,
|
||||
'location_dest_id': location_dest_id,
|
||||
'state': 'draft',
|
||||
})
|
||||
|
||||
def warranty_values(invoice, product):
|
||||
values = {}
|
||||
try:
|
||||
warranty = claim_line_obj._warranty_limit_values(
|
||||
cr, uid, [], invoice,
|
||||
claim_type, product,
|
||||
claim_date, context=context)
|
||||
except (InvoiceNoDate, ProductNoSupplier):
|
||||
# we don't mind at this point if the warranty can't be
|
||||
# computed and we don't want to block the user
|
||||
values.update({'guarantee_limit': False, 'warning': False})
|
||||
else:
|
||||
values.update(warranty)
|
||||
company = company_obj.browse(cr, uid, company_id, context=context)
|
||||
warehouse = warehouse_obj.browse(cr, uid, warehouse_id,
|
||||
context=context)
|
||||
warranty_address = claim_line_obj._warranty_return_address_values(
|
||||
cr, uid, [], product, company,
|
||||
warehouse, context=context)
|
||||
values.update(warranty_address)
|
||||
return values
|
||||
|
||||
if create_lines: # happens when the invoice is changed
|
||||
for invoice_line in invoice_lines:
|
||||
location_dest_id = claim_line_obj.get_destination_location(
|
||||
cr, uid, invoice_line.product_id.id,
|
||||
warehouse_id, context=context)
|
||||
line = {
|
||||
'name': invoice_line.name,
|
||||
'claim_origine': "none",
|
||||
'invoice_line_id': invoice_line.id,
|
||||
'product_id': invoice_line.product_id.id,
|
||||
'product_returned_quantity': invoice_line.quantity,
|
||||
'unit_sale_price': invoice_line.price_unit,
|
||||
'location_dest_id': location_dest_id,
|
||||
'state': 'draft',
|
||||
}
|
||||
line.update(warranty_values(invoice_line.invoice_id,
|
||||
invoice_line.product_id))
|
||||
claim_lines.append(line)
|
||||
elif lines: # happens when the date, warehouse or claim type is
|
||||
# modified
|
||||
for command in lines:
|
||||
code = command[0]
|
||||
assert code != 6, "command 6 not supported in on_change"
|
||||
if code in (0, 1, 4):
|
||||
# 0: link a new record with values
|
||||
# 1: update an existing record with values
|
||||
# 4: link to existing record
|
||||
line_id = command[1]
|
||||
if code == 4:
|
||||
code = 1 # we want now to update values
|
||||
values = {}
|
||||
else:
|
||||
values = command[2]
|
||||
invoice_line_id = values.get('invoice_line_id')
|
||||
product_id = values.get('product_id')
|
||||
if code == 1: # get the existing line
|
||||
# if the fields have not changed, fallback
|
||||
# on the database values
|
||||
browse_line = claim_line_obj.read(cr, uid,
|
||||
line_id,
|
||||
['invoice_line_id',
|
||||
'product_id'],
|
||||
context=context)
|
||||
if not invoice_line_id:
|
||||
invoice_line_id = browse_line['invoice_line_id'][0]
|
||||
if not product_id:
|
||||
product_id = browse_line['product_id'][0]
|
||||
|
||||
if invoice_line_id and product_id:
|
||||
invoice_line = invoice_line_obj.browse(cr, uid,
|
||||
invoice_line_id,
|
||||
context=context)
|
||||
product = product_obj.browse(cr, uid, product_id,
|
||||
context=context)
|
||||
values.update(warranty_values(invoice_line.invoice_id,
|
||||
product))
|
||||
claim_lines.append((code, line_id, values))
|
||||
elif code in (2, 3, 5):
|
||||
claim_lines.append(command)
|
||||
|
||||
value = {'claim_line_ids': claim_lines}
|
||||
delivery_address_id = False
|
||||
if invoice_id:
|
||||
|
||||
@@ -92,14 +92,16 @@
|
||||
<group>
|
||||
<group string="Returned good">
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, claim_id, False, False, False, False, context)"/>
|
||||
<field name="prodlot_id"/>
|
||||
<field name="unit_sale_price"/>
|
||||
<field name="return_value"/>
|
||||
</group>
|
||||
<group string="Linked Document">
|
||||
<field name="claim_id" />
|
||||
<field name="invoice_line_id"/>
|
||||
<field name="invoice_line_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, claim_id, False, False, False, False, context)"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
@@ -165,13 +167,63 @@
|
||||
<group name="Product Return">
|
||||
<separator string="Product Return" colspan="4"/>
|
||||
<group>
|
||||
<field name="invoice_id" on_change="onchange_invoice_id(invoice_id,warehouse_id,context)" domain="['|',('commercial_partner_id','=',partner_id),('partner_id','=',partner_id)]" />
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="invoice_id" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, True, context)" domain="['|',('commercial_partner_id','=',partner_id),('partner_id','=',partner_id)]" />
|
||||
<field name="delivery_address_id" context="{'tree_view_ref': 'crm_claim_rma.view_partner_contact_tree', 'search_default_parent_id': partner_id}"/>
|
||||
</group>
|
||||
<group>
|
||||
<!-- Place for mass return button from crm_rma_lot_mass_return -->
|
||||
</group>
|
||||
<field name="claim_line_ids" nolabel="1" colspan="4" editable="top"/>
|
||||
<field name="claim_line_ids" nolabel="1" colspan="4" editable="top">
|
||||
<form string="Claim Line" version="7.0">
|
||||
<header>
|
||||
<button name="set_warranty" string="Calculate warranty state" type="object" class="oe_highlight"/>
|
||||
</header>
|
||||
<sheet string="Claims">
|
||||
<div class="oe_title">
|
||||
<group>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</group>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Returned good">
|
||||
<field name="product_returned_quantity"/>
|
||||
<field name="product_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, False, parent.company_id, parent.warehouse_id, parent.claim_type, parent.date, context)"/>
|
||||
<field name="prodlot_id"/>
|
||||
<field name="unit_sale_price"/>
|
||||
<field name="return_value"/>
|
||||
</group>
|
||||
<group string="Linked Document">
|
||||
<field name="invoice_line_id"
|
||||
on_change="onchange_product_id(product_id, invoice_line_id, False, parent.company_id, parent.warehouse_id, parent.claim_type, parent.date, context)"/>
|
||||
<field name="refund_line_id"/>
|
||||
<field name="move_in_id"/>
|
||||
<field name="move_out_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Problem">
|
||||
<field name="claim_origine" nolabel="1" colspan="4"/>
|
||||
<field name="claim_descr" nolabel="1" colspan="4"/>
|
||||
</group>
|
||||
<group string="Warranty">
|
||||
<field name="guarantee_limit"/>
|
||||
<field name="warning"/>
|
||||
<field name="warranty_return_partner"/>
|
||||
<field name="warranty_type"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="State" colspan="4"/>
|
||||
<group col="6" colspan="4">
|
||||
<field name="state"/>
|
||||
<field name="substate_id" widget='selection' />
|
||||
<field name="last_state_change"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</group>
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('state', '<>','open')]}">
|
||||
<separator string="Action" colspan="4" />
|
||||
@@ -264,8 +316,8 @@
|
||||
<field name="inherit_id" ref="crm_claim.crm_case_claims_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="date_deadline" position="after">
|
||||
<field name="claim_type"/>
|
||||
<field name="warehouse_id" />
|
||||
<field name="claim_type" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, False, context)"/>
|
||||
<field name="warehouse_id" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, False, context)"/>
|
||||
</field>
|
||||
<field name="name" position="replace">
|
||||
<div class="oe_title">
|
||||
@@ -277,7 +329,7 @@
|
||||
</field>
|
||||
<field name="user_id" position="before">
|
||||
<field name="name" />
|
||||
<field name="date"/>
|
||||
<field name="date" on_change="onchange_invoice_id(invoice_id, warehouse_id, claim_type, date, company_id, claim_line_ids, False, context)"/>
|
||||
</field>
|
||||
<xpath expr="//sheet[@string='Claims']/group[1]" position="inside">
|
||||
<div class="oe_right oe_button_box" name="buttons">
|
||||
|
||||
@@ -78,7 +78,8 @@ class test_lp_1282584(common.TransactionCase):
|
||||
|
||||
res = self.WizardMakePicking.action_create_picking(
|
||||
cr, uid, [wizard_id], context=wiz_context)
|
||||
self.assertEquals(res.get('res_model'), 'stock.picking.in', "Wrong model defined")
|
||||
self.assertEquals(res.get('res_model'), 'stock.picking.in',
|
||||
"Wrong model defined")
|
||||
|
||||
def test_01(self):
|
||||
"""Test wizard opened view model for a new delivery
|
||||
@@ -92,7 +93,9 @@ class test_lp_1282584(common.TransactionCase):
|
||||
wizard_chg_qty_id = WizardChangeProductQty.create(cr, uid, {
|
||||
'product_id': self.product_id,
|
||||
'new_quantity': 12})
|
||||
WizardChangeProductQty.change_product_qty(cr, uid, [wizard_chg_qty_id], context=wiz_context)
|
||||
WizardChangeProductQty.change_product_qty(cr, uid,
|
||||
[wizard_chg_qty_id],
|
||||
context=wiz_context)
|
||||
|
||||
wiz_context = {
|
||||
'active_id': self.claim_id,
|
||||
@@ -105,4 +108,5 @@ class test_lp_1282584(common.TransactionCase):
|
||||
|
||||
res = self.WizardMakePicking.action_create_picking(
|
||||
cr, uid, [wizard_id], context=wiz_context)
|
||||
self.assertEquals(res.get('res_model'), 'stock.picking.out', "Wrong model defined")
|
||||
self.assertEquals(res.get('res_model'), 'stock.picking.out',
|
||||
"Wrong model defined")
|
||||
|
||||
@@ -223,14 +223,15 @@ class claim_make_picking(orm.TransientModel):
|
||||
},
|
||||
context=context)
|
||||
# Create picking lines
|
||||
fmt = DEFAULT_SERVER_DATETIME_FORMAT
|
||||
for wizard_claim_line in wizard.claim_line_ids:
|
||||
move_obj = self.pool.get('stock.move')
|
||||
move_id = move_obj.create(
|
||||
cr, uid,
|
||||
{'name': wizard_claim_line.product_id.name_template,
|
||||
'priority': '0',
|
||||
'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'date_expected': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'date': time.strftime(fmt),
|
||||
'date_expected': time.strftime(fmt),
|
||||
'product_id': wizard_claim_line.product_id.id,
|
||||
'product_qty': wizard_claim_line.product_returned_quantity,
|
||||
'product_uom': wizard_claim_line.product_id.uom_id.id,
|
||||
|
||||
Reference in New Issue
Block a user