using Mapster;
using foodmarket.Application.Catalog;
using foodmarket.Domain.Catalog;
namespace foodmarket.Application.Mapping;
/// Sprint 20 / TD-3: централизованная Mapster-конфигурация
/// для проекций domain → DTO. Используется через
/// queryable.ProjectToType<TDto>(MapsterConfig.Config)
/// — Mapster кодогенерирует SQL-friendly `.Select(...)`-выражение,
/// что эквивалентно ручному `Select(p => new TDto(...))` по
/// производительности, но компактнее в контроллере.
///
/// Главное правило записи:
/// 1. Все computed-поля (joins, агрегаты) — через `.Map(...)`.
/// 2. Все коллекции (Prices, Barcodes) — через `.Map(...)` с
/// `.Adapt<TItemDto>()` на каждом элементе.
/// 3. PreserveReference = false (default) — для EF-проекций
/// циклы не нужны.
///
/// Регистрация — в `Program.cs`:
///
/// var cfg = MapsterConfig.Build();
/// services.AddSingleton(cfg);
/// services.AddScoped<IMapper, ServiceMapper>();
///
///
public static class MapsterConfig
{
private static TypeAdapterConfig? _cached;
/// Singleton TypeAdapterConfig. Lazy-initialized чтобы
/// тесты тоже могли вызвать без DI.
public static TypeAdapterConfig Config => _cached ??= Build();
public static TypeAdapterConfig Build()
{
var cfg = new TypeAdapterConfig();
cfg.NewConfig()
.ConstructUsing(src => new ProductBarcodeDto(
src.Id, src.Code, src.Type, src.IsPrimary));
cfg.NewConfig()
.ConstructUsing(src => new ProductPriceDto(
src.Id, src.PriceTypeId, src.PriceType!.Name,
src.Amount, src.CurrencyId, src.Currency!.Code));
cfg.NewConfig()
.ConstructUsing(src => new ProductDto(
src.Id, src.Name, src.Article, src.Description,
src.UnitOfMeasureId, src.UnitOfMeasure!.Name,
src.Vat, src.VatEnabled,
src.ProductGroupId, src.ProductGroup!.Name,
src.DefaultSupplierId,
src.DefaultSupplier != null ? src.DefaultSupplier.Name : null,
src.CountryOfOriginId,
src.CountryOfOrigin != null ? src.CountryOfOrigin.Name : null,
src.IsService, src.Packaging, src.IsMarked,
src.MinStock, src.MaxStock,
src.ReferencePrice, src.ReferencePriceUpdatedAt,
src.PurchaseCurrencyId,
src.PurchaseCurrency != null ? src.PurchaseCurrency.Code : null,
src.Cost, src.LastSupplyAt,
src.ImageUrl,
src.IsArchived, src.IsAvailableForSale,
src.Prices.Select(pr => new ProductPriceDto(
pr.Id, pr.PriceTypeId, pr.PriceType!.Name,
pr.Amount, pr.CurrencyId, pr.Currency!.Code)).ToList(),
src.Barcodes.Select(b => new ProductBarcodeDto(
b.Id, b.Code, b.Type, b.IsPrimary)).ToList()));
cfg.NewConfig()
.ConstructUsing(src => new CounterpartyDto(
src.Id, src.Name, src.LegalName, src.Type,
src.Bin, src.Iin, src.TaxNumber,
src.CountryId, src.Country != null ? src.Country.Name : null,
src.Address, src.Phone, src.Email,
src.BankName, src.BankAccount, src.Bik,
src.ContactPerson, src.Notes));
return cfg;
}
}