新增门户目录接口与 route_sort 字段迁移;完善公告创建与包裹到货相关逻辑。 Co-authored-by: Cursor <cursoragent@cursor.com>
566 lines
21 KiB
C#
566 lines
21 KiB
C#
using System.Linq.Expressions;
|
|
using Fake.Application;
|
|
using Fake.Rbac.Infrastructure;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Oms.Application.Dtos.Shared;
|
|
using Oms.Application.Dtos.System;
|
|
using Oms.Application.Shared;
|
|
using Oms.Domain.CustomersAggregate.Entities;
|
|
using Oms.Domain.CustomersAggregate.Repositories;
|
|
using Oms.Domain.SystemAggregate.Entities;
|
|
using Oms.Domain.SystemAggregate.Repositories;
|
|
|
|
namespace Oms.Application.Services.System;
|
|
|
|
/// <summary>
|
|
/// 公告 API 服务。
|
|
/// </summary>
|
|
[ApiExplorerSettings(GroupName = "OMS-System")]
|
|
public class NoticeService : ApplicationService
|
|
{
|
|
private const string NoticeTypeNotice = "notice";
|
|
private const string NoticeTypeMessage = "message";
|
|
private const string ReceiverTypeUser = "user";
|
|
private const string ReceiverTypeCustomer = "customer";
|
|
|
|
private const string InternalUserFilter = """
|
|
(
|
|
NOT EXISTS (SELECT 1 FROM `user_role` ur0 WHERE ur0.`UserId` = u.`Id`)
|
|
OR EXISTS (
|
|
SELECT 1
|
|
FROM `user_role` ur
|
|
INNER JOIN `role` r ON r.`Id` = ur.`RoleId` AND r.`IsDeleted` = 0
|
|
WHERE ur.`UserId` = u.`Id`
|
|
AND r.`Code` <> 'Customer'
|
|
AND r.`Code` NOT LIKE 'CustomerSub\_%'
|
|
)
|
|
)
|
|
""";
|
|
|
|
private INoticeRepository NoticeRepository => LazyServiceProvider.GetRequiredService<INoticeRepository>();
|
|
private INoticeReadRepository NoticeReadRepository => LazyServiceProvider.GetRequiredService<INoticeReadRepository>();
|
|
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
|
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService<FakeRbacDbContext>();
|
|
|
|
/// <summary>
|
|
/// 查询通知列表。
|
|
/// </summary>
|
|
[HttpGet("/system/notice/list")]
|
|
public async Task<OmsPageResult<NoticeOutput>> GetListAsync([FromQuery] NoticeListInput input)
|
|
{
|
|
var receiverUserId = CurrentUser.GetId<int>();
|
|
if (receiverUserId <= 0)
|
|
{
|
|
return new OmsPageResult<NoticeOutput> { Items = [], Total = 0 };
|
|
}
|
|
|
|
var expectedReceiverType = OmsSessionHelper.IsCustomerLogin(CurrentUser)
|
|
? ReceiverTypeCustomer
|
|
: ReceiverTypeUser;
|
|
|
|
var myReadNoticeIds = (await NoticeReadRepository.GetListAsync(x =>
|
|
x.ReceiverType == expectedReceiverType && x.ReceiverId == receiverUserId))
|
|
.Select(x => x.NoticeId)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
var predicate = BuildListPredicate(
|
|
input,
|
|
receiverUserId,
|
|
expectedReceiverType,
|
|
input.OnlyUnread == true ? myReadNoticeIds : null);
|
|
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
|
var notices = await NoticeRepository.GetPagedListAsync(
|
|
predicate,
|
|
pageIndex,
|
|
pageSize,
|
|
new Dictionary<string, bool> { [nameof(Notice.CreateTime)] = false, [nameof(Notice.Id)] = false });
|
|
var total = await NoticeRepository.CountAsync(predicate);
|
|
|
|
var noticeIds = notices.Select(x => x.Id).Distinct().ToArray();
|
|
var reads = noticeIds.Length == 0
|
|
? []
|
|
: await NoticeReadRepository.GetListAsync(x => noticeIds.Contains(x.NoticeId));
|
|
var readCountMap = reads
|
|
.GroupBy(x => x.NoticeId)
|
|
.ToDictionary(
|
|
x => x.Key,
|
|
x => (int)x.Select(item => $"{item.ReceiverType}:{item.ReceiverId}").Distinct(StringComparer.Ordinal).Count());
|
|
var myReadIdSet = myReadNoticeIds.ToHashSet();
|
|
|
|
var selectionIdMap = await BuildReceiverSelectionIdMapAsync(notices);
|
|
|
|
return new OmsPageResult<NoticeOutput>
|
|
{
|
|
Items = notices.Select(x => MapNoticeOutput(
|
|
x,
|
|
readCountMap.TryGetValue(x.Id, out var readCount) ? readCount : 0,
|
|
myReadIdSet.Contains(x.Id),
|
|
selectionIdMap.TryGetValue(x.Id, out var selectionIds) ? selectionIds : [])).ToList(),
|
|
Total = total
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 接收人候选项(按接收对象类型加载)。
|
|
/// </summary>
|
|
[HttpGet("/system/notice/receiver-options")]
|
|
public async Task<OmsPageResult<NoticeReceiverOptionOutput>> GetReceiverOptionsAsync([FromQuery] NoticeReceiverOptionsInput input)
|
|
{
|
|
var receiverType = NormalizeReceiverType(input.ReceiverType);
|
|
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
|
var keyword = input.Keyword?.Trim();
|
|
|
|
if (receiverType == ReceiverTypeCustomer)
|
|
{
|
|
return await QueryCustomerReceiverOptionsAsync(keyword, pageIndex, pageSize);
|
|
}
|
|
|
|
return await QueryUserReceiverOptionsAsync(keyword, pageIndex, pageSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 新增通知。
|
|
/// </summary>
|
|
[HttpPost("/system/notice/create")]
|
|
public async Task CreateAsync(NoticeCreateInput input)
|
|
{
|
|
var noticeType = NormalizeNoticeType(input.NoticeType);
|
|
ValidateNoticeText(noticeType, input.Title, input.Content);
|
|
var receiverType = ResolveReceiverType(noticeType, input.ReceiverType);
|
|
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(noticeType, receiverType, input.ReceiverIds);
|
|
await NoticeRepository.InsertAsync(new Notice
|
|
{
|
|
NoticeType = noticeType,
|
|
Title = NormalizeTitle(input.Title),
|
|
Content = input.Content.Trim(),
|
|
IsPopup = noticeType == NoticeTypeMessage ? (byte)0 : NormalizePopupValue(input.IsPopup),
|
|
ReceiverType = receiverType,
|
|
ReceiverIds = OmsDocHelper.SerializeJson(receiverIds) ?? "[]"
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新通知。
|
|
/// </summary>
|
|
[HttpPost("/system/notice/update")]
|
|
public async Task UpdateAsync(NoticeUpdateInput input)
|
|
{
|
|
var entity = await GetNoticeOrThrowAsync(input.Id);
|
|
var noticeType = NormalizeNoticeType(input.NoticeType);
|
|
ValidateNoticeText(noticeType, input.Title, input.Content);
|
|
var receiverType = ResolveReceiverType(noticeType, input.ReceiverType);
|
|
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(noticeType, receiverType, input.ReceiverIds);
|
|
entity.NoticeType = noticeType;
|
|
entity.Title = NormalizeTitle(input.Title);
|
|
entity.Content = input.Content.Trim();
|
|
entity.IsPopup = noticeType == NoticeTypeMessage ? (byte)0 : NormalizePopupValue(input.IsPopup);
|
|
entity.ReceiverType = receiverType;
|
|
entity.ReceiverIds = OmsDocHelper.SerializeJson(receiverIds) ?? "[]";
|
|
await NoticeRepository.UpdateAsync(entity);
|
|
|
|
// 全体广播不清理已读;定向接收时移除已不在名单内的已读记录
|
|
if (receiverIds.Count > 0)
|
|
{
|
|
var existsReads = await NoticeReadRepository.GetListAsync(x => x.NoticeId == entity.Id);
|
|
var receiverIdSet = receiverIds.ToHashSet();
|
|
foreach (var read in existsReads.Where(x =>
|
|
!string.Equals(x.ReceiverType, receiverType, StringComparison.OrdinalIgnoreCase) ||
|
|
!receiverIdSet.Contains(x.ReceiverId)))
|
|
{
|
|
await NoticeReadRepository.DeleteAsync(read);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除通知。
|
|
/// </summary>
|
|
[HttpPost("/system/notice/delete")]
|
|
public async Task DeleteAsync(IdInput input)
|
|
{
|
|
var entity = await GetNoticeOrThrowAsync(input.Id);
|
|
await NoticeRepository.DeleteAsync(entity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 确认通知已读。
|
|
/// </summary>
|
|
[HttpPost("/system/notice/read-confirm")]
|
|
public async Task ReadConfirmAsync(IdInput input)
|
|
{
|
|
var notice = await GetNoticeOrThrowAsync(input.Id);
|
|
var receiverIds = OmsDocHelper.DeserializeJson<List<int>>(notice.ReceiverIds) ?? [];
|
|
var currentReceiverType = OmsSessionHelper.IsCustomerLogin(CurrentUser)
|
|
? ReceiverTypeCustomer
|
|
: ReceiverTypeUser;
|
|
var currentReceiverId = CurrentUser.GetId<int>();
|
|
if (currentReceiverId <= 0)
|
|
{
|
|
throw new BusinessException("当前登录主体无效");
|
|
}
|
|
|
|
// 空接收人 = 全体广播;否则校验当前主体在定向名单内
|
|
if (receiverIds.Count > 0 && !receiverIds.Contains(currentReceiverId))
|
|
{
|
|
throw new BusinessException("当前主体不在通知接收范围内");
|
|
}
|
|
|
|
var existed = await NoticeReadRepository.GetByNoticeReceiverAsync(notice.Id, currentReceiverType, currentReceiverId);
|
|
if (existed != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await NoticeReadRepository.InsertAsync(new NoticeRead
|
|
{
|
|
NoticeId = notice.Id,
|
|
ReceiverType = currentReceiverType,
|
|
ReceiverId = currentReceiverId,
|
|
ReadTime = DateTime.Now
|
|
});
|
|
}
|
|
|
|
private async Task<OmsPageResult<NoticeReceiverOptionOutput>> QueryUserReceiverOptionsAsync(
|
|
string? keyword,
|
|
int pageIndex,
|
|
int pageSize)
|
|
{
|
|
var where = $"WHERE u.`IsDeleted` = 0 AND {InternalUserFilter}";
|
|
var args = new List<object>();
|
|
if (!string.IsNullOrWhiteSpace(keyword))
|
|
{
|
|
var pattern = $"%{keyword}%";
|
|
where += $" AND (u.`Name` LIKE {{{args.Count}}} OR u.`Account` LIKE {{{args.Count + 1}}})";
|
|
args.Add(pattern);
|
|
args.Add(pattern);
|
|
}
|
|
|
|
var countSql = $"SELECT COUNT(*) AS `Value` FROM `user` u {where}";
|
|
var total = await RbacDbContext.Database.SqlQueryRaw<long>(countSql, args.ToArray()).FirstAsync();
|
|
var offset = Math.Max(pageIndex - 1, 0) * pageSize;
|
|
var listSql = $"""
|
|
SELECT
|
|
u.`userid` AS `Value`,
|
|
u.`Name` AS `Label`,
|
|
u.`Account` AS `Extra`
|
|
FROM `user` u
|
|
{where}
|
|
ORDER BY u.`Name`, u.`userid`
|
|
LIMIT {pageSize} OFFSET {offset}
|
|
""";
|
|
var items = await RbacDbContext.Database.SqlQueryRaw<NoticeReceiverOptionRow>(listSql, args.ToArray())
|
|
.Select(x => new NoticeReceiverOptionOutput
|
|
{
|
|
Value = x.Value,
|
|
Label = string.IsNullOrWhiteSpace(x.Label) ? $"用户{x.Value}" : x.Label.Trim(),
|
|
Extra = string.IsNullOrWhiteSpace(x.Extra) ? null : x.Extra.Trim()
|
|
})
|
|
.ToListAsync();
|
|
|
|
return new OmsPageResult<NoticeReceiverOptionOutput> { Items = items, Total = total };
|
|
}
|
|
|
|
private async Task<OmsPageResult<NoticeReceiverOptionOutput>> QueryCustomerReceiverOptionsAsync(
|
|
string? keyword,
|
|
int pageIndex,
|
|
int pageSize)
|
|
{
|
|
Expression<Func<Customer, bool>> predicate = x => x.ParentCustomerId == null;
|
|
if (!string.IsNullOrWhiteSpace(keyword))
|
|
{
|
|
var pattern = keyword.Trim();
|
|
predicate = x =>
|
|
x.ParentCustomerId == null &&
|
|
(x.CustomerName.Contains(pattern) ||
|
|
x.CustomerCode.Contains(pattern) ||
|
|
x.Phone.Contains(pattern));
|
|
}
|
|
|
|
var total = await CustomerRepository.CountAsync(predicate);
|
|
var customers = await CustomerRepository.GetPagedListAsync(
|
|
predicate,
|
|
pageIndex,
|
|
pageSize,
|
|
new Dictionary<string, bool> { [nameof(Customer.CustomerName)] = true, [nameof(Customer.Id)] = true });
|
|
|
|
return new OmsPageResult<NoticeReceiverOptionOutput>
|
|
{
|
|
Total = total,
|
|
Items = customers.Select(x => new NoticeReceiverOptionOutput
|
|
{
|
|
Value = x.Id,
|
|
Label = string.IsNullOrWhiteSpace(x.CustomerName) ? $"客户{x.Id}" : x.CustomerName.Trim(),
|
|
Extra = string.IsNullOrWhiteSpace(x.CustomerCode) ? x.Phone : x.CustomerCode.Trim()
|
|
}).ToList()
|
|
};
|
|
}
|
|
|
|
private async Task<Dictionary<int, List<int>>> BuildReceiverSelectionIdMapAsync(IReadOnlyCollection<Notice> notices)
|
|
{
|
|
var result = new Dictionary<int, List<int>>();
|
|
if (notices.Count == 0)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
var customerNotices = notices
|
|
.Where(x => string.Equals(x.ReceiverType, ReceiverTypeCustomer, StringComparison.OrdinalIgnoreCase))
|
|
.ToList();
|
|
Dictionary<int, List<int>> userIdToCustomerIds = [];
|
|
if (customerNotices.Count > 0)
|
|
{
|
|
var allUserIds = customerNotices
|
|
.SelectMany(x => OmsDocHelper.DeserializeJson<List<int>>(x.ReceiverIds) ?? [])
|
|
.Where(x => x > 0)
|
|
.Distinct()
|
|
.ToList();
|
|
if (allUserIds.Count > 0)
|
|
{
|
|
var customers = await CustomerRepository.GetListAsync(x =>
|
|
x.ParentCustomerId == null && allUserIds.Contains(x.UserId));
|
|
userIdToCustomerIds = customers
|
|
.GroupBy(x => x.UserId)
|
|
.ToDictionary(x => x.Key, x => x.Select(c => c.Id).Distinct().ToList());
|
|
}
|
|
}
|
|
|
|
foreach (var notice in notices)
|
|
{
|
|
var storedUserIds = OmsDocHelper.DeserializeJson<List<int>>(notice.ReceiverIds) ?? [];
|
|
if (string.Equals(notice.ReceiverType, ReceiverTypeCustomer, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var selectionIds = storedUserIds
|
|
.SelectMany(userId => userIdToCustomerIds.TryGetValue(userId, out var customerIds)
|
|
? customerIds
|
|
: [])
|
|
.Distinct()
|
|
.ToList();
|
|
result[notice.Id] = selectionIds.Count > 0 ? selectionIds : storedUserIds;
|
|
continue;
|
|
}
|
|
|
|
result[notice.Id] = storedUserIds.Distinct().ToList();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static Expression<Func<Notice, bool>> BuildListPredicate(
|
|
NoticeListInput input,
|
|
int receiverUserId,
|
|
string expectedReceiverType,
|
|
IReadOnlyCollection<int>? excludeNoticeIds)
|
|
{
|
|
var noticeType = input.NoticeType?.Trim();
|
|
var receiverType = input.ReceiverType?.Trim();
|
|
var keyword = input.Keyword?.Trim();
|
|
var receiverIdToken = receiverUserId.ToString();
|
|
var excluded = excludeNoticeIds?.Where(x => x > 0).Distinct().ToList() ?? [];
|
|
return x =>
|
|
(string.IsNullOrWhiteSpace(noticeType) || x.NoticeType == noticeType) &&
|
|
(string.IsNullOrWhiteSpace(receiverType) || x.ReceiverType == receiverType) &&
|
|
(string.IsNullOrWhiteSpace(keyword) ||
|
|
x.Title.Contains(keyword) ||
|
|
x.Content.Contains(keyword)) &&
|
|
(
|
|
// 空接收人列表 = 全体广播(员工/客户均可见)
|
|
x.ReceiverIds == "[]" ||
|
|
(x.ReceiverType == expectedReceiverType &&
|
|
(x.ReceiverIds == $"[{receiverIdToken}]" ||
|
|
x.ReceiverIds.StartsWith($"[{receiverIdToken},") ||
|
|
x.ReceiverIds.Contains($",{receiverIdToken},") ||
|
|
x.ReceiverIds.EndsWith($",{receiverIdToken}]")))
|
|
) &&
|
|
(excluded.Count == 0 || !excluded.Contains(x.Id));
|
|
}
|
|
|
|
private async Task<Notice> GetNoticeOrThrowAsync(int id)
|
|
{
|
|
var entity = await NoticeRepository.FirstOrDefaultAsync(x => x.Id == id);
|
|
if (entity == null)
|
|
{
|
|
throw new BusinessException("通知不存在");
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
private async Task<List<int>> NormalizeAndValidateReceiverIdsAsync(
|
|
string noticeType,
|
|
string receiverType,
|
|
IEnumerable<int>? receiverIds)
|
|
{
|
|
var selectionIds = (receiverIds ?? [])
|
|
.Where(x => x > 0)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
// 公告默认全体生效,允许空接收人;站内信必须指定接收人
|
|
if (selectionIds.Count == 0)
|
|
{
|
|
if (noticeType == NoticeTypeNotice)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
throw new BusinessException("接收人不能为空");
|
|
}
|
|
|
|
var normalizedReceiverType = NormalizeReceiverType(receiverType);
|
|
|
|
if (normalizedReceiverType == ReceiverTypeCustomer)
|
|
{
|
|
var customers = await CustomerRepository.GetListAsync(x =>
|
|
selectionIds.Contains(x.Id) && x.ParentCustomerId == null);
|
|
if (customers.Count != selectionIds.Count)
|
|
{
|
|
throw new BusinessException("接收客户包含无效数据");
|
|
}
|
|
|
|
var userIds = customers.Select(x => x.UserId).Where(x => x > 0).Distinct().ToList();
|
|
if (userIds.Count == 0)
|
|
{
|
|
throw new BusinessException("接收客户未关联有效用户");
|
|
}
|
|
|
|
var validUserIds = await QueryValidUserIdsAsync(userIds);
|
|
if (validUserIds.Count != userIds.Count)
|
|
{
|
|
throw new BusinessException("接收客户关联用户无效");
|
|
}
|
|
|
|
return userIds;
|
|
}
|
|
|
|
var validIds = await QueryValidUserIdsAsync(selectionIds);
|
|
if (validIds.Count != selectionIds.Count)
|
|
{
|
|
throw new BusinessException("接收用户包含无效数据");
|
|
}
|
|
|
|
return selectionIds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 公告全体广播时接收类型仅作占位;站内信必须明确 user/customer。
|
|
/// </summary>
|
|
private static string ResolveReceiverType(string noticeType, string? receiverType)
|
|
{
|
|
if (noticeType == NoticeTypeNotice && string.IsNullOrWhiteSpace(receiverType))
|
|
{
|
|
return ReceiverTypeUser;
|
|
}
|
|
|
|
return NormalizeReceiverType(receiverType ?? string.Empty);
|
|
}
|
|
|
|
private async Task<List<int>> QueryValidUserIdsAsync(IReadOnlyCollection<int> userIds)
|
|
{
|
|
if (userIds.Count == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var sql = $"SELECT `userid` AS `UserId` FROM `user` WHERE `IsDeleted` = 0 AND `userid` IN ({string.Join(",", userIds)})";
|
|
return await RbacDbContext.Database.SqlQueryRaw<UserIdQueryResult>(sql)
|
|
.Select(x => x.UserId)
|
|
.ToListAsync();
|
|
}
|
|
|
|
private static void ValidateNoticeText(string noticeType, string? title, string content)
|
|
{
|
|
if (noticeType == NoticeTypeNotice && string.IsNullOrWhiteSpace(title))
|
|
{
|
|
throw new BusinessException("通知标题不能为空");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
{
|
|
throw new BusinessException("通知内容不能为空");
|
|
}
|
|
}
|
|
|
|
private static string NormalizeTitle(string? title) =>
|
|
string.IsNullOrWhiteSpace(title) ? string.Empty : title.Trim();
|
|
|
|
private static NoticeOutput MapNoticeOutput(
|
|
Notice entity,
|
|
int readCount,
|
|
bool isRead,
|
|
List<int> receiverSelectionIds)
|
|
{
|
|
var receiverIds = OmsDocHelper.DeserializeJson<List<int>>(entity.ReceiverIds) ?? [];
|
|
var isBroadcast = receiverIds.Count == 0;
|
|
return new NoticeOutput
|
|
{
|
|
Id = entity.Id,
|
|
NoticeType = entity.NoticeType,
|
|
NoticeTypeName = entity.NoticeType == NoticeTypeNotice ? "公告" : "站内信",
|
|
Title = entity.Title,
|
|
Content = entity.Content,
|
|
IsPopup = entity.IsPopup,
|
|
ReceiverType = entity.ReceiverType,
|
|
ReceiverTypeName = isBroadcast
|
|
? "全部"
|
|
: entity.ReceiverType == ReceiverTypeCustomer ? "客户" : "用户",
|
|
ReceiverIds = receiverIds,
|
|
ReceiverSelectionIds = receiverSelectionIds,
|
|
ReadCount = readCount,
|
|
IsRead = isRead,
|
|
ReceiverCount = isBroadcast ? 0 : receiverIds.Distinct().Count(),
|
|
CreateTime = entity.CreateTime,
|
|
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
|
|
};
|
|
}
|
|
|
|
private static string NormalizeNoticeType(string noticeType)
|
|
{
|
|
var normalized = noticeType.Trim().ToLowerInvariant();
|
|
return normalized switch
|
|
{
|
|
NoticeTypeNotice => NoticeTypeNotice,
|
|
NoticeTypeMessage => NoticeTypeMessage,
|
|
_ => throw new BusinessException("通知类型无效")
|
|
};
|
|
}
|
|
|
|
private static string NormalizeReceiverType(string receiverType)
|
|
{
|
|
var normalized = receiverType.Trim().ToLowerInvariant();
|
|
return normalized switch
|
|
{
|
|
ReceiverTypeUser => ReceiverTypeUser,
|
|
ReceiverTypeCustomer => ReceiverTypeCustomer,
|
|
_ => throw new BusinessException("接收对象类型无效")
|
|
};
|
|
}
|
|
|
|
private static byte NormalizePopupValue(byte value)
|
|
{
|
|
return value switch
|
|
{
|
|
0 => 0,
|
|
1 => 1,
|
|
_ => throw new BusinessException("弹窗标记无效")
|
|
};
|
|
}
|
|
|
|
private sealed class UserIdQueryResult
|
|
{
|
|
public int UserId { get; set; }
|
|
}
|
|
|
|
private sealed class NoticeReceiverOptionRow
|
|
{
|
|
public int Value { get; set; }
|
|
|
|
public string? Label { get; set; }
|
|
|
|
public string? Extra { get; set; }
|
|
}
|
|
}
|