# Data Model ## Core relationship ```text User → Seller → Product → Inventory Item → Listing │ ├── Images ├── Acquisition ├── Pricing Observations ├── Market Valuations └── Activity Sale → Sale Items → Inventory Items ``` ## Product Represents what the item is, independent of physical ownership. Examples: - Charizard ex — SV151 — #199/165 - Pokémon Center Worlds 2025 Pikachu Plush Products persist after all inventory is sold. ## Inventory Item Represents one physical copy. Each copy has its own: - condition - images - acquisition - asking price/listing - market valuation - notes - status Do not model multiple physical copies as a quantity field on one Inventory Item. ## Listing Represents how an Inventory Item is currently offered publicly. A Product can have many physical Inventory Items with different conditions and asking prices. ## Sale A manual external transaction. One Sale can contain multiple Sale Items. ## Provider Product Mapping Provider mappings are Product-level relationships. Recommended fields: - id - product_id - provider_id - mapping_type (`PRODUCT` or `SEARCH`) - external_identifier - search_query / search parameters - source_url - status - metadata - created_at - updated_at - disabled_at A provider can support one or both mapping types. eBay supports both: - specific product/catalog mappings where appropriate - search/query mappings where no canonical product is appropriate Mappings can be replaced without destroying historical records. Only one active/default mapping per provider should be used for normal pricing unless a provider integration explicitly supports multiple active sources. ## Suggested pricing tables ### pricing_providers `id, name, code, is_enabled, priority, configuration, created_at, updated_at` ### product_pricing_providers `product_id, provider_id, is_enabled` ### pricing_provider_mappings As described above. ### pricing_observations `id, product_id, inventory_item_id, provider_id, source_identifier, source_url, condition_id, price_minor, currency, observed_at, quality_score, is_outlier, outlier_reason, raw_reference` ### market_valuations `id, inventory_item_id, value_minor, currency, strategy, confidence, source_count, calculated_at` ### market_value_overrides `id, inventory_item_id, value_minor, currency, reason, created_by, created_at, removed_at` ### pricing_jobs `id, job_type, status, requested_by, started_at, completed_at, total_items, processed_items, successful_items, failed_items, error_summary, created_at` ## Conditions `condition_profiles`: - id - category_id - name - active - sort ## Seller Settings `seller_settings` stores one JSON settings document per Seller. Supported categories are general contact details, inventory image limits, pricing strategy, and session idle duration. Settings are private to the Seller and are validated through the admin API allowlist. `conditions`: - id - condition_profile_id - name - code - quality_rank - description - active - sort ## Images `inventory_images`: - id - inventory_item_id - storage_key - original_filename - mime_type - file_size - width - height - alt_text - sort_order - is_primary - processing_status - created_at - updated_at ## Acquisitions `acquisitions`: - id - inventory_item_id - method - cost_minor - currency - date - source - location - shipping_minor - tax_minor - other_fees_minor - estimated_trade_value_minor - notes - created_at - updated_at ## Sales `sales`: - id - seller_id - sale_date - sale_location - sold_price_minor - currency - platform_fees_minor - payment_processing_fees_minor - shipping_cost_minor - taxes_minor - other_fees_minor - buyer_name - buyer_contact - notes - created_by - created_at - updated_at `sale_items`: - id - sale_id - inventory_item_id - sold_price_minor - cost_basis_minor ## Activity Activity records retain: - affected Product/Inventory Item - event - previous value where applicable - new value - actor - timestamp Security events are stored separately. ## Money Store money as integer minor units. Negative values are prohibited; zero is valid. USD is canonical for stored/calculated market values unless a documented external representation requires another currency. Public seller descriptions use optional `general.publicDescription` in seller settings: trimmed plain text, at most 2,000 characters. An empty string clears the description. Public listing and product detail DTOs expose only this field as `sellerDescription`, alongside the existing public seller name and contact fields; other settings remain private. No database migration is required. Public contact methods also use the existing seller settings JSON. Explicit empty strings in `general.publicContactEmail` or `general.publicContactPhone` represent removal; omitted fields retain their values during partial updates. Public detail projections normalize empty contacts to `null`. No migration is required, and seller ownership and private settings boundaries are unchanged.