using FoodLabeling.Application.Helpers;
using FoodLabeling.Application.Contracts.Dtos.Common;
using FoodLabeling.Application.Contracts.Dtos.Partner;
using FoodLabeling.Application.Contracts.IServices;
using FoodLabeling.Application.Services.DbModels;
using Microsoft.AspNetCore.Mvc;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
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;
///
/// 合作伙伴管理(fl_partner)
///
public class PartnerAppService : ApplicationService, IPartnerAppService
{
private const int ExportPdfMaxRows = 5000;
private readonly ISqlSugarDbContext _dbContext;
private readonly IGuidGenerator _guidGenerator;
public PartnerAppService(ISqlSugarDbContext dbContext, IGuidGenerator guidGenerator)
{
_dbContext = dbContext;
_guidGenerator = guidGenerator;
}
///
public async Task> GetListAsync(PartnerGetListInputVo input)
{
RefAsync total = 0;
var query = await BuildPartnerListQueryAsync(input);
var entities = await query.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
var items = entities.Select(MapListItem).ToList();
return BuildPagedResult(input.SkipCount, input.MaxResultCount, total, items);
}
///
public async Task GetAsync(Guid id)
{
if (id == Guid.Empty)
{
throw new UserFriendlyException("Partner id is required.");
}
var partnerId = id.ToString();
var entity = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => !x.IsDeleted && x.Id == partnerId);
if (entity is null)
{
throw new UserFriendlyException("Partner not found.");
}
return MapDetail(entity);
}
///
[UnitOfWork]
public async Task CreateAsync(PartnerCreateInputVo input)
{
var name = input.PartnerName?.Trim();
if (string.IsNullOrWhiteSpace(name))
{
throw new UserFriendlyException("Partner name is required.");
}
var email = input.ContactEmail?.Trim();
if (!string.IsNullOrWhiteSpace(email) && !IsPlausibleEmail(email))
{
throw new UserFriendlyException("Invalid contact email format.");
}
var now = Clock.Now;
var entity = new FlPartnerDbEntity
{
Id = _guidGenerator.Create().ToString(),
IsDeleted = false,
PartnerName = name,
ContactEmail = string.IsNullOrWhiteSpace(email) ? null : email,
PhoneNumber = TrimToNull(input.PhoneNumber),
State = input.State,
CreationTime = now,
CreatorId = CurrentUser?.Id?.ToString(),
LastModificationTime = now,
LastModifierId = CurrentUser?.Id?.ToString()
};
ApplyAddressFields(entity, input);
await _dbContext.SqlSugarClient.Insertable(entity).ExecuteCommandAsync();
return await GetAsync(Guid.Parse(entity.Id));
}
///
[UnitOfWork]
public async Task UpdateAsync(Guid id, PartnerUpdateInputVo input)
{
if (id == Guid.Empty)
{
throw new UserFriendlyException("Partner id is required.");
}
var partnerId = id.ToString();
var entity = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => !x.IsDeleted && x.Id == partnerId);
if (entity is null)
{
throw new UserFriendlyException("Partner not found.");
}
var name = input.PartnerName?.Trim();
if (string.IsNullOrWhiteSpace(name))
{
throw new UserFriendlyException("Partner name is required.");
}
var email = input.ContactEmail?.Trim();
if (!string.IsNullOrWhiteSpace(email) && !IsPlausibleEmail(email))
{
throw new UserFriendlyException("Invalid contact email format.");
}
entity.PartnerName = name;
entity.ContactEmail = string.IsNullOrWhiteSpace(email) ? null : email;
entity.PhoneNumber = TrimToNull(input.PhoneNumber);
entity.State = input.State;
ApplyAddressFields(entity, input);
entity.LastModificationTime = Clock.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
return await GetAsync(id);
}
///
[UnitOfWork]
public async Task DeleteAsync(Guid id)
{
if (id == Guid.Empty)
{
throw new UserFriendlyException("Partner id is required.");
}
var partnerId = id.ToString();
var entity = await _dbContext.SqlSugarClient.Queryable()
.FirstAsync(x => !x.IsDeleted && x.Id == partnerId);
if (entity is null)
{
throw new UserFriendlyException("Partner not found.");
}
entity.IsDeleted = true;
entity.LastModificationTime = Clock.Now;
entity.LastModifierId = CurrentUser?.Id?.ToString();
await _dbContext.SqlSugarClient.Updateable(entity).ExecuteCommandAsync();
}
///
[HttpGet]
public async Task ExportPdfAsync([FromQuery] PartnerGetListInputVo input)
{
QuestPDF.Settings.License = LicenseType.Community;
var count = await (await BuildPartnerListQueryAsync(input)).CountAsync();
var query = await BuildPartnerListQueryAsync(input);
if (count > ExportPdfMaxRows)
{
throw new UserFriendlyException(
$"Export exceeds the maximum of {ExportPdfMaxRows} rows. Narrow your filters and try again.");
}
var rows = await query.Take(ExportPdfMaxRows).ToListAsync();
var fileName = $"companies_{Clock.Now:yyyy-MM-dd_HH-mm-ss}.pdf";
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Margin(28);
page.DefaultTextStyle(x => x.FontSize(10));
page.Header().Text("Companies").SemiBold().FontSize(18);
page.Content().PaddingTop(12).Table(table =>
{
table.ColumnsDefinition(c =>
{
c.RelativeColumn(2.2f);
c.RelativeColumn(2.4f);
c.RelativeColumn(1.8f);
c.RelativeColumn(1f);
c.RelativeColumn(1.6f);
});
static IContainer CellHeader(IContainer c) =>
c.Background(Colors.Grey.Lighten3).Padding(6).DefaultTextStyle(x => x.SemiBold());
table.Cell().Element(CellHeader).Text("Company");
table.Cell().Element(CellHeader).Text("Contact");
table.Cell().Element(CellHeader).Text("Phone");
table.Cell().Element(CellHeader).Text("Status");
table.Cell().Element(CellHeader).Text("Created");
foreach (var e in rows)
{
var status = e.State ? "active" : "inactive";
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(5)
.Text(e.PartnerName ?? string.Empty);
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(5)
.Text(string.IsNullOrWhiteSpace(e.ContactEmail) ? "None" : e.ContactEmail!);
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(5)
.Text(string.IsNullOrWhiteSpace(e.PhoneNumber) ? "None" : e.PhoneNumber!);
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(5).Text(status);
table.Cell().BorderBottom(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(5)
.Text(e.CreationTime.ToString("yyyy-MM-dd HH:mm"));
}
});
});
});
var stream = new MemoryStream();
document.GeneratePdf(stream);
stream.Position = 0;
return new FileStreamResult(stream, "application/pdf") { FileDownloadName = fileName };
}
private async Task> BuildPartnerListQueryAsync(PartnerGetListInputVo input)
{
var scope = await PartnerScopeHelper.ResolvePartnerScopeAsync(CurrentUser, _dbContext);
var keyword = input.Keyword?.Trim();
var query = _dbContext.SqlSugarClient.Queryable()
.Where(x => !x.IsDeleted);
query = PartnerScopeHelper.ApplyPartnerScope(query, scope);
query = query
.WhereIF(input.State != null, x => x.State == input.State)
.WhereIF(!string.IsNullOrWhiteSpace(keyword),
x => x.PartnerName.Contains(keyword!) ||
(x.ContactEmail != null && x.ContactEmail.Contains(keyword!)) ||
(x.PhoneNumber != null && x.PhoneNumber.Contains(keyword!)) ||
(x.Street != null && x.Street.Contains(keyword!)) ||
(x.City != null && x.City.Contains(keyword!)) ||
(x.StateCode != null && x.StateCode.Contains(keyword!)) ||
(x.Country != null && x.Country.Contains(keyword!)) ||
(x.ZipCode != null && x.ZipCode.Contains(keyword!)));
if (!string.IsNullOrWhiteSpace(input.Sorting))
{
var sorting = input.Sorting.Trim();
if (sorting.Equals("PartnerName desc", StringComparison.OrdinalIgnoreCase))
{
query = query.OrderByDescending(x => x.PartnerName);
}
else if (sorting.Equals("PartnerName asc", StringComparison.OrdinalIgnoreCase))
{
query = query.OrderBy(x => x.PartnerName);
}
else if (sorting.Equals("CreationTime desc", StringComparison.OrdinalIgnoreCase))
{
query = query.OrderByDescending(x => x.CreationTime);
}
else if (sorting.Equals("CreationTime asc", StringComparison.OrdinalIgnoreCase))
{
query = query.OrderBy(x => x.CreationTime);
}
else if (sorting.Equals("State desc", StringComparison.OrdinalIgnoreCase))
{
query = query.OrderByDescending(x => x.State);
}
else if (sorting.Equals("State asc", StringComparison.OrdinalIgnoreCase))
{
query = query.OrderBy(x => x.State);
}
else
{
query = query.OrderByDescending(x => x.CreationTime);
}
}
else
{
query = query.OrderByDescending(x => x.CreationTime);
}
return query;
}
private static PartnerGetListOutputDto MapListItem(FlPartnerDbEntity x)
{
var dto = new PartnerGetListOutputDto
{
Id = x.Id,
PartnerName = x.PartnerName,
ContactEmail = x.ContactEmail,
PhoneNumber = x.PhoneNumber,
State = x.State,
CreationTime = x.CreationTime
};
MapAddressFields(dto, x);
return dto;
}
private static PartnerGetOutputDto MapDetail(FlPartnerDbEntity x)
{
var dto = new PartnerGetOutputDto
{
Id = x.Id,
PartnerName = x.PartnerName,
ContactEmail = x.ContactEmail,
PhoneNumber = x.PhoneNumber,
State = x.State,
CreationTime = x.CreationTime,
LastModificationTime = x.LastModificationTime
};
MapAddressFields(dto, x);
return dto;
}
private static void ApplyAddressFields(FlPartnerDbEntity entity, PartnerAddressFieldsDto input)
{
entity.Street = TrimToNull(input.Street);
entity.City = TrimToNull(input.City);
entity.StateCode = TrimToNull(input.StateCode);
entity.Country = TrimToNull(input.Country);
entity.ZipCode = TrimToNull(input.ZipCode);
}
private static void MapAddressFields(PartnerAddressFieldsDto dto, FlPartnerDbEntity entity)
{
dto.Street = entity.Street;
dto.City = entity.City;
dto.StateCode = entity.StateCode;
dto.Country = entity.Country;
dto.ZipCode = entity.ZipCode;
}
private static string? TrimToNull(string? value)
{
var t = value?.Trim();
return string.IsNullOrEmpty(t) ? null : t;
}
private static bool IsPlausibleEmail(string email)
{
if (email.Length > 256)
{
return false;
}
var at = email.IndexOf('@');
return at > 0 && at < email.Length - 1 && email.IndexOf('@', at + 1) < 0;
}
private static PagedResultWithPageDto BuildPagedResult(int skipCount, int maxResultCount, int total,
List items)
{
var pageSize = maxResultCount <= 0 ? items.Count : maxResultCount;
var pageIndex = pageSize <= 0 ? 1 : PagedQueryConvention.PageIndexFromSkipCount(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
};
}
}