using System.Text.Json;
using FoodLabeling.Application.Helpers;
using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.Dtos.Label;
using FoodLabeling.Application.Contracts.Dtos.LabelTemplate;
using FoodLabeling.Application.Contracts.IServices;
using FoodLabeling.Application.Services.DbModels;
using FoodLabeling.Domain.Entities;
using SqlSugar;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Guids;
using Volo.Abp.Uow;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace FoodLabeling.Application.Services;
///
/// 标签管理(一个产品展示多个标签)
///
public class LabelAppService : ApplicationService, ILabelAppService
{
private readonly ISqlSugarDbContext _dbContext;
private readonly IGuidGenerator _guidGenerator;
public LabelAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator)
{
_dbContext = dbContext;
_guidGenerator = guidGenerator;
}
public async Task> GetListAsync(LabelGetListInputVo input)
{
RefAsync total = 0;
var productId = input.ProductId?.Trim();
var partnerId = input.PartnerId?.Trim();
var groupId = input.GroupId?.Trim();
var locationId = input.LocationId?.Trim();
var keyword = input.Keyword?.Trim();
var labelCategoryId = input.LabelCategoryId?.Trim();
var labelTypeId = input.LabelTypeId?.Trim();
var templateCode = input.TemplateCode?.Trim();
// 目标:列表每行是“标签”,同一个标签下的 products 以 “,” 拼接展示
// 因此需要按 label 维度分页(避免 label-product join 导致重复行与分页错乱)。
var labelIdsQuery = _dbContext.SqlSugarClient.Queryable()
.Where(l => !l.IsDeleted)
.WhereIF(!string.IsNullOrWhiteSpace(labelCategoryId), l => l.LabelCategoryId == labelCategoryId)
.WhereIF(!string.IsNullOrWhiteSpace(labelTypeId), l => l.LabelTypeId == labelTypeId)
.WhereIF(input.State != null, l => l.State == input.State);
if (!string.IsNullOrWhiteSpace(templateCode))
{
labelIdsQuery = labelIdsQuery
.InnerJoin((l, tpl) => l.TemplateId == tpl.Id)
.Where((l, tpl) => !tpl.IsDeleted && tpl.TemplateCode == templateCode)
.Select((l, tpl) => l);
}
var regionSchema = await LabelRegionSchemaHelper.GetStatusAsync(_dbContext.SqlSugarClient);
var scopedLocationIds = await LocationScopeBindingHelper.ResolveScopedLocationIdsAsync(
_dbContext.SqlSugarClient, groupId, locationId);
var filterGroupId = groupId?.Trim();
if (!string.IsNullOrWhiteSpace(filterGroupId))
{
labelIdsQuery = scopedLocationIds is { Count: 0 }
? labelIdsQuery.Where(_ => false)
: LabelRegionScopeHelper.ApplyLabelRegionListFilter(
labelIdsQuery, filterGroupId, scopedLocationIds, regionSchema);
}
else if (scopedLocationIds is not null)
{
labelIdsQuery = scopedLocationIds.Count == 0
? labelIdsQuery.Where(_ => false)
: labelIdsQuery.Where(l => scopedLocationIds.Contains(l.LocationId));
}
else if (!string.IsNullOrWhiteSpace(groupId))
{
labelIdsQuery = LabelRegionScopeHelper.ApplyGroupIdListFilter(labelIdsQuery, groupId);
}
else if (!string.IsNullOrWhiteSpace(partnerId))
{
var partnerLocationIds = await LocationScopeBindingHelper.ResolveFilteredLocationIdsForListAsync(
_dbContext.SqlSugarClient, partnerId, null, null);
if (partnerLocationIds is not null)
{
labelIdsQuery = partnerLocationIds.Count == 0
? labelIdsQuery.Where(_ => false)
: labelIdsQuery.Where(l => partnerLocationIds.Contains(l.LocationId));
}
}
// 按产品筛选:存在 label-product 关联即可
if (!string.IsNullOrWhiteSpace(productId))
{
labelIdsQuery = labelIdsQuery
.InnerJoin((l, lp) => lp.LabelId == l.Id)
.Where((l, lp) => lp.ProductId == productId)
.Select((l, lp) => l);
}
// 关键字:匹配 labelName/categoryName/typeName/templateName/productName
if (!string.IsNullOrWhiteSpace(keyword))
{
labelIdsQuery = labelIdsQuery
.LeftJoin((l, c) => l.LabelCategoryId == c.Id)
.LeftJoin((l, c, t) => l.LabelTypeId == t.Id)
.LeftJoin((l, c, t, tpl) => l.TemplateId == tpl.Id)
.LeftJoin((l, c, t, tpl, lp) => lp.LabelId == l.Id)
.LeftJoin((l, c, t, tpl, lp, p) => lp.ProductId == p.Id)
.Where((l, c, t, tpl, lp, p) =>
l.LabelName.Contains(keyword!) ||
(c.CategoryName != null && c.CategoryName.Contains(keyword!)) ||
(t.TypeName != null && t.TypeName.Contains(keyword!)) ||
(tpl.TemplateName != null && tpl.TemplateName.Contains(keyword!)) ||
(p.ProductName != null && p.ProductName.Contains(keyword!)))
.Select((l, c, t, tpl, lp, p) => l);
}
// 排序(优先外部 Sorting,否则按最后编辑倒序)
if (!string.IsNullOrWhiteSpace(input.Sorting))
{
labelIdsQuery = labelIdsQuery.OrderBy(input.Sorting);
}
else
{
labelIdsQuery = labelIdsQuery.OrderByDescending(l => l.LastModificationTime ?? l.CreationTime);
}
var pageLabelIds = await labelIdsQuery
.Select(l => l.Id)
.Distinct()
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
if (pageLabelIds.Count == 0)
{
return new PagedResultWithPageDto
{
PageIndex = 1,
PageSize = input.MaxResultCount,
TotalCount = total,
TotalPages = 0,
Items = new List()
};
}
// 查询标签基础信息(分类/类型/模板)
var labelRows = await _dbContext.SqlSugarClient
.Queryable(
(l, c, t, tpl) => l.LabelCategoryId == c.Id && l.LabelTypeId == t.Id && l.TemplateId == tpl.Id)
.Where((l, c, t, tpl) => pageLabelIds.Contains(l.Id))
.Where((l, c, t, tpl) => !l.IsDeleted && !c.IsDeleted && !t.IsDeleted && !tpl.IsDeleted)
.Select((l, c, t, tpl) => new
{
l.Id,
l.LabelCode,
l.LabelName,
l.LocationId,
l.AppliedRegionType,
LabelCategoryName = c.CategoryName,
LabelTypeName = t.TypeName,
TemplateName = tpl.TemplateName,
TemplateCode = tpl.TemplateCode,
l.State,
LastEdited = l.LastModificationTime ?? l.CreationTime
})
.ToListAsync();
// 按分页顺序输出
var labelMap = labelRows.ToDictionary(x => x.Id, x => x);
var orderedLabels = pageLabelIds.Where(id => labelMap.ContainsKey(id)).Select(id => labelMap[id]).ToList();
var appliedRegionTypeMap = await LabelRegionSchemaHelper.GetAppliedRegionTypesForLabelsAsync(
_dbContext.SqlSugarClient, pageLabelIds);
var regionScopeMap = new Dictionary(StringComparer.Ordinal);
foreach (var lid in pageLabelIds)
{
if (!labelMap.TryGetValue(lid, out var row))
{
continue;
}
appliedRegionTypeMap.TryGetValue(lid, out var appliedType);
regionScopeMap[lid] = await LabelRegionScopeHelper.BuildScopeDisplayAsync(
_dbContext.SqlSugarClient,
lid,
appliedType,
row.LocationId);
}
// 查询 products 并拼接
var productRows = await _dbContext.SqlSugarClient
.Queryable()
.InnerJoin((lp, l) => lp.LabelId == l.Id)
.InnerJoin((lp, l, p) => lp.ProductId == p.Id)
.LeftJoin((lp, l, p, pc) => p.CategoryId == pc.Id)
.Where((lp, l, p, pc) => pageLabelIds.Contains(lp.LabelId))
.Where((lp, l, p, pc) => !l.IsDeleted && !p.IsDeleted)
.Select((lp, l, p, pc) => new { lp.LabelId, p.ProductName, ProductCategoryName = pc.CategoryName })
.ToListAsync();
var productsMap = productRows
.GroupBy(x => x.LabelId)
.ToDictionary(
g => g.Key,
g => new
{
Products = string.Join(",", g.Select(x => x.ProductName ?? string.Empty).Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct()),
ProductCategoryName = string.Join(",", g.Select(x => x.ProductCategoryName ?? string.Empty).Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).Distinct())
});
var locationIds = orderedLabels
.Select(x => x.LocationId)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!.Trim())
.Distinct()
.ToList();
var locationMap = new Dictionary();
if (locationIds.Count > 0)
{
var locGuids = locationIds.Where(x => Guid.TryParse(x, out _)).Select(Guid.Parse).ToList();
if (locGuids.Count > 0)
{
var locs = await _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted)
.Where(x => locGuids.Contains(x.Id))
.ToListAsync();
locationMap = locs.ToDictionary(x => x.Id.ToString(), x => x);
}
}
var regionScopeMap = await LabelRegionScopeHelper.BuildRegionScopeMapAsync(
_dbContext.SqlSugarClient,
orderedLabels.Select(x => (x.Id, x.AppliedRegionType ?? LabelRegionScopeHelper.AppliedTypeSpecified)).ToList());
var items = orderedLabels.Select(x =>
{
var locationName = string.Empty;
if (!string.IsNullOrWhiteSpace(x.LocationId) && locationMap.TryGetValue(x.LocationId!, out var loc))
{
locationName = loc.LocationName ?? loc.LocationCode;
}
var products = productsMap.TryGetValue(x.Id, out var prod) ? prod.Products : string.Empty;
var productCategoryNameValue = productsMap.TryGetValue(x.Id, out var prod2) ? prod2.ProductCategoryName : string.Empty;
regionScopeMap.TryGetValue(x.Id, out var regionScope);
appliedRegionTypeMap.TryGetValue(x.Id, out var appliedRegionType);
return new LabelGetListOutputDto
{
Id = x.LabelCode ?? string.Empty,
LabelName = x.LabelName ?? string.Empty,
LocationName = string.IsNullOrWhiteSpace(locationName) ? "无" : locationName,
Region = regionScope?.Region ?? "无",
RegionIds = regionScope?.RegionIds ?? new List(),
GroupIds = regionScope?.GroupIds ?? new List(),
AppliedRegionType = string.IsNullOrWhiteSpace(appliedRegionType)
? LabelRegionScopeHelper.AppliedRegionSpecified
: appliedRegionType.Trim(),
LabelCategoryName = x.LabelCategoryName ?? string.Empty,
ProductCategoryName = string.IsNullOrWhiteSpace(productCategoryNameValue) ? "无" : productCategoryNameValue,
Products = products,
TemplateName = x.TemplateName ?? string.Empty,
TemplateCode = x.TemplateCode ?? string.Empty,
LabelTypeName = x.LabelTypeName ?? string.Empty,
State = x.State,
LastEdited = x.LastEdited,
HasError = false
};
}).ToList();
var pageSize = input.MaxResultCount <= 0 ? items.Count : input.MaxResultCount;
var pageIndex = pageSize <= 0 ? 1 : PagedQueryConvention.PageIndexFromSkipCount(input.SkipCount);
var totalPages = pageSize <= 0 ? 0 : (int)Math.Ceiling(total / (double)pageSize);
return new PagedResultWithPageDto
{
PageIndex = pageIndex,
PageSize = pageSize,
TotalCount = total,
TotalPages = totalPages,
Items = items
};
}
public async Task GetAsync(string id)
{
var labelCode = id?.Trim();
if (string.IsNullOrWhiteSpace(labelCode))
{
throw new UserFriendlyException("标签Code不能为空");
}
var label = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => !x.IsDeleted && x.LabelCode == labelCode);
if (label is null)
{
throw new UserFriendlyException("标签不存在");
}
var template = await LabelTemplateQueryHelper.ProjectListColumns(
_dbContext.SqlSugarClient.Queryable())
.FirstAsync(x => x.Id == label.TemplateId);
var category = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => x.Id == label.LabelCategoryId);
var type = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => x.Id == label.LabelTypeId);
LocationAggregateRoot? location = null;
if (!string.IsNullOrWhiteSpace(label.LocationId) && Guid.TryParse(label.LocationId, out var locId))
{
location = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => !x.IsDeleted && x.Id == locId);
}
var productIds = await _dbContext.SqlSugarClient.Queryable()
.Where(x => x.LabelId == label.Id)
.Select(x => x.ProductId)
.ToListAsync();
object? labelInfo = null;
if (!string.IsNullOrWhiteSpace(label.LabelInfoJson))
{
labelInfo = JsonSerializer.Deserialize