Files
PowderCoatingLogix/src/PowderCoating.Core/Entities/CatalogItem.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

116 lines
4.4 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.
namespace PowderCoating.Core.Entities
{
/// <summary>
/// Represents a reusable item in the product catalog that can be quickly added to quotes.
/// Stores default values for common powder coating jobs.
/// </summary>
public class CatalogItem : BaseEntity
{
/// <summary>
/// Gets or sets the item name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the optional item description.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Gets or sets the optional SKU (Stock Keeping Unit) or item code.
/// </summary>
public string? SKU { get; set; }
/// <summary>
/// Gets or sets the category ID this item belongs to.
/// </summary>
public int CategoryId { get; set; }
/// <summary>
/// Gets or sets the category navigation property.
/// </summary>
public virtual CatalogCategory Category { get; set; } = null!;
/// <summary>
/// Gets or sets the default price for this item.
/// Can be overridden when added to a quote.
/// </summary>
public decimal DefaultPrice { get; set; }
/// <summary>
/// Gets or sets whether this item typically requires sandblasting.
/// </summary>
public bool DefaultRequiresSandblasting { get; set; }
/// <summary>
/// Gets or sets whether this item typically requires masking/taping.
/// </summary>
public bool DefaultRequiresMasking { get; set; }
/// <summary>
/// Gets or sets the estimated processing time in minutes.
/// </summary>
public int? DefaultEstimatedMinutes { get; set; }
/// <summary>
/// Gets or sets the approximate surface area in square feet (or square meters if using metric).
/// </summary>
public decimal? ApproximateArea { get; set; }
/// <summary>
/// Gets or sets the display order for sorting items within a category.
/// Lower numbers appear first.
/// </summary>
public int DisplayOrder { get; set; }
/// <summary>
/// Gets or sets whether this item is currently active and visible.
/// </summary>
public bool IsActive { get; set; } = true;
/// <summary>
/// When true, this item is retail merchandise available for direct sale on an invoice
/// without a job (e.g. branded apparel, cleaning products). Inherited from the category
/// by default but can be overridden per item.
/// </summary>
public bool IsMerchandise { get; set; } = false;
/// <summary>
/// Optional link to an inventory item so stock is decremented when this item is sold.
/// </summary>
public int? InventoryItemId { get; set; }
public virtual InventoryItem? InventoryItem { get; set; }
// ── Financial Account Mapping ──────────────────────────────────────────
/// <summary>
/// Gets or sets the revenue account ID for invoicing this item.
/// When null, falls back to the default revenue account.
/// </summary>
public int? RevenueAccountId { get; set; }
/// <summary>
/// Gets or sets the COGS account ID for recording material cost when this item is used.
/// When null, falls back to the default COGS account.
/// </summary>
public int? CogsAccountId { get; set; }
public virtual Account? RevenueAccount { get; set; }
public virtual Account? CogsAccount { get; set; }
// ── Images ────────────────────────────────────────────────────────────
/// <summary>
/// Blob path of the full-size uploaded image, relative to the catalogimages container.
/// Null when no image has been uploaded.
/// </summary>
public string? ImagePath { get; set; }
/// <summary>
/// Blob path of the 200×200 thumbnail generated on upload.
/// Null when no image has been uploaded.
/// </summary>
public string? ThumbnailPath { get; set; }
}
}