feat(edit): 商品编辑页开发

This commit is contained in:
R524809
2026-09-10 17:45:46 +08:00
parent 130a005231
commit 946af649dd
22 changed files with 7034 additions and 518 deletions
@@ -0,0 +1,10 @@
SET search_path TO atlas, public;
-- M3.3 keeps the user-curated AI source material beside the product aggregate.
-- The original collection normalized_payload/raw_evidence remains immutable
-- evidence; this nullable document is initialized from that evidence on read
-- and becomes durable only after the user saves the editor draft.
ALTER TABLE products
ADD COLUMN ai_source_material JSONB,
ADD CONSTRAINT ck_products_ai_source_material_object
CHECK (ai_source_material IS NULL OR jsonb_typeof(ai_source_material) = 'object');
@@ -0,0 +1,40 @@
SET search_path TO atlas, public;
-- M3.3.1 evolves the full AI source snapshot into field-level overrides.
-- Preserve any document saved between migrations 019 and 020 by treating all
-- of its material fields as explicit overrides based on its recorded source
-- snapshot revision.
ALTER TABLE products
ADD COLUMN ai_source_material_base_revision BIGINT
CHECK (ai_source_material_base_revision IS NULL OR ai_source_material_base_revision > 0);
-- The migrator owns the table, but products uses FORCE RLS. Temporarily turn
-- it off inside this atomic migration so existing rows from every workspace
-- can be transformed; the table lock prevents application writes in between.
ALTER TABLE products NO FORCE ROW LEVEL SECURITY;
ALTER TABLE products DISABLE ROW LEVEL SECURITY;
UPDATE products
SET ai_source_material_base_revision =
CASE
WHEN ai_source_material ? 'source_snapshot_revision'
AND (ai_source_material->>'source_snapshot_revision') ~ '^[1-9][0-9]*$'
THEN (ai_source_material->>'source_snapshot_revision')::BIGINT
ELSE NULL
END,
ai_source_material = ai_source_material - 'source_snapshot_revision'
WHERE ai_source_material IS NOT NULL;
ALTER TABLE products RENAME COLUMN ai_source_material TO ai_source_material_overrides;
ALTER TABLE products
ALTER COLUMN ai_source_material_overrides SET DEFAULT '{}'::jsonb;
UPDATE products
SET ai_source_material_overrides = '{}'::jsonb
WHERE ai_source_material_overrides IS NULL;
ALTER TABLE products
ALTER COLUMN ai_source_material_overrides SET NOT NULL;
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE products FORCE ROW LEVEL SECURITY;
@@ -0,0 +1,46 @@
SET search_path TO atlas, public;
-- M3.4 stores the platform's cached category attribute contract separately
-- from workspace drafts. Product attributes always reference IDs from this
-- schema; dictionary values are a cache, never free-form client metadata.
CREATE TABLE platform_product_category_attributes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
platform_id UUID NOT NULL REFERENCES platforms(id) ON DELETE CASCADE,
platform_product_category_id UUID NOT NULL,
platform_attribute_id TEXT NOT NULL,
complex_id TEXT NOT NULL DEFAULT '0',
name TEXT NOT NULL,
value_type TEXT NOT NULL CHECK (value_type IN ('text', 'integer', 'decimal', 'boolean', 'dictionary')),
is_required BOOLEAN NOT NULL DEFAULT false,
is_collection BOOLEAN NOT NULL DEFAULT false,
max_value_count INTEGER NOT NULL DEFAULT 1 CHECK (max_value_count BETWEEN 1 AND 100),
dictionary_id TEXT,
is_aspect BOOLEAN NOT NULL DEFAULT false,
source_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
FOREIGN KEY (platform_id, platform_product_category_id)
REFERENCES platform_product_categories(platform_id, id) ON DELETE CASCADE,
UNIQUE (platform_id, platform_product_category_id, platform_attribute_id, complex_id),
CHECK ((value_type = 'dictionary') = (dictionary_id IS NOT NULL)),
CHECK (is_collection OR max_value_count = 1)
);
CREATE INDEX ix_platform_product_category_attributes_lookup
ON platform_product_category_attributes (platform_id, platform_product_category_id, is_required DESC, name, id);
CREATE TABLE platform_attribute_dictionary_values (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
platform_id UUID NOT NULL REFERENCES platforms(id) ON DELETE CASCADE,
dictionary_id TEXT NOT NULL,
external_value_id TEXT NOT NULL,
value TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true,
source_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
synced_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (platform_id, dictionary_id, external_value_id)
);
CREATE INDEX ix_platform_attribute_dictionary_values_search
ON platform_attribute_dictionary_values (platform_id, dictionary_id, is_active, value, external_value_id);