Files
PowderCoatingLogix/src/PowderCoating.Application/Mappings/CatalogProfile.cs
T
spouliot 00bf8a4cd0 Add catalog item images with thumbnail preview in wizard
Each catalog item now supports one optional image (jpg/jpeg/png/gif/webp,
max 10 MB). Uploading generates a 200x200 JPEG thumbnail automatically via
SixLabors.ImageSharp. Images are stored in Azure Blob Storage under a new
catalogimages container, keyed by {companyId}/catalog/{itemId}/.

- CatalogItem entity: ImagePath + ThumbnailPath (nullable string fields)
- Migration: AddCatalogItemImages applied
- ICatalogImageService / CatalogImageService: upload, thumbnail generation,
  delete; old blobs replaced atomically on re-upload
- CatalogItemsController: Create/Edit accept optional IFormFile image;
  Image(id, thumbnail) action serves blobs with [Authorize] so wizard users
  can load thumbnails without CanManageProducts policy
- Catalog index (_CategoryNode): 40x40 thumbnail (or placeholder icon)
  left of each item name
- Details view: image card in right column with click-to-full-size link
- Create/Edit views: file picker with live preview; Edit shows current
  thumbnail with Remove checkbox
- Wizard (item-wizard.js): thumbnails in product list with hover preview
  that follows the cursor (showCatalogPreview / moveCatalogPreview);
  fixed Bootstrap d-flex !important bug that broke the filter box by
  moving flex layout to an inner wrapper div

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 09:33:59 -04:00

138 lines
7.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using AutoMapper;
using PowderCoating.Application.DTOs.Catalog;
using PowderCoating.Core.Entities;
using System.Collections.Generic;
using System.Linq;
namespace PowderCoating.Application.Mappings
{
/// <summary>
/// AutoMapper profile for product catalog entities.
/// </summary>
public class CatalogProfile : Profile
{
public CatalogProfile()
{
// ==================== Category Mappings ====================
// Category -> CategoryDto
CreateMap<CatalogCategory, CategoryDto>()
.ForMember(dest => dest.ParentCategoryName,
opt => opt.MapFrom(src => src.ParentCategory != null ? src.ParentCategory.Name : null))
.ForMember(dest => dest.ItemCount,
opt => opt.MapFrom(src => src.Items.Count(i => !i.IsDeleted)))
.ForMember(dest => dest.SubCategoryCount,
opt => opt.MapFrom(src => src.SubCategories.Count(c => !c.IsDeleted)));
// CreateCategoryDto -> CatalogCategory
CreateMap<CreateCategoryDto, CatalogCategory>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.CompanyId, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedBy, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.ParentCategory, opt => opt.Ignore())
.ForMember(dest => dest.SubCategories, opt => opt.Ignore())
.ForMember(dest => dest.Items, opt => opt.Ignore());
// CatalogCategory -> UpdateCategoryDto (reverse mapping for Edit GET)
CreateMap<CatalogCategory, UpdateCategoryDto>();
// UpdateCategoryDto -> CatalogCategory
CreateMap<UpdateCategoryDto, CatalogCategory>()
.ForMember(dest => dest.CompanyId, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedBy, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.ParentCategory, opt => opt.Ignore())
.ForMember(dest => dest.SubCategories, opt => opt.Ignore())
.ForMember(dest => dest.Items, opt => opt.Ignore());
// CatalogCategory -> CategoryTreeDto (recursive)
CreateMap<CatalogCategory, CategoryTreeDto>()
.ForMember(dest => dest.ItemCount,
opt => opt.MapFrom(src => src.Items.Count(i => !i.IsDeleted)))
.ForMember(dest => dest.HasItems,
opt => opt.MapFrom(src => src.Items.Any(i => !i.IsDeleted)))
.ForMember(dest => dest.Children,
opt => opt.MapFrom(src => src.SubCategories.Where(c => !c.IsDeleted).OrderBy(c => c.DisplayOrder)));
// ==================== Item Mappings ====================
// CatalogItem -> CatalogItemDto
CreateMap<CatalogItem, CatalogItemDto>()
.ForMember(dest => dest.CategoryName,
opt => opt.MapFrom(src => src.Category.Name))
.ForMember(dest => dest.FullCategoryPath,
opt => opt.MapFrom(src => GetCategoryPath(src.Category)))
.ForMember(dest => dest.RevenueAccountName,
opt => opt.MapFrom(src => src.RevenueAccount != null
? $"{src.RevenueAccount.AccountNumber} {src.RevenueAccount.Name}" : null))
.ForMember(dest => dest.CogsAccountName,
opt => opt.MapFrom(src => src.CogsAccount != null
? $"{src.CogsAccount.AccountNumber} {src.CogsAccount.Name}" : null));
// CatalogItem -> CatalogItemListDto
CreateMap<CatalogItem, CatalogItemListDto>()
.ForMember(dest => dest.CategoryName,
opt => opt.MapFrom(src => src.Category.Name));
// CreateCatalogItemDto -> CatalogItem
CreateMap<CreateCatalogItemDto, CatalogItem>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.CompanyId, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedBy, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.Category, opt => opt.Ignore())
.ForMember(dest => dest.RevenueAccount, opt => opt.Ignore())
.ForMember(dest => dest.CogsAccount, opt => opt.Ignore())
// Image paths are set by CatalogImageService after the entity is saved, not from the DTO.
.ForMember(dest => dest.ImagePath, opt => opt.Ignore())
.ForMember(dest => dest.ThumbnailPath, opt => opt.Ignore());
// UpdateCatalogItemDto -> CatalogItem
CreateMap<UpdateCatalogItemDto, CatalogItem>()
.ForMember(dest => dest.CompanyId, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
.ForMember(dest => dest.UpdatedBy, opt => opt.Ignore())
.ForMember(dest => dest.IsDeleted, opt => opt.Ignore())
.ForMember(dest => dest.Category, opt => opt.Ignore())
.ForMember(dest => dest.RevenueAccount, opt => opt.Ignore())
.ForMember(dest => dest.CogsAccount, opt => opt.Ignore())
.ForMember(dest => dest.ImagePath, opt => opt.Ignore())
.ForMember(dest => dest.ThumbnailPath, opt => opt.Ignore());
// CatalogItem -> UpdateCatalogItemDto (reverse mapping for Edit)
CreateMap<CatalogItem, UpdateCatalogItemDto>();
}
/// <summary>
/// Builds the full category path by walking up the hierarchy.
/// Example: "Wheels > 18 inch"
/// </summary>
private string GetCategoryPath(CatalogCategory category)
{
var path = new List<string>();
var current = category;
// Walk up the hierarchy
while (current != null)
{
path.Insert(0, current.Name);
current = current.ParentCategory;
}
return string.Join(" > ", path);
}
}
}