using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using NCC.Common.Core.Manager;
using NCC.Common.Filter;
using NCC.Common.Helper;
using NCC.Dependency;
using NCC.DynamicApiController;
using NCC.Extend.Entitys.Dto.LqEvent;
using NCC.Extend.Entitys.Dto.LqEventUser;
using NCC.Extend.Entitys.lq_event;
using NCC.Extend.Entitys.lq_eventuser;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Interfaces.LqEvent;
using NCC.FriendlyException;
using NCC.System.Entitys.Permission;
using SqlSugar;
using Yitter.IdGenerator;
namespace NCC.Extend.LqEvent
{
///
/// 拓客活动服务
///
[ApiDescriptionSettings(Tag = "绿纤拓客活动服务", Name = "LqEvent", Order = 200)]
[Route("api/Extend/[controller]")]
[ApiController]
public class LqEventService : ILqEventService, IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
private readonly IUserManager _userManager;
///
/// 初始化一个类型的新实例
///
/// 数据库
/// 用户管理
public LqEventService(ISqlSugarClient db, IUserManager userManager)
{
_db = db;
_userManager = userManager;
}
#region 获取拓客活动列表
///
/// 获取拓客活动列表
///
///
/// 分页查询拓客活动列表,支持多条件筛选和排序。
/// 返回结果包含每个活动的成员数量统计。
///
/// 示例请求:
/// GET /api/Extend/LqEvent?page=1&rows=10&sidx=Id&sord=desc&eventName=春节拓客活动
///
/// 支持的条件筛选:
/// - 活动名称模糊查询
/// - 活动编号模糊查询
/// - 活动负责人模糊查询
/// - 开始时间范围查询
/// - 结束时间范围查询
///
/// 支持的排序字段:
/// - Id: 活动ID
/// - EventName: 活动名称
/// - EventNumber: 活动编号
/// - EventCoordinator: 活动负责人
/// - StartTime: 开始时间
/// - EndTime: 结束时间
///
/// 查询参数
/// 分页的拓客活动列表,包含成员数量统计
[HttpGet("")]
public async Task GetList([FromQuery] LqEventListQueryInput input)
{
var sidx = input.sidx == null ? "id" : input.sidx;
var sord = input.sord == null ? "desc" : input.sord;
// 先查询活动列表
var Data = await _db.Queryable()
.WhereIF(!string.IsNullOrEmpty(input.EventName), e => e.EventName.Contains(input.EventName))
.WhereIF(!string.IsNullOrEmpty(input.EventNumber), e => e.EventNumber.Contains(input.EventNumber))
.Select(e => new LqEventListOutput
{
id = e.Id,
eventName = e.EventName,
startTime = e.StartTime,
endTime = e.EndTime,
eventCoordinator = e.EventCoordinator,
eventNumber = e.EventNumber,
memberCount = SqlFunc.Subqueryable().Where(u => u.EventId == e.Id).Count(),
})
.MergeTable()
.OrderBy(sidx + " " + sord)
.ToPagedListAsync(input.page, input.rows);
return PageResult.SqlSugarPageResult(Data);
}
#endregion
#region 获取拓客活动详情
///
/// 获取拓客活动详情
///
///
/// 根据活动ID获取拓客活动的详细信息,包括基础信息和所有参与成员列表。
///
/// 示例请求:
/// GET /api/Extend/LqEvent/1234567890123456789
///
/// 返回数据包含:
/// - 活动基础信息(ID、名称、编号、负责人、开始时间、结束时间)
/// - 成员列表(用户ID、部门ID、战队名称、创建时间、创建用户)
///
/// 如果活动不存在,将返回404错误。
///
/// 拓客活动ID
/// 拓客活动详情,包含成员信息
/// 成功返回拓客活动详情
/// 拓客活动不存在
/// 服务器内部错误
[HttpGet("{id}")]
public async Task GetInfo(string id)
{
// 1. 获取拓客活动基本信息
var entity = await _db.Queryable().FirstAsync(p => p.Id == id);
if (entity == null)
{
throw NCCException.Oh("拓客活动不存在");
}
var output = entity.Adapt();
// 2. 获取拓客活动成员信息
var members = await _db.Queryable().Where(u => u.EventId == id.ToString()).ToListAsync();
// 3. 转换为输出格式
output.Members = members.Adapt>();
return output;
}
#endregion
#region 创建拓客活动
///
/// 创建拓客活动
///
///
/// 创建新的拓客活动,可以同时添加参与成员。
/// 系统会自动生成活动ID,并记录创建用户信息。
///
/// 示例请求:
/// POST /api/Extend/LqEvent
/// Content-Type: application/json
///
/// ```json
/// {
/// "eventName": "春节拓客活动",
/// "eventNumber": "TK2024001",
/// "eventCoordinator": "张三",
/// "startTime": "2024-01-15T09:00:00",
/// "endTime": "2024-01-20T18:00:00",
/// "members": [
/// {
/// "userId": "user001",
/// "depId": "dept001",
/// "teamName": "第一战队"
/// },
/// {
/// "userId": "user002",
/// "depId": "dept001",
/// "teamName": "第二战队"
/// }
/// ]
/// }
/// ```
///
/// 参数验证:
/// - eventName: 必填,最大长度255字符
/// - eventNumber: 可选,最大长度255字符
/// - eventCoordinator: 可选,最大长度255字符
/// - members: 可选,成员列表
/// - userId: 必填
/// - depId: 可选
/// - teamName: 可选
///
/// 创建流程:
/// 1. 生成新的活动ID
/// 2. 插入拓客活动记录
/// 3. 插入成员记录(如果有)
///
/// 创建参数
/// 无返回值
[HttpPost("")]
public async Task Create([FromBody] LqEventCrInput input)
{
var userInfo = await _userManager.GetUserInfo();
var entity = input.Adapt();
// 生成新的ID
entity.Id = YitIdHelper.NextId().ToString();
// 插入拓客活动
var isOk = await _db.Insertable(entity).ExecuteCommandAsync();
if (!(isOk > 0))
throw NCCException.Oh("创建拓客活动失败");
// 插入拓客活动成员
if (input.Members != null && input.Members.Count > 0)
{
var memberEntities = new List();
foreach (var member in input.Members)
{
memberEntities.Add(
new LqEventUserEntity
{
Id = YitIdHelper.NextId().ToString(),
EventId = entity.Id.ToString(),
UserId = member.UserId,
DepId = member.DepId,
TeamName = member.TeamName,
CreationTime = DateTime.Now,
CreationUser = userInfo?.userName,
StoreId = member.StoreId,
}
);
}
await _db.Insertable(memberEntities).ExecuteCommandAsync();
}
}
#endregion
#region 更新拓客活动
///
/// 更新拓客活动
///
///
/// 更新拓客活动信息,包括基础信息和成员列表。
/// 更新时会先删除原有成员,再添加新成员。
///
/// 示例请求:
/// PUT /api/Extend/LqEvent
/// Content-Type: application/json
///
/// ```json
/// {
/// "id": "1234567890123456789",
/// "eventName": "春节拓客活动(更新)",
/// "eventNumber": "TK2024001",
/// "eventCoordinator": "李四",
/// "startTime": "2024-01-15T09:00:00",
/// "endTime": "2024-01-25T18:00:00",
/// "members": [
/// {
/// "userId": "user003",
/// "depId": "dept002",
/// "teamName": "精英战队"
/// }
/// ]
/// }
/// ```
///
/// 参数验证:
/// - id: 必填,拓客活动ID
/// - eventName: 必填,最大长度255字符
/// - eventNumber: 可选,最大长度255字符
/// - eventCoordinator: 可选,最大长度255字符
/// - members: 可选,成员列表(为空则清空所有成员)
///
/// 更新流程:
/// 1. 更新拓客活动记录
/// 2. 删除原有成员记录
/// 3. 插入新成员记录(如果有)
///
/// 更新参数
/// 无返回值
/// 拓客活动更新成功
/// 请求参数错误或验证失败
/// 拓客活动不存在
/// 服务器内部错误
[HttpPut("")]
public async Task Update([FromBody] LqEventUpInput input)
{
var userInfo = await _userManager.GetUserInfo();
var entity = input.Adapt();
// 更新拓客活动
var isOk = await _db.Updateable(entity).ExecuteCommandAsync();
if (!(isOk > 0))
throw NCCException.Oh("更新拓客活动失败");
// 删除原有成员
await _db.Deleteable().Where(u => u.EventId == entity.Id.ToString()).ExecuteCommandAsync();
// 插入新成员
if (input.Members != null && input.Members.Count > 0)
{
var memberEntities = new List();
foreach (var member in input.Members)
{
memberEntities.Add(
new LqEventUserEntity
{
Id = YitIdHelper.NextId().ToString(),
EventId = entity.Id.ToString(),
UserId = member.UserId,
DepId = member.DepId,
TeamName = member.TeamName,
CreationTime = DateTime.Now,
CreationUser = userInfo?.userName,
StoreId = member.StoreId,
}
);
}
await _db.Insertable(memberEntities).ExecuteCommandAsync();
}
}
#endregion
#region 删除拓客活动
///
/// 删除拓客活动
///
///
/// 删除指定的拓客活动,会同时删除该活动的所有成员记录。
/// 删除操作不可恢复,请谨慎使用。
///
/// 示例请求:
/// DELETE /api/Extend/LqEvent/1234567890123456789
///
/// 删除顺序:
/// 1. 先删除所有相关成员记录
/// 2. 再删除拓客活动记录
///
/// 注意事项:
/// - 删除操作不可恢复
/// - 会级联删除所有相关成员记录
/// - 如果活动不存在,将返回404错误
/// - 建议在删除前先查询确认活动信息
///
/// 拓客活动ID
/// 无返回值
/// 拓客活动删除成功
/// 拓客活动不存在
/// 服务器内部错误
[HttpDelete("{id}")]
public async Task Delete(string id)
{
// 删除拓客活动成员
await _db.Deleteable().Where(u => u.EventId == id.ToString()).ExecuteCommandAsync();
// 删除拓客活动
var isOk = await _db.Deleteable().Where(p => p.Id == id).ExecuteCommandAsync();
if (!(isOk > 0))
throw NCCException.Oh("删除拓客活动失败");
}
#endregion
#region 更新拓客目标数量
///
/// 更新拓客目标数量
///
/// 拓客活动ID
/// 拓客目标数量
///
[HttpPut("{id}/target")]
public async Task UpdateTarget(string id, int target)
{
var isOk = await _db.Updateable().SetColumns(it => new LqEventUserEntity { EventTarget = target }).Where(x => x.EventId == id).ExecuteCommandAsync();
if (!(isOk > 0))
throw NCCException.Oh("更新拓客目标数量失败");
}
#endregion
#region 获取拓客目标数量
///
/// 获取拓客目标数量
///
/// 拓客活动ID
///
[HttpGet("{id}/target")]
public async Task GetTarget(string id)
{
return await _db.Queryable().Where(x => x.EventId == id).Select(x => x.EventTarget).FirstAsync();
}
#endregion
#region 根据用户ID获取参与的拓客活动
///
/// 根据用户ID获取参与的拓客活动
///
///
/// 根据用户ID查询该用户参与的所有拓客活动,支持时间范围筛选。
/// 返回结果包含活动基础信息和用户在该活动中的详细信息。
///
/// 示例请求:
/// GET /api/Extend/LqEvent/user/user001?startTime=2024-01-01&endTime=2024-12-31
///
/// 参数说明:
/// - userId: 用户ID(必填)
/// - startTime: 开始时间(可选),查询在此时间之后开始的活动
/// - endTime: 结束时间(可选),查询在此时间之前结束的活动
///
/// 返回数据包含:
/// - 活动基础信息(ID、名称、编号、负责人、开始时间、结束时间)
/// - 用户参与信息(部门ID、战队名称、目标数量等)
///
/// 用户ID
/// 开始时间(可选)
/// 结束时间(可选)
/// 用户参与的拓客活动列表
/// 成功返回用户参与的拓客活动列表
/// 请求参数错误
/// 服务器内部错误
[HttpGet("user/{userId}")]
[ProducesResponseType(typeof(List), 200)]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public async Task> GetUserEvents(string userId, [FromQuery] DateTime? startTime = null, [FromQuery] DateTime? endTime = null)
{
try
{
// 先测试简单的查询
var eventUsers = await _db.Queryable().Where(u => u.UserId == userId).ToListAsync();
if (!eventUsers.Any())
{
return new List();
}
var eventIds = eventUsers.Select(u => u.EventId).ToList();
// 查询活动信息
var events = await _db.Queryable().Where(e => eventIds.Contains(e.Id)).ToListAsync();
// 时间范围筛选
if (startTime.HasValue)
{
events = events.Where(e => e.StartTime >= startTime.Value).ToList();
}
if (endTime.HasValue)
{
events = events.Where(e => e.EndTime <= endTime.Value).ToList();
}
// 构建结果
var result = new List();
foreach (var eventUser in eventUsers)
{
var eventInfo = events.FirstOrDefault(e => e.Id == eventUser.EventId);
if (eventInfo != null)
{
result.Add(
new LqEventUserEventOutput
{
// 活动基础信息
EventId = eventInfo.Id,
EventName = eventInfo.EventName,
EventNumber = eventInfo.EventNumber,
EventCoordinator = eventInfo.EventCoordinator,
StartTime = eventInfo.StartTime,
EndTime = eventInfo.EndTime,
// 用户参与信息
UserId = eventUser.UserId,
DepId = eventUser.DepId,
TeamName = eventUser.TeamName,
EventTarget = eventUser.EventTarget,
CreationTime = eventUser.CreationTime,
CreationUser = eventUser.CreationUser,
}
);
}
}
// 按开始时间降序排列
return result.OrderByDescending(x => x.StartTime).ToList();
}
catch (Exception ex)
{
throw NCCException.Oh("获取用户参与的拓客活动失败", ex);
}
}
#endregion
#region Excel导入拓客活动用户
///
/// Excel导入拓客活动用户
///
///
/// 通过Excel文件导入拓客活动用户数据,支持批量导入。
/// Excel列名:员工手机号、姓名、战队、门店、目标张数
///
/// 导入流程:
/// 1. 通过员工手机号查找用户信息,获取用户ID和组织ID
/// 2. 通过门店中文名称查找门店信息,获取门店ID
/// 3. 验证数据完整性
/// 4. 返回成功和失败的数据列表
///
/// 示例请求:
/// POST /api/Extend/LqEvent/import-users
/// Content-Type: multipart/form-data
///
/// 参数:
/// - file: Excel文件
///
/// 返回数据:
/// - SuccessCount: 成功数量
/// - FailCount: 失败数量
/// - SuccessData: 成功的数据列表
/// - FailData: 失败的数据列表(包含错误信息)
///
/// Excel文件
/// 导入结果
/// 导入完成,返回成功和失败的数据统计
/// 请求参数错误或文件格式不正确
/// 服务器内部错误
[HttpPost("import-users")]
public async Task ImportUsers(IFormFile file)
{
try
{
if (file == null || file.Length == 0)
{
throw NCCException.Oh("请选择要导入的Excel文件");
}
var result = new LqEventImportResult();
// 读取Excel文件
var excelData = await ReadExcelFile(file);
result.TotalCount = excelData.Count;
foreach (var row in excelData)
{
try
{
var importError = new LqEventImportError
{
RowNumber = row.RowNumber,
MobilePhone = row.MobilePhone,
Name = row.Name,
TeamName = row.TeamName,
StoreName = row.StoreName,
TargetCount = row.TargetCount,
};
// 验证必填字段
if (string.IsNullOrEmpty(row.MobilePhone))
{
importError.ErrorMessage = "员工手机号不能为空";
result.FailData.Add(importError);
result.FailCount++;
continue;
}
// 通过手机号查找用户
var user = await _db.Queryable().Where(u => u.MobilePhone == row.MobilePhone).FirstAsync();
if (user == null)
{
importError.ErrorMessage = $"未找到手机号为 {row.MobilePhone} 的用户";
result.FailData.Add(importError);
result.FailCount++;
continue;
}
// 获取用户组织ID(部门ID)
if (string.IsNullOrEmpty(user.OrganizeId))
{
importError.ErrorMessage = $"用户 {user.RealName} 没有关联的组织信息";
result.FailData.Add(importError);
result.FailCount++;
continue;
}
// 查找门店信息
string storeId = null;
if (!string.IsNullOrEmpty(row.StoreName))
{
var store = await _db.Queryable().Where(s => s.Dm == row.StoreName).FirstAsync();
if (store == null)
{
importError.ErrorMessage = $"未找到名称为 {row.StoreName} 的门店";
result.FailData.Add(importError);
result.FailCount++;
continue;
}
storeId = store.Id;
}
// 创建成功的数据
var successData = new NCC.Extend.Entitys.Dto.LqEventUser.LqEventUserCrInput
{
Id = YitIdHelper.NextId().ToString(),
UserId = user.Id,
DepId = user.OrganizeId,
TeamName = row.TeamName,
StoreId = storeId,
};
result.SuccessData.Add(successData);
result.SuccessCount++;
}
catch (Exception ex)
{
var importError = new LqEventImportError
{
RowNumber = row.RowNumber,
MobilePhone = row.MobilePhone,
Name = row.Name,
TeamName = row.TeamName,
StoreName = row.StoreName,
TargetCount = row.TargetCount,
ErrorMessage = $"处理数据时发生错误: {ex.Message}",
};
result.FailData.Add(importError);
result.FailCount++;
}
}
return result;
}
catch (Exception ex)
{
throw NCCException.Oh($"导入拓客活动用户失败: {ex.Message}", ex);
}
}
///
/// 读取Excel文件数据
///
/// Excel文件
/// Excel数据列表
[NonAction]
private async Task> ReadExcelFile(IFormFile file)
{
var result = new List();
// 保存临时文件
var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName));
try
{
using (var stream = new FileStream(tempFilePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// 使用ExcelImportHelper读取Excel文件
var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0);
// 从第2行开始读取数据(第1行是标题)
for (int i = 1; i < dataTable.Rows.Count; i++)
{
var row = dataTable.Rows[i];
var mobilePhone = row[0]?.ToString()?.Trim();
var name = row[1]?.ToString()?.Trim();
var teamName = row[2]?.ToString()?.Trim();
var storeName = row[3]?.ToString()?.Trim();
var targetCountStr = row[4]?.ToString()?.Trim();
int? targetCount = null;
if (!string.IsNullOrEmpty(targetCountStr) && int.TryParse(targetCountStr, out int target))
{
targetCount = target;
}
// 跳过空行
if (string.IsNullOrEmpty(mobilePhone) && string.IsNullOrEmpty(name))
{
continue;
}
result.Add(
new ExcelImportRow
{
RowNumber = i + 1, // Excel行号从1开始
MobilePhone = mobilePhone,
Name = name,
TeamName = teamName,
StoreName = storeName,
TargetCount = targetCount,
}
);
}
}
finally
{
// 清理临时文件
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
}
}
return result;
}
///
/// Excel导入行数据
///
private class ExcelImportRow
{
public int RowNumber { get; set; }
public string MobilePhone { get; set; }
public string Name { get; set; }
public string TeamName { get; set; }
public string StoreName { get; set; }
public int? TargetCount { get; set; }
}
#endregion
}
}