Merge branch 'dev' of https://git.tuotong-tech.com/tnb/tnb.server into dev
This commit is contained in:
@@ -11,5 +11,10 @@ namespace Tnb.ProductionMgr.Entities.Entity.ErpEntity
|
|||||||
public string ID { get; set; }
|
public string ID { get; set; }
|
||||||
public string CODE { get; set; }
|
public string CODE { get; set; }
|
||||||
public string NAME { get; set; }
|
public string NAME { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1 未启用,2已启用,3已停用
|
||||||
|
/// </summary>
|
||||||
|
public int ENABLESTATE { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,10 @@ namespace Tnb.ProductionMgr.Entities.Entity.ErpEntity
|
|||||||
public string PK_PSNDOC { get; set; }
|
public string PK_PSNDOC { get; set; }
|
||||||
public string CODE { get; set; }
|
public string CODE { get; set; }
|
||||||
public string NAME { get; set; }
|
public string NAME { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1 未启用,2已启用,3已停用
|
||||||
|
/// </summary>
|
||||||
|
public int ENABLESTATE { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,5 +13,10 @@ namespace Tnb.ProductionMgr.Entities.Entity.ErpEntity
|
|||||||
/// 1 erp人员 2 erp用户
|
/// 1 erp人员 2 erp用户
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TYPE { get; set; }
|
public string TYPE { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1 未启用,2已启用,3已停用
|
||||||
|
/// </summary>
|
||||||
|
public int ENABLESTATE { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,21 +493,31 @@ namespace Tnb.ProductionMgr
|
|||||||
return "YTCS没开机";
|
return "YTCS没开机";
|
||||||
}
|
}
|
||||||
string stateHxja = await _redisData.TryGetValueByKeyField<string>("hxjA", "State");
|
string stateHxja = await _redisData.TryGetValueByKeyField<string>("hxjA", "State");
|
||||||
|
string stateHxjb = await _redisData.TryGetValueByKeyField<string>("hxjB", "State");
|
||||||
string stateHxjc = await _redisData.TryGetValueByKeyField<string>("hxjC", "State");
|
string stateHxjc = await _redisData.TryGetValueByKeyField<string>("hxjC", "State");
|
||||||
|
string stateHxjd = await _redisData.TryGetValueByKeyField<string>("hxjD", "State");
|
||||||
List<String> hxjList = new List<string>();
|
List<String> hxjList = new List<string>();
|
||||||
if ("OK" == stateHxja)
|
if ("OK" == stateHxja)
|
||||||
{
|
{
|
||||||
hxjList.Add("hxjA");
|
hxjList.Add("hxjA");
|
||||||
}
|
}
|
||||||
|
if ("OK" == stateHxjb)
|
||||||
|
{
|
||||||
|
hxjList.Add("hxjB");
|
||||||
|
}
|
||||||
if ("OK" == stateHxjc)
|
if ("OK" == stateHxjc)
|
||||||
{
|
{
|
||||||
hxjList.Add("hxjC");
|
hxjList.Add("hxjC");
|
||||||
}
|
}
|
||||||
|
if ("OK" == stateHxjd)
|
||||||
|
{
|
||||||
|
hxjList.Add("hxjD");
|
||||||
|
}
|
||||||
|
|
||||||
if (hxjList.IsEmpty())
|
if (hxjList.IsEmpty())
|
||||||
{
|
{
|
||||||
Log.Error($"hxjA,hxjC不正常");
|
Log.Error($"hxjA,hxjB,hxjC,hxjD不正常");
|
||||||
return "hxjA,hxjC不正常";
|
return "hxjA,hxjB,hxjC,hxjD不正常";
|
||||||
}
|
}
|
||||||
string msg = "";
|
string msg = "";
|
||||||
List<EqpEquipment> equipments = await _db.Queryable<EqpEquipType>()
|
List<EqpEquipment> equipments = await _db.Queryable<EqpEquipType>()
|
||||||
@@ -733,6 +743,8 @@ namespace Tnb.ProductionMgr
|
|||||||
return "true";
|
return "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 同步基础数据
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 同步基础数据
|
/// 同步基础数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1500,15 +1512,18 @@ namespace Tnb.ProductionMgr
|
|||||||
{
|
{
|
||||||
var erpdb = _db.AsTenant().GetConnection("erpdb");
|
var erpdb = _db.AsTenant().GetConnection("erpdb");
|
||||||
List<ErpUserDto> persons = await erpdb.Queryable<ErpBdPsndoc>()
|
List<ErpUserDto> persons = await erpdb.Queryable<ErpBdPsndoc>()
|
||||||
|
.Where(x=>x.ENABLESTATE!=1)
|
||||||
.Select(x=>new ErpUserDto
|
.Select(x=>new ErpUserDto
|
||||||
{
|
{
|
||||||
PERSON_ID = x.ID,
|
PERSON_ID = x.ID,
|
||||||
CODE = x.CODE,
|
CODE = x.CODE,
|
||||||
NAME = x.NAME,
|
NAME = x.NAME,
|
||||||
TYPE = "1",
|
TYPE = "1",
|
||||||
|
ENABLESTATE = x.ENABLESTATE
|
||||||
})
|
})
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
List<ErpUserDto> users = await erpdb.Queryable<ErpSmUser>()
|
List<ErpUserDto> users = await erpdb.Queryable<ErpSmUser>()
|
||||||
|
.Where(x=>x.ENABLESTATE!=1)
|
||||||
.Select(x=>new ErpUserDto
|
.Select(x=>new ErpUserDto
|
||||||
{
|
{
|
||||||
PERSON_ID = x.PK_PSNDOC,
|
PERSON_ID = x.PK_PSNDOC,
|
||||||
@@ -1516,86 +1531,197 @@ namespace Tnb.ProductionMgr
|
|||||||
CODE = x.CODE,
|
CODE = x.CODE,
|
||||||
NAME = x.NAME,
|
NAME = x.NAME,
|
||||||
TYPE = "2",
|
TYPE = "2",
|
||||||
|
ENABLESTATE = x.ENABLESTATE
|
||||||
})
|
})
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
persons.AddRange(users);
|
// persons.AddRange(users);
|
||||||
|
|
||||||
List<ErpExtendField> erpExtendFields = await _db.Queryable<ErpExtendField>().Where(x=>x.table_name=="base_user").ToListAsync();
|
List<ErpExtendField> erpExtendFields = await _db.Queryable<ErpExtendField>().Where(x=>x.table_name=="base_user").ToListAsync();
|
||||||
List<UserEntity> insertUsers = new List<UserEntity>();
|
|
||||||
List<UserEntity> existsUsers = await _db.Queryable<UserEntity>().ToListAsync();
|
List<UserEntity> existsUsers = await _db.Queryable<UserEntity>().ToListAsync();
|
||||||
|
List<UserEntity> insertUsers = new List<UserEntity>();
|
||||||
List<UserRelationEntity> insertUserRelations = new List<UserRelationEntity>();
|
List<UserRelationEntity> insertUserRelations = new List<UserRelationEntity>();
|
||||||
List<ErpExtendField> insertErpExtendFields = new List<ErpExtendField>();
|
List<ErpExtendField> insertErpExtendFields = new List<ErpExtendField>();
|
||||||
|
|
||||||
|
List<UserEntity> insertUsers2 = new List<UserEntity>();
|
||||||
|
List<UserRelationEntity> insertUserRelations2 = new List<UserRelationEntity>();
|
||||||
|
List<ErpExtendField> insertErpExtendFields2 = new List<ErpExtendField>();
|
||||||
|
|
||||||
await _db.Ado.BeginTranAsync();
|
await _db.Ado.BeginTranAsync();
|
||||||
|
|
||||||
foreach (var person in persons)
|
foreach (var user in users)
|
||||||
{
|
{
|
||||||
if ((person.TYPE=="1" && erpExtendFields.All(x => x.person_id != person.PERSON_ID)) || (person.TYPE=="2" && erpExtendFields.All(x => x.user_id != person.USER_ID)))
|
List<ErpExtendField> userErpExtendFields = erpExtendFields.Where(x => x.user_id == user.USER_ID).ToList();
|
||||||
|
if (userErpExtendFields == null || userErpExtendFields.IsEmpty() || userErpExtendFields.Count == 1)
|
||||||
{
|
{
|
||||||
if (person.TYPE == "2" && insertErpExtendFields.FindIndex(x=>x.person_id==person.PERSON_ID)!=-1 && person.PERSON_ID!="~")
|
|
||||||
|
UserEntity userEntity = new UserEntity();
|
||||||
|
if (userErpExtendFields.Count == 1)
|
||||||
{
|
{
|
||||||
ErpExtendField eef = insertErpExtendFields.Find(x => x.person_id == person.PERSON_ID);
|
userEntity = existsUsers.Find(x => x.Id == erpExtendFields[0].table_id);
|
||||||
eef.user_id = person.USER_ID;
|
int state = user.ENABLESTATE == 2 ? 1 : 0;
|
||||||
}
|
if (userEntity != null)
|
||||||
else
|
|
||||||
{
|
|
||||||
UserEntity userEntity = new UserEntity();
|
|
||||||
if (existsUsers.Exists(x => x.Account == person.CODE))
|
|
||||||
{
|
{
|
||||||
userEntity = existsUsers.Find(x => x.Account == person.CODE);
|
if (userEntity.EnabledMark != state)
|
||||||
|
{
|
||||||
|
await _db.Updateable<UserEntity>()
|
||||||
|
.SetColumns(x => x.EnabledMark == state)
|
||||||
|
.Where(x => x.Id == userEntity.Id)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
userEntity.Id = SnowflakeIdHelper.NextId();
|
|
||||||
userEntity.Account = person.CODE;
|
|
||||||
userEntity.RealName = person.NAME;
|
|
||||||
userEntity.Gender = 1;
|
|
||||||
userEntity.OrganizeId = WmsWareHouseConst.AdministratorOrgId;
|
|
||||||
userEntity.RoleId = "30327535942933";
|
|
||||||
userEntity.Secretkey = Guid.NewGuid().ToString();
|
|
||||||
userEntity.Password = MD5Encryption.Encrypt(MD5Encryption.Encrypt(CommonConst.DEFAULTPASSWORD) + userEntity.Secretkey);
|
|
||||||
userEntity.EntryDate = DateTime.Now;
|
|
||||||
userEntity.EnabledMark = 1;
|
|
||||||
userEntity.CreatorTime = DateTime.Now;
|
|
||||||
insertUsers.Add(userEntity);
|
|
||||||
|
|
||||||
UserRelationEntity userRelationEntity = new UserRelationEntity();
|
|
||||||
userRelationEntity.Id = SnowflakeIdHelper.NextId();
|
|
||||||
userRelationEntity.UserId = userEntity.Id;
|
|
||||||
userRelationEntity.ObjectType = "Role";
|
|
||||||
userRelationEntity.ObjectId = "30327535942933";
|
|
||||||
userRelationEntity.CreatorTime = DateTime.Now;
|
|
||||||
insertUserRelations.Add(userRelationEntity);
|
|
||||||
|
|
||||||
UserRelationEntity userRelationEntity2 = new UserRelationEntity();
|
|
||||||
userRelationEntity2.Id = SnowflakeIdHelper.NextId();
|
|
||||||
userRelationEntity2.UserId = userEntity.Id;
|
|
||||||
userRelationEntity2.ObjectType = "Organize";
|
|
||||||
userRelationEntity2.ObjectId = WmsWareHouseConst.AdministratorOrgId;
|
|
||||||
userRelationEntity2.CreatorTime = DateTime.Now;
|
|
||||||
insertUserRelations.Add(userRelationEntity2);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErpExtendField extendField = new ErpExtendField();
|
|
||||||
extendField.org_id = WmsWareHouseConst.AdministratorOrgId;
|
|
||||||
extendField.table_name = "base_user";
|
|
||||||
extendField.table_id = userEntity.Id;
|
|
||||||
extendField.person_id = person.PERSON_ID;
|
|
||||||
extendField.user_id = person.USER_ID;
|
|
||||||
insertErpExtendFields.Add(extendField);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userEntity.Id = SnowflakeIdHelper.NextId();
|
||||||
|
userEntity.Account = user.CODE;
|
||||||
|
userEntity.RealName = user.NAME;
|
||||||
|
userEntity.Gender = 1;
|
||||||
|
userEntity.OrganizeId = WmsWareHouseConst.AdministratorOrgId;
|
||||||
|
userEntity.RoleId = "30327535942933";
|
||||||
|
userEntity.Secretkey = Guid.NewGuid().ToString();
|
||||||
|
userEntity.Password = MD5Encryption.Encrypt(MD5Encryption.Encrypt(CommonConst.DEFAULTPASSWORD) +
|
||||||
|
userEntity.Secretkey);
|
||||||
|
userEntity.EntryDate = DateTime.Now;
|
||||||
|
userEntity.EnabledMark = user.ENABLESTATE == 2 ? 1 : 0;
|
||||||
|
userEntity.CreatorTime = DateTime.Now;
|
||||||
|
insertUsers.Add(userEntity);
|
||||||
|
|
||||||
|
UserRelationEntity userRelationEntity = new UserRelationEntity();
|
||||||
|
userRelationEntity.Id = SnowflakeIdHelper.NextId();
|
||||||
|
userRelationEntity.UserId = userEntity.Id;
|
||||||
|
userRelationEntity.ObjectType = "Role";
|
||||||
|
userRelationEntity.ObjectId = "30327535942933";
|
||||||
|
userRelationEntity.CreatorTime = DateTime.Now;
|
||||||
|
insertUserRelations.Add(userRelationEntity);
|
||||||
|
|
||||||
|
UserRelationEntity userRelationEntity2 = new UserRelationEntity();
|
||||||
|
userRelationEntity2.Id = SnowflakeIdHelper.NextId();
|
||||||
|
userRelationEntity2.UserId = userEntity.Id;
|
||||||
|
userRelationEntity2.ObjectType = "Organize";
|
||||||
|
userRelationEntity2.ObjectId = WmsWareHouseConst.AdministratorOrgId;
|
||||||
|
userRelationEntity2.CreatorTime = DateTime.Now;
|
||||||
|
insertUserRelations.Add(userRelationEntity2);
|
||||||
|
|
||||||
|
ErpExtendField erpExtendField = new ErpExtendField();
|
||||||
|
erpExtendField.org_id = WmsWareHouseConst.AdministratorOrgId;
|
||||||
|
erpExtendField.table_name = "base_user";
|
||||||
|
erpExtendField.table_id = userEntity.Id;
|
||||||
|
erpExtendField.person_id = user.PERSON_ID;
|
||||||
|
erpExtendField.user_id = user.USER_ID;
|
||||||
|
insertErpExtendFields.Add(erpExtendField);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _db.Insertable(insertUsers).ExecuteCommandAsync();
|
if (!insertUsers.IsEmpty())
|
||||||
await _db.Insertable(insertUserRelations).ExecuteCommandAsync();
|
{
|
||||||
await _db.Insertable(insertErpExtendFields).ExecuteCommandAsync();
|
existsUsers.AddRange(insertUsers);
|
||||||
|
await _db.Insertable(insertUsers).ExecuteCommandAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!insertUserRelations.IsEmpty())
|
||||||
|
{
|
||||||
|
await _db.Insertable(insertUserRelations).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!insertErpExtendFields.IsEmpty())
|
||||||
|
{
|
||||||
|
erpExtendFields.AddRange(insertErpExtendFields);
|
||||||
|
await _db.Insertable(insertErpExtendFields).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var person in persons)
|
||||||
|
{
|
||||||
|
List<ErpExtendField> userErpExtendFields = erpExtendFields.Where(x => x.person_id == person.PERSON_ID).ToList();
|
||||||
|
if (userErpExtendFields == null || userErpExtendFields.IsEmpty() || userErpExtendFields.Count == 1)
|
||||||
|
{
|
||||||
|
|
||||||
|
UserEntity userEntity = new UserEntity();
|
||||||
|
if (userErpExtendFields.Count == 1)
|
||||||
|
{
|
||||||
|
userEntity = existsUsers.Find(x => x.Id == erpExtendFields[0].table_id);
|
||||||
|
int state = person.ENABLESTATE == 2 ? 1 : 0;
|
||||||
|
if (userEntity != null)
|
||||||
|
{
|
||||||
|
if (userEntity.EnabledMark != state)
|
||||||
|
{
|
||||||
|
await _db.Updateable<UserEntity>()
|
||||||
|
.SetColumns(x => x.EnabledMark == state)
|
||||||
|
.Where(x => x.Id == userEntity.Id)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userEntity.Id = SnowflakeIdHelper.NextId();
|
||||||
|
userEntity.Account = person.CODE;
|
||||||
|
userEntity.RealName = person.NAME;
|
||||||
|
userEntity.Gender = 1;
|
||||||
|
userEntity.OrganizeId = WmsWareHouseConst.AdministratorOrgId;
|
||||||
|
userEntity.RoleId = "30327535942933";
|
||||||
|
userEntity.Secretkey = Guid.NewGuid().ToString();
|
||||||
|
userEntity.Password = MD5Encryption.Encrypt(MD5Encryption.Encrypt(CommonConst.DEFAULTPASSWORD) +
|
||||||
|
userEntity.Secretkey);
|
||||||
|
userEntity.EntryDate = DateTime.Now;
|
||||||
|
userEntity.EnabledMark = person.ENABLESTATE == 2 ? 1 : 0;
|
||||||
|
userEntity.CreatorTime = DateTime.Now;
|
||||||
|
insertUsers2.Add(userEntity);
|
||||||
|
|
||||||
|
UserRelationEntity userRelationEntity = new UserRelationEntity();
|
||||||
|
userRelationEntity.Id = SnowflakeIdHelper.NextId();
|
||||||
|
userRelationEntity.UserId = userEntity.Id;
|
||||||
|
userRelationEntity.ObjectType = "Role";
|
||||||
|
userRelationEntity.ObjectId = "30327535942933";
|
||||||
|
userRelationEntity.CreatorTime = DateTime.Now;
|
||||||
|
insertUserRelations2.Add(userRelationEntity);
|
||||||
|
|
||||||
|
UserRelationEntity userRelationEntity2 = new UserRelationEntity();
|
||||||
|
userRelationEntity2.Id = SnowflakeIdHelper.NextId();
|
||||||
|
userRelationEntity2.UserId = userEntity.Id;
|
||||||
|
userRelationEntity2.ObjectType = "Organize";
|
||||||
|
userRelationEntity2.ObjectId = WmsWareHouseConst.AdministratorOrgId;
|
||||||
|
userRelationEntity2.CreatorTime = DateTime.Now;
|
||||||
|
insertUserRelations2.Add(userRelationEntity2);
|
||||||
|
|
||||||
|
ErpExtendField erpExtendField = new ErpExtendField();
|
||||||
|
erpExtendField.org_id = WmsWareHouseConst.AdministratorOrgId;
|
||||||
|
erpExtendField.table_name = "base_user";
|
||||||
|
erpExtendField.table_id = userEntity.Id;
|
||||||
|
erpExtendField.person_id = person.PERSON_ID;
|
||||||
|
erpExtendField.user_id = person.USER_ID;
|
||||||
|
insertErpExtendFields2.Add(erpExtendField);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!insertUsers2.IsEmpty())
|
||||||
|
{
|
||||||
|
await _db.Insertable(insertUsers2).ExecuteCommandAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!insertUserRelations2.IsEmpty())
|
||||||
|
{
|
||||||
|
await _db.Insertable(insertUserRelations2).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!insertErpExtendFields2.IsEmpty())
|
||||||
|
{
|
||||||
|
await _db.Insertable(insertErpExtendFields2).ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
|
||||||
await _db.Ado.CommitTranAsync();
|
await _db.Ado.CommitTranAsync();
|
||||||
|
|
||||||
msg = $"新增用户{insertUsers.Count}条";
|
msg = $"新增用户{insertUsers.Count+insertUsers2.Count}条";
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -1608,6 +1734,8 @@ namespace Tnb.ProductionMgr
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 监测工艺
|
/// 监测工艺
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -539,7 +539,7 @@ namespace Tnb.WarehouseMgr
|
|||||||
}
|
}
|
||||||
|
|
||||||
InStockStrategyQuery inStockStrategyInput = new() { warehouse_id = WmsWareHouseConst.WAREHOUSE_CP_ID, Size = items_pretask.Count, Region_id = WmsWareHouseConst.REGION_CPOutstock_ID };
|
InStockStrategyQuery inStockStrategyInput = new() { warehouse_id = WmsWareHouseConst.WAREHOUSE_CP_ID, Size = items_pretask.Count, Region_id = WmsWareHouseConst.REGION_CPOutstock_ID };
|
||||||
List<BasLocation> endLocations = await _wareHouseService.InStockStrategy(inStockStrategyInput);
|
List<BasLocation> endLocations = await _wareHouseService.InStockStrategy(inStockStrategyInput, _db);
|
||||||
|
|
||||||
int instockLocIndex = 0;
|
int instockLocIndex = 0;
|
||||||
foreach (Tuple<string, WmsCarryH, WmsCarryCode, BasLocation> item in items_pretask)
|
foreach (Tuple<string, WmsCarryH, WmsCarryCode, BasLocation> item in items_pretask)
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ namespace Tnb.WarehouseMgr
|
|||||||
code_batch = r.Key.code_batch,
|
code_batch = r.Key.code_batch,
|
||||||
material_specification = item.material_specification,
|
material_specification = item.material_specification,
|
||||||
container_no = item.container_no,
|
container_no = item.container_no,
|
||||||
warehouse_id = WmsWareHouseConst.WAREHOUSE_CP_ID,
|
warehouse_id = WmsWareHouseConst.WAREHOUSE_YCL_ID,
|
||||||
print_qty = qty,
|
print_qty = qty,
|
||||||
scan_qty = qty,
|
scan_qty = qty,
|
||||||
print_id = "",
|
print_id = "",
|
||||||
|
|||||||
Reference in New Issue
Block a user