完善保险端的前后端
This commit is contained in:
@@ -51,6 +51,23 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
insurance_category: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: true,
|
||||
comment: '参保类型(如:牛、羊、猪等)',
|
||||
validate: {
|
||||
len: [1, 50]
|
||||
}
|
||||
},
|
||||
application_quantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 1,
|
||||
comment: '申请数量',
|
||||
validate: {
|
||||
min: 1
|
||||
}
|
||||
},
|
||||
application_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
@@ -59,8 +76,16 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
|
||||
}
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'under_review'),
|
||||
defaultValue: 'pending'
|
||||
type: DataTypes.ENUM(
|
||||
'pending', // 待初审
|
||||
'initial_approved', // 初审通过待复核
|
||||
'under_review', // 已支付待复核
|
||||
'approved', // 已支付
|
||||
'rejected', // 已拒绝
|
||||
'paid' // 已支付
|
||||
),
|
||||
defaultValue: 'pending',
|
||||
comment: '申请状态'
|
||||
},
|
||||
application_date: {
|
||||
type: DataTypes.DATE,
|
||||
@@ -71,6 +96,11 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
remarks: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注信息'
|
||||
},
|
||||
reviewer_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
@@ -99,6 +129,8 @@ const InsuranceApplication = sequelize.define('InsuranceApplication', {
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['application_date'] },
|
||||
{ fields: ['insurance_type_id'] },
|
||||
{ fields: ['insurance_category'] },
|
||||
{ fields: ['application_quantity'] },
|
||||
{ fields: ['reviewer_id'] }
|
||||
]
|
||||
});
|
||||
|
||||
@@ -11,17 +11,19 @@ const InsuranceType = sequelize.define('InsuranceType', {
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '保险类型名称',
|
||||
comment: '险种名称',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '保险类型名称不能为空'
|
||||
msg: '险种名称不能为空'
|
||||
},
|
||||
len: {
|
||||
args: [1, 100],
|
||||
msg: '保险类型名称长度必须在1-100个字符之间'
|
||||
msg: '险种名称长度必须在1-100个字符之间'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
|
||||
202
insurance_backend/models/LivestockClaim.js
Normal file
202
insurance_backend/models/LivestockClaim.js
Normal file
@@ -0,0 +1,202 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const LivestockClaim = sequelize.define('LivestockClaim', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
comment: '生资理赔ID'
|
||||
},
|
||||
claim_no: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '理赔编号',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '理赔编号不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
policy_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '关联的保单ID',
|
||||
references: {
|
||||
model: 'livestock_policies',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
claim_type: {
|
||||
type: DataTypes.ENUM('disease', 'natural_disaster', 'accident', 'theft', 'other'),
|
||||
allowNull: false,
|
||||
comment: '理赔类型:disease-疾病,natural_disaster-自然灾害,accident-意外事故,theft-盗窃,other-其他'
|
||||
},
|
||||
incident_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '事故发生日期'
|
||||
},
|
||||
report_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '报案日期'
|
||||
},
|
||||
incident_description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
comment: '事故描述',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '事故描述不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
incident_location: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: false,
|
||||
comment: '事故地点',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '事故地点不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
affected_count: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '受影响牲畜数量',
|
||||
validate: {
|
||||
min: {
|
||||
args: [1],
|
||||
msg: '受影响牲畜数量不能小于1'
|
||||
}
|
||||
}
|
||||
},
|
||||
claim_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '理赔金额',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '理赔金额不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
claim_status: {
|
||||
type: DataTypes.ENUM('pending', 'investigating', 'approved', 'rejected', 'paid'),
|
||||
allowNull: false,
|
||||
defaultValue: 'pending',
|
||||
comment: '理赔状态:pending-待审核,investigating-调查中,approved-已批准,rejected-已拒绝,paid-已支付'
|
||||
},
|
||||
investigator_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '调查员ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
investigation_notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '调查备注'
|
||||
},
|
||||
investigation_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '调查日期'
|
||||
},
|
||||
reviewer_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '审核人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
review_notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '审核备注'
|
||||
},
|
||||
review_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '审核日期'
|
||||
},
|
||||
settlement_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: true,
|
||||
comment: '实际赔付金额',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '实际赔付金额不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
settlement_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '赔付日期'
|
||||
},
|
||||
supporting_documents: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
comment: '支持文件(JSON数组,包含文件URL和描述)'
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'livestock_claims',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
indexes: [
|
||||
{
|
||||
name: 'idx_livestock_claim_no',
|
||||
fields: ['claim_no'],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_claim_policy',
|
||||
fields: ['policy_id']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_claim_type',
|
||||
fields: ['claim_type']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_claim_status',
|
||||
fields: ['claim_status']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_claim_dates',
|
||||
fields: ['incident_date', 'report_date']
|
||||
}
|
||||
],
|
||||
comment: '生资理赔表'
|
||||
});
|
||||
|
||||
module.exports = LivestockClaim;
|
||||
217
insurance_backend/models/LivestockPolicy.js
Normal file
217
insurance_backend/models/LivestockPolicy.js
Normal file
@@ -0,0 +1,217 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const LivestockPolicy = sequelize.define('LivestockPolicy', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
comment: '生资保单ID'
|
||||
},
|
||||
policy_no: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '保单编号',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '保单编号不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
livestock_type_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '牲畜类型ID',
|
||||
references: {
|
||||
model: 'livestock_types',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
policyholder_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
comment: '投保人姓名',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '投保人姓名不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
policyholder_id_card: {
|
||||
type: DataTypes.STRING(18),
|
||||
allowNull: false,
|
||||
comment: '投保人身份证号',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '投保人身份证号不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
policyholder_phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
comment: '投保人手机号',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '投保人手机号不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
policyholder_address: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: false,
|
||||
comment: '投保人地址',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '投保人地址不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
farm_name: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
comment: '养殖场名称'
|
||||
},
|
||||
farm_address: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '养殖场地址'
|
||||
},
|
||||
farm_license: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true,
|
||||
comment: '养殖场许可证号'
|
||||
},
|
||||
livestock_count: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: '投保牲畜数量',
|
||||
validate: {
|
||||
min: {
|
||||
args: [1],
|
||||
msg: '投保牲畜数量不能小于1'
|
||||
}
|
||||
}
|
||||
},
|
||||
unit_value: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
comment: '单头价值',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '单头价值不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
total_value: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '总保额',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '总保额不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
premium_amount: {
|
||||
type: DataTypes.DECIMAL(15, 2),
|
||||
allowNull: false,
|
||||
comment: '保费金额',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '保费金额不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
start_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '保险开始日期'
|
||||
},
|
||||
end_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
comment: '保险结束日期'
|
||||
},
|
||||
policy_status: {
|
||||
type: DataTypes.ENUM('active', 'expired', 'cancelled', 'suspended'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '保单状态:active-有效,expired-已过期,cancelled-已取消,suspended-已暂停'
|
||||
},
|
||||
payment_status: {
|
||||
type: DataTypes.ENUM('paid', 'unpaid', 'partial'),
|
||||
allowNull: false,
|
||||
defaultValue: 'unpaid',
|
||||
comment: '支付状态:paid-已支付,unpaid-未支付,partial-部分支付'
|
||||
},
|
||||
payment_date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
comment: '支付日期'
|
||||
},
|
||||
policy_document_url: {
|
||||
type: DataTypes.STRING(500),
|
||||
allowNull: true,
|
||||
comment: '保单文档URL'
|
||||
},
|
||||
notes: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '备注信息'
|
||||
},
|
||||
created_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '创建人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
updated_by: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: '更新人ID',
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'livestock_policies',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
indexes: [
|
||||
{
|
||||
name: 'idx_livestock_policy_no',
|
||||
fields: ['policy_no'],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_policy_policyholder',
|
||||
fields: ['policyholder_name']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_policy_status',
|
||||
fields: ['policy_status']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_policy_payment_status',
|
||||
fields: ['payment_status']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_policy_dates',
|
||||
fields: ['start_date', 'end_date']
|
||||
}
|
||||
],
|
||||
comment: '生资保单表'
|
||||
});
|
||||
|
||||
module.exports = LivestockPolicy;
|
||||
101
insurance_backend/models/LivestockType.js
Normal file
101
insurance_backend/models/LivestockType.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
|
||||
const LivestockType = sequelize.define('LivestockType', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
comment: '牲畜类型ID'
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '牲畜类型名称',
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: '牲畜类型名称不能为空'
|
||||
}
|
||||
}
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: '牲畜类型代码'
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: '牲畜类型描述'
|
||||
},
|
||||
unit_price_min: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 0.00,
|
||||
comment: '单头最低价值',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '单头最低价值不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
unit_price_max: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
defaultValue: 50000.00,
|
||||
comment: '单头最高价值',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '单头最高价值不能小于0'
|
||||
}
|
||||
}
|
||||
},
|
||||
premium_rate: {
|
||||
type: DataTypes.DECIMAL(5, 4),
|
||||
allowNull: false,
|
||||
defaultValue: 0.0050,
|
||||
comment: '保险费率',
|
||||
validate: {
|
||||
min: {
|
||||
args: [0],
|
||||
msg: '保费费率不能小于0'
|
||||
},
|
||||
max: {
|
||||
args: [1],
|
||||
msg: '保费费率不能大于1'
|
||||
}
|
||||
}
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive'),
|
||||
allowNull: false,
|
||||
defaultValue: 'active',
|
||||
comment: '状态'
|
||||
}
|
||||
}, {
|
||||
tableName: 'livestock_types',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
indexes: [
|
||||
{
|
||||
name: 'idx_livestock_type_name',
|
||||
fields: ['name']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_type_code',
|
||||
fields: ['code']
|
||||
},
|
||||
{
|
||||
name: 'idx_livestock_type_status',
|
||||
fields: ['status']
|
||||
}
|
||||
],
|
||||
comment: '牲畜类型表'
|
||||
});
|
||||
|
||||
module.exports = LivestockType;
|
||||
@@ -9,6 +9,9 @@ const Claim = require('./Claim');
|
||||
const Menu = require('./Menu');
|
||||
const SupervisoryTask = require('./SupervisoryTask');
|
||||
const InstallationTask = require('./InstallationTask');
|
||||
const LivestockType = require('./LivestockType');
|
||||
const LivestockPolicy = require('./LivestockPolicy');
|
||||
const LivestockClaim = require('./LivestockClaim');
|
||||
|
||||
// 定义模型关联关系
|
||||
|
||||
@@ -46,6 +49,26 @@ InsuranceApplication.hasOne(Policy, {
|
||||
as: 'policy'
|
||||
});
|
||||
|
||||
// 保单和客户关联
|
||||
Policy.belongsTo(User, {
|
||||
foreignKey: 'customer_id',
|
||||
as: 'customer'
|
||||
});
|
||||
User.hasMany(Policy, {
|
||||
foreignKey: 'customer_id',
|
||||
as: 'policies'
|
||||
});
|
||||
|
||||
// 保单和保险类型关联
|
||||
Policy.belongsTo(InsuranceType, {
|
||||
foreignKey: 'insurance_type_id',
|
||||
as: 'insurance_type'
|
||||
});
|
||||
InsuranceType.hasMany(Policy, {
|
||||
foreignKey: 'insurance_type_id',
|
||||
as: 'policies'
|
||||
});
|
||||
|
||||
// 理赔和保单关联
|
||||
Claim.belongsTo(Policy, {
|
||||
foreignKey: 'policy_id',
|
||||
@@ -84,6 +107,49 @@ InstallationTask.belongsTo(User, {
|
||||
as: 'updater'
|
||||
});
|
||||
|
||||
// 生资保险相关关联
|
||||
// 注意:牲畜类型表中没有created_by和updated_by字段,所以不定义这些关联
|
||||
|
||||
// 生资保单和牲畜类型关联
|
||||
LivestockPolicy.belongsTo(LivestockType, {
|
||||
foreignKey: 'livestock_type_id',
|
||||
as: 'livestock_type'
|
||||
});
|
||||
LivestockType.hasMany(LivestockPolicy, {
|
||||
foreignKey: 'livestock_type_id',
|
||||
as: 'policies'
|
||||
});
|
||||
|
||||
// 生资保单和用户关联
|
||||
LivestockPolicy.belongsTo(User, {
|
||||
foreignKey: 'created_by',
|
||||
as: 'creator'
|
||||
});
|
||||
LivestockPolicy.belongsTo(User, {
|
||||
foreignKey: 'updated_by',
|
||||
as: 'updater'
|
||||
});
|
||||
|
||||
// 生资理赔和生资保单关联
|
||||
LivestockClaim.belongsTo(LivestockPolicy, {
|
||||
foreignKey: 'policy_id',
|
||||
as: 'policy'
|
||||
});
|
||||
LivestockPolicy.hasMany(LivestockClaim, {
|
||||
foreignKey: 'policy_id',
|
||||
as: 'claims'
|
||||
});
|
||||
|
||||
// 生资理赔和用户关联
|
||||
LivestockClaim.belongsTo(User, {
|
||||
foreignKey: 'created_by',
|
||||
as: 'creator'
|
||||
});
|
||||
LivestockClaim.belongsTo(User, {
|
||||
foreignKey: 'reviewed_by',
|
||||
as: 'reviewer'
|
||||
});
|
||||
|
||||
// 导出所有模型
|
||||
module.exports = {
|
||||
sequelize,
|
||||
@@ -95,5 +161,8 @@ module.exports = {
|
||||
Claim,
|
||||
Menu,
|
||||
SupervisoryTask,
|
||||
InstallationTask
|
||||
InstallationTask,
|
||||
LivestockType,
|
||||
LivestockPolicy,
|
||||
LivestockClaim
|
||||
};
|
||||
Reference in New Issue
Block a user