add management commands for invoice due date
This commit is contained in:
parent
65d7e8c685
commit
87dabb97cc
134
inventory/management/commands/invoices_due_date_reminder.py
Normal file
134
inventory/management/commands/invoices_due_date_reminder.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
from inventory.tasks import send_email
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django_ledger.models import InvoiceModel,EstimateModel
|
||||||
|
from inventory.models import ExtraInfo,Notification,CustomGroup
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Handles invoices due date reminders"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.stdout.write("Starting invoices due date reminders..")
|
||||||
|
|
||||||
|
# 1. Send expiration reminders
|
||||||
|
self.invocie_expiration_reminders()
|
||||||
|
# self.invoice_past_due()
|
||||||
|
|
||||||
|
# # 2. Deactivate expired plans
|
||||||
|
# self.deactivate_expired_plans()
|
||||||
|
|
||||||
|
# # 3. Clean up old incomplete orders
|
||||||
|
# self.cleanup_old_orders()
|
||||||
|
|
||||||
|
# self.stdout.write("Reminders completed!")
|
||||||
|
|
||||||
|
def invocie_expiration_reminders(self):
|
||||||
|
"""Queue email reminders for expiring plans"""
|
||||||
|
reminder_days = getattr(settings, 'INVOICE_PAST_DUE_REMIND', [3, 7, 14])
|
||||||
|
today = timezone.now().date()
|
||||||
|
|
||||||
|
for days in reminder_days:
|
||||||
|
target_date = today + timedelta(days=days)
|
||||||
|
expiring_plans = InvoiceModel.objects.filter(
|
||||||
|
date_due=target_date
|
||||||
|
).select_related('customer','ce_model')
|
||||||
|
|
||||||
|
for inv in expiring_plans:
|
||||||
|
# dealer = inv.customer.customer_set.first().dealer
|
||||||
|
subject = f"Your invoice is due in {days} days"
|
||||||
|
message = render_to_string('emails/invoice_past_due_reminder.txt', {
|
||||||
|
'customer_name': inv.customer.customer_name,
|
||||||
|
'invoice_number': inv.invoice_number,
|
||||||
|
'amount_due': inv.amount_due,
|
||||||
|
'days_past_due': inv.due_in_days(),
|
||||||
|
'SITE_NAME': settings.SITE_NAME
|
||||||
|
})
|
||||||
|
send_email(
|
||||||
|
'noreply@yourdomain.com',
|
||||||
|
inv.customer.email,
|
||||||
|
subject,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stdout.write(f"Queuing {expiring_plans} reminders for {target_date}")
|
||||||
|
|
||||||
|
def invoice_past_due(self):
|
||||||
|
"""Queue email reminders for expiring plans"""
|
||||||
|
today = timezone.now().date()
|
||||||
|
expiring_plans = InvoiceModel.objects.filter(
|
||||||
|
date_due__lte = today
|
||||||
|
).select_related('customer','ce_model')
|
||||||
|
|
||||||
|
# Send email
|
||||||
|
for inv in expiring_plans:
|
||||||
|
dealer = inv.customer.customer_set.first().dealer
|
||||||
|
|
||||||
|
subject = f"Your invoice is past due"
|
||||||
|
message = render_to_string('emails/invoice_past_due.txt', {
|
||||||
|
'customer_name': inv.customer.customer_name,
|
||||||
|
'invoice_number': inv.invoice_number,
|
||||||
|
'amount_due': inv.amount_due,
|
||||||
|
'days_past_due': (today - inv.date_due).days,
|
||||||
|
'SITE_NAME': settings.SITE_NAME
|
||||||
|
})
|
||||||
|
|
||||||
|
# send notification to accountatnt
|
||||||
|
recipients = (
|
||||||
|
CustomGroup.objects.filter(dealer=dealer, name="Accountant")
|
||||||
|
.first()
|
||||||
|
.group.user_set.exclude(email=dealer.user.email)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
for rec in recipients:
|
||||||
|
Notification.objects.create(
|
||||||
|
user=rec,
|
||||||
|
message=_(
|
||||||
|
"""
|
||||||
|
Invoice {invoice_number} is past due,please your
|
||||||
|
<a href="{url}" target="_blank">View</a>.
|
||||||
|
"""
|
||||||
|
).format(
|
||||||
|
invoice_number=inv.invoice_number,
|
||||||
|
url=reverse(
|
||||||
|
"invoice_detail",
|
||||||
|
kwargs={"dealer_slug": dealer.slug, "entity_slug": dealer.entity.slug, "pk": inv.pk},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# send email to customer
|
||||||
|
send_email(
|
||||||
|
'noreply@yourdomain.com',
|
||||||
|
inv.customer.email,
|
||||||
|
subject,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stdout.write(f"Queuing {expiring_plans} reminders for {today}")
|
||||||
|
|
||||||
|
# def deactivate_expired_plans(self):
|
||||||
|
# """Deactivate plans that have expired (synchronous)"""
|
||||||
|
# expired_plans = UserPlan.objects.filter(
|
||||||
|
# active=True,
|
||||||
|
# expire__lt=timezone.now().date()
|
||||||
|
# )
|
||||||
|
# count = expired_plans.update(active=False)
|
||||||
|
# self.stdout.write(f"Deactivated {count} expired plans")
|
||||||
|
|
||||||
|
# def cleanup_old_orders(self):
|
||||||
|
# """Delete incomplete orders older than 30 days"""
|
||||||
|
# cutoff = timezone.now() - timedelta(days=30)
|
||||||
|
# count, _ = Order.objects.filter(
|
||||||
|
# created__lt=cutoff,
|
||||||
|
# status=Order.STATUS.NEW
|
||||||
|
# ).delete()
|
||||||
|
# self.stdout.write(f"Cleaned up {count} old incomplete orders")
|
||||||
@ -10921,22 +10921,22 @@ def car_sale_report_csv_export(request,dealer_slug):
|
|||||||
writer = csv.writer(response)
|
writer = csv.writer(response)
|
||||||
|
|
||||||
header=[
|
header=[
|
||||||
'Make',
|
'Make',
|
||||||
'VIN',
|
'VIN',
|
||||||
'Model',
|
'Model',
|
||||||
'Year',
|
'Year',
|
||||||
'Serie',
|
'Serie',
|
||||||
'Trim',
|
'Trim',
|
||||||
'Mileage',
|
'Mileage',
|
||||||
'Stock Type',
|
'Stock Type',
|
||||||
'Created Date',
|
'Created Date',
|
||||||
'Sold Date',
|
'Sold Date',
|
||||||
'Cost Price',
|
'Cost Price',
|
||||||
'Marked Price',
|
'Marked Price',
|
||||||
'Discount Amount',
|
'Discount Amount',
|
||||||
'Selling Price',
|
'Selling Price',
|
||||||
'Tax Amount',
|
'Tax Amount',
|
||||||
'Invoice Number',
|
'Invoice Number',
|
||||||
]
|
]
|
||||||
writer.writerow(header)
|
writer.writerow(header)
|
||||||
|
|
||||||
@ -11007,11 +11007,9 @@ class RecallDetailView(DetailView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def RecallFilterView(request):
|
def RecallFilterView(request):
|
||||||
context = {'make_data': models.CarMake.objects.all()}
|
context = {'make_data': models.CarMake.objects.all()}
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
print(request.POST)
|
|
||||||
make = request.POST.get('make')
|
make = request.POST.get('make')
|
||||||
model = request.POST.get('model')
|
model = request.POST.get('model')
|
||||||
serie = request.POST.get('serie')
|
serie = request.POST.get('serie')
|
||||||
|
|||||||
10
templates/emails/invoice_past_due.txt
Normal file
10
templates/emails/invoice_past_due.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Hello {{ customer_name }},
|
||||||
|
|
||||||
|
This is a friendly reminder that your invoice for {{ invoice_number }} is now {{ days_past_due }} days past due.
|
||||||
|
|
||||||
|
Please settle your outstanding balance of {{ amount_due }} .
|
||||||
|
|
||||||
|
If you have already paid, please disregard this notice.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
{{ SITE_NAME }} Team
|
||||||
11
templates/emails/invoice_past_due_reminder.txt
Normal file
11
templates/emails/invoice_past_due_reminder.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Hello {{ customer_name }},
|
||||||
|
|
||||||
|
This is a friendly reminder that your invoice for {{ invoice_number }} is due in {{ days_past_due }} days.
|
||||||
|
|
||||||
|
Please settle your outstanding balance of {{ amount_due }} before the due date to avoid any late fees.
|
||||||
|
|
||||||
|
If you have already paid, please disregard this notice.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
{{ SITE_NAME }} Team
|
||||||
|
|
||||||
@ -492,7 +492,7 @@
|
|||||||
{% if request.is_dealer %}
|
{% if request.is_dealer %}
|
||||||
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
|
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h6 class="mt-2 text-body-emphasis">{{ user.staffmember.staff.get_local_name }}</h6>
|
<h6 class="mt-2 text-body-emphasis">{{ user.staff.get_local_name }}</h6>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load tenhal_tag %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ _("Car Sale Report") |capfirst }}
|
{{ _("Car Sale Report") |capfirst }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
@ -98,8 +99,8 @@
|
|||||||
<div class="col-md-6 col-lg-3">
|
<div class="col-md-6 col-lg-3">
|
||||||
<div class="card summary-card">
|
<div class="card summary-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{% trans 'Total Revenue' %}<i class="fas fa-dollar-sign ms-2"></i></h5>
|
<h5 class="card-title">{% trans 'Total Revenue' %}<span class="icon-saudi_riyal"></span></h5>
|
||||||
<p class="card-text">{{ total_revenue|floatformat:2 }}</p>
|
<p class="card-text">{{ total_revenue|floatformat:2 }} <span class="icon-saudi_riyal"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -107,7 +108,7 @@
|
|||||||
<div class="card summary-card">
|
<div class="card summary-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{% trans 'Total VAT Amount' %}<i class="fas fa-percent ms-2"></i></h5>
|
<h5 class="card-title">{% trans 'Total VAT Amount' %}<i class="fas fa-percent ms-2"></i></h5>
|
||||||
<p class="card-text">{{ 10000|floatformat:2 }}</p>
|
<p class="card-text">{{ 10000|floatformat:2 }} <span class="icon-saudi_riyal"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -115,7 +116,7 @@
|
|||||||
<div class="card summary-card">
|
<div class="card summary-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{% trans 'Total Discount Amount' %}<i class="fas fa-tag ms-2"></i></h5>
|
<h5 class="card-title">{% trans 'Total Discount Amount' %}<i class="fas fa-tag ms-2"></i></h5>
|
||||||
<p class="card-text">{{ total_discount|floatformat:2 }}</p>
|
<p class="card-text">{{ total_discount|floatformat:2 }} <span class="icon-saudi_riyal"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -174,14 +175,14 @@
|
|||||||
<td class="fs-9">{{ car.id_car_serie.name }}</td>
|
<td class="fs-9">{{ car.id_car_serie.name }}</td>
|
||||||
<td class="fs-9">{{ car.id_car_trim.name }}</td>
|
<td class="fs-9">{{ car.id_car_trim.name }}</td>
|
||||||
<td class="fs-9">{{ car.mileage }}</td>
|
<td class="fs-9">{{ car.mileage }}</td>
|
||||||
<td class="fs-9">{{ car.stock_type }}</td>
|
<td class="fs-9">{{ car.stock_type|capfirst }}</td>
|
||||||
<td class="fs-9">{{ car.created_at|date }}</td>
|
<td class="fs-9">{{ car.created_at|date }}</td>
|
||||||
<td class="fs-9">{{ car.invoice.date_paid|date|default_if_none:"-" }}</td>
|
<td class="fs-9">{{ car.invoice.date_paid|date|default_if_none:"-" }}</td>
|
||||||
<td class="fs-9">{{ car.finances.cost_price }}</td>
|
<td class="fs-9">{{ car.finances.cost_price }} <span class="icon-saudi_riyal"></span></td>
|
||||||
<td class="fs-9">{{ car.finances.marked_price }}</td>
|
<td class="fs-9">{{ car.finances.marked_price }} <span class="icon-saudi_riyal"></span></td>
|
||||||
<td class="fs-9">{{ car.finances.discount_amount }}</td>
|
<td class="fs-9">{{ car.finances.discount_amount }} <span class="icon-saudi_riyal"></span></td>
|
||||||
<td class="fs-9">{{ car.finances.selling_price }}</td>
|
<td class="fs-9">{{ car.finances.selling_price }} <span class="icon-saudi_riyal"></span></td>
|
||||||
<td class="fs-9">{{ car.finances.vat_amount }}</td>
|
<td class="fs-9">{{ car.finances.vat_amount }} <span class="icon-saudi_riyal"></span></td>
|
||||||
<td class="fs-9">{{ car.invoice.invoice_number }}</td>
|
<td class="fs-9">{{ car.invoice.invoice_number }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -256,9 +256,10 @@
|
|||||||
<tr class="bg-body-secondary total-sum">
|
<tr class="bg-body-secondary total-sum">
|
||||||
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Discount Amount" %}</td>
|
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Discount Amount" %}</td>
|
||||||
<td class="align-middle text-start text-danger fw-semibold">
|
<td class="align-middle text-start text-danger fw-semibold">
|
||||||
<form action="{% url 'update_estimate_discount' request.dealer.slug estimate.pk %}"
|
{% if estimate.is_draft %}
|
||||||
method="post">
|
<form action="{% url 'update_estimate_discount' request.dealer.slug estimate.pk %}"
|
||||||
{% csrf_token %}
|
method="post">
|
||||||
|
{% csrf_token %}
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="number"
|
<input type="number"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
@ -266,10 +267,13 @@
|
|||||||
value="{{ data.total_discount }}"
|
value="{{ data.total_discount }}"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
style="width: 1px">
|
style="width: 1px">
|
||||||
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
<span class="input-group-text"><span class="icon-saudi_riyal"></span></span>
|
||||||
<button type="submit" class="btn btn-sm btn-phoenix-primary ms-n2">{% trans "Update" %}</button>
|
<button type="submit" class="btn btn-sm btn-phoenix-primary ms-n2">{% trans "Update" %}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% else %}
|
||||||
|
{{ data.total_discount }} <span class="icon-saudi_riyal"></span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="bg-body-secondary total-sum">
|
<tr class="bg-body-secondary total-sum">
|
||||||
@ -285,12 +289,14 @@
|
|||||||
<small><span class="fw-semibold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
|
<small><span class="fw-semibold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
|
||||||
<br>
|
<br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if estimate.is_draft %}
|
||||||
<button class="btn btn-phoenix-primary btn-xs ms-auto"
|
<button class="btn btn-phoenix-primary btn-xs ms-auto"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#additionalModal">
|
data-bs-target="#additionalModal">
|
||||||
<span class="fas fa-plus me-1"></span>{{ _("Add") }}
|
<span class="fas fa-plus me-1"></span>{{ _("Add") }}
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="bg-body-secondary total-sum">
|
<tr class="bg-body-secondary total-sum">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load custom_filters %}
|
{% load custom_filters %}
|
||||||
|
{% load tenhal_tag %}
|
||||||
{% block title %}{{ page_title }} - {{ sale_order.formatted_order_id }}{% endblock %}
|
{% block title %}{{ page_title }} - {{ sale_order.formatted_order_id }}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
@ -17,9 +18,9 @@
|
|||||||
<!-- Basic Information -->
|
<!-- Basic Information -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<h4>{% trans "Customer Information" %}</h4>
|
<h4 class="mb-3">{% trans "Customer Information" %}</h4>
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Name" %}:</strong> {{ sale_order.full_name }}
|
<strong>{% trans "Name" %}:</strong> {{ sale_order.full_name|title }}
|
||||||
<br>
|
<br>
|
||||||
{% if sale_order.customer %}
|
{% if sale_order.customer %}
|
||||||
<strong>{% trans "Contact" %}:</strong> {{ sale_order.customer.phone_number }}
|
<strong>{% trans "Contact" %}:</strong> {{ sale_order.customer.phone_number }}
|
||||||
@ -29,7 +30,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<h4>{% trans "Order Details" %}</h4>
|
<h4 class="mb-3">{% trans "Order Details" %}</h4>
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Order Date" %}:</strong> {{ sale_order.order_date|date }}
|
<strong>{% trans "Order Date" %}:</strong> {{ sale_order.order_date|date }}
|
||||||
<br>
|
<br>
|
||||||
@ -187,7 +188,7 @@
|
|||||||
<strong>{% trans "Amount Paid" %}:</strong>
|
<strong>{% trans "Amount Paid" %}:</strong>
|
||||||
</td>
|
</td>
|
||||||
<td style="padding-left: 0.5rem;">
|
<td style="padding-left: 0.5rem;">
|
||||||
<span class="currency">{{ CURRENCY }}</span>{{ sale_order.invoice.amount_paid|floatformat:2 }}
|
<span class="icon-saudi_riyal"></span>{{ sale_order.invoice.amount_paid|floatformat:2 }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -195,7 +196,7 @@
|
|||||||
<strong>{% trans "Balance Due" %}:</strong>
|
<strong>{% trans "Balance Due" %}:</strong>
|
||||||
</td>
|
</td>
|
||||||
<td style="padding-left: 0.5rem;">
|
<td style="padding-left: 0.5rem;">
|
||||||
<span class="currency">{{ CURRENCY }}</span>{{ sale_order.invoice.amount_due|floatformat:2 }}
|
<span class="icon-saudi_riyal"></span>{{ sale_order.invoice.amount_due|floatformat:2 }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -203,7 +204,7 @@
|
|||||||
<strong>{% trans "Amount Unearned" %}:</strong>
|
<strong>{% trans "Amount Unearned" %}:</strong>
|
||||||
</td>
|
</td>
|
||||||
<td style="padding-left: 0.5rem;">
|
<td style="padding-left: 0.5rem;">
|
||||||
<span class="currency">{{ CURRENCY }}</span>{{ sale_order.invoice.amount_unearned|floatformat:2 }}
|
<span class="icon-saudi_riyal"></span>{{ sale_order.invoice.amount_unearned|floatformat:2 }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -211,7 +212,7 @@
|
|||||||
<strong>{% trans "Amount Receivable" %}:</strong>
|
<strong>{% trans "Amount Receivable" %}:</strong>
|
||||||
</td>
|
</td>
|
||||||
<td style="padding-left: 0.5rem;">
|
<td style="padding-left: 0.5rem;">
|
||||||
<span class="currency">{{ CURRENCY }}</span>{{ sale_order.invoice.amount_receivable|floatformat:2 }}
|
<span class="icon-saudi_riyal"></span>{{ sale_order.invoice.amount_receivable|floatformat:2 }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user