feat(edit): 定价试算开发、SKU 样式功能修改
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const commissionImportRequestSchema = z
|
||||
.object({
|
||||
platform_id: z.uuid(),
|
||||
version: z.string().trim().min(1).max(100),
|
||||
source_reference: z.string().trim().min(3).max(500),
|
||||
captured_on: z.iso.date(),
|
||||
document: z.unknown().refine((v) => v !== undefined, '请上传 JSON'),
|
||||
})
|
||||
.strict();
|
||||
export const commissionImportPublishSchema =
|
||||
commissionImportRequestSchema.extend({
|
||||
preview_hash: z.string().regex(/^[a-f0-9]{64}$/),
|
||||
confirm_scope: z.literal(true),
|
||||
});
|
||||
export type CommissionImportRequest = z.infer<
|
||||
typeof commissionImportRequestSchema
|
||||
>;
|
||||
export type CommissionImportPublish = z.infer<
|
||||
typeof commissionImportPublishSchema
|
||||
>;
|
||||
export type CommissionBand = {
|
||||
min_price: string | null;
|
||||
max_price: string | null;
|
||||
min_inclusive: boolean;
|
||||
max_inclusive: boolean;
|
||||
commission_rate: string;
|
||||
};
|
||||
export type CommissionImportCategory = {
|
||||
source_category_id: string;
|
||||
parent_source_id: string;
|
||||
name: string;
|
||||
parent_name: string;
|
||||
bands: CommissionBand[];
|
||||
status: 'eligible' | 'excluded';
|
||||
issues: string[];
|
||||
change: 'added' | 'changed' | 'unchanged';
|
||||
mapped_types: number;
|
||||
};
|
||||
export type CommissionImportSummary = {
|
||||
categories: number;
|
||||
rules: number;
|
||||
eligible: number;
|
||||
excluded: number;
|
||||
mapped_types: number;
|
||||
removed: number;
|
||||
};
|
||||
export type CommissionImportPreview = {
|
||||
preview_hash: string;
|
||||
previous_import_id: string | null;
|
||||
categories: CommissionImportCategory[];
|
||||
summary: CommissionImportSummary;
|
||||
};
|
||||
export type CommissionImportVersion = {
|
||||
id: string;
|
||||
platform_id: string;
|
||||
version: string;
|
||||
source_reference: string;
|
||||
captured_on: string;
|
||||
activated_at: string;
|
||||
is_current: boolean;
|
||||
summary: CommissionImportSummary;
|
||||
};
|
||||
export type CommissionImportDetail = CommissionImportVersion & {
|
||||
categories: CommissionImportCategory[];
|
||||
};
|
||||
export type CommissionRulesResult = {
|
||||
category_name: string;
|
||||
currency_code: string;
|
||||
version: string;
|
||||
source: string;
|
||||
effective_from: string;
|
||||
bands: Array<CommissionBand & { id: string }>;
|
||||
};
|
||||
export type CommissionDetails = CommissionBand & {
|
||||
category_name: string;
|
||||
currency_code: string;
|
||||
version: string;
|
||||
source: string;
|
||||
};
|
||||
@@ -9,3 +9,4 @@ export * from './online-products';
|
||||
|
||||
export * from './pricing';
|
||||
export * from './pricing-admin';
|
||||
export * from './category-commissions';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { CommissionDetails } from './category-commissions';
|
||||
import { z } from 'zod';
|
||||
|
||||
const decimal = z.string().regex(/^\d{1,9}(\.\d{1,8})?$/);
|
||||
@@ -132,6 +133,7 @@ export type PricingPreviewRequest = z.infer<typeof pricingPreviewRequestSchema>;
|
||||
export type PricingSelection = z.infer<typeof pricingSelectionSchema>;
|
||||
export type PricingPolicyRequest = z.infer<typeof pricingPolicyRequestSchema>;
|
||||
export type PricingOption = {
|
||||
commission_details?: CommissionDetails;
|
||||
id: string;
|
||||
transport_method_id: string;
|
||||
transport_name: string;
|
||||
|
||||
@@ -202,6 +202,8 @@ export type ProductEditorSnapshot = {
|
||||
};
|
||||
default_physical_spec: PhysicalSpec;
|
||||
default_package_spec: PackageSpec;
|
||||
package_weight_mode: 'common' | 'variant';
|
||||
package_dimensions_mode: 'common' | 'variant';
|
||||
};
|
||||
profile: {
|
||||
id: string;
|
||||
@@ -507,6 +509,8 @@ export const saveProductEditorDraftRequestSchema = z
|
||||
supply_note: nullableText(4_000),
|
||||
default_physical_spec: physicalSpec,
|
||||
default_package_spec: packageSpec,
|
||||
package_weight_mode: z.enum(['common', 'variant']).optional(),
|
||||
package_dimensions_mode: z.enum(['common', 'variant']).optional(),
|
||||
})
|
||||
.strict(),
|
||||
profile: z
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
SET search_path TO atlas, public;
|
||||
|
||||
-- 平台共享参考规则;所有写入口均要求系统管理员,批次保存原始来源以供追溯。
|
||||
CREATE TABLE commission_imports (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
platform_id UUID NOT NULL REFERENCES platforms(id) ON DELETE RESTRICT,
|
||||
version TEXT NOT NULL CHECK(length(version) BETWEEN 1 AND 100),
|
||||
source_reference TEXT NOT NULL,
|
||||
captured_on DATE NOT NULL,
|
||||
document JSONB NOT NULL,
|
||||
content_hash CHAR(64) NOT NULL,
|
||||
categories JSONB NOT NULL,
|
||||
summary JSONB NOT NULL,
|
||||
activated_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
activated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
is_current BOOLEAN NOT NULL DEFAULT true,
|
||||
UNIQUE(platform_id,version),
|
||||
UNIQUE(platform_id,content_hash),
|
||||
UNIQUE(platform_id,id)
|
||||
);
|
||||
CREATE UNIQUE INDEX commission_import_current ON commission_imports(platform_id) WHERE is_current;
|
||||
ALTER TABLE commission_category_rules ADD COLUMN import_id UUID REFERENCES commission_imports(id) ON DELETE RESTRICT;
|
||||
ALTER TABLE platform_product_category_commission_mappings ADD COLUMN import_id UUID;
|
||||
ALTER TABLE platform_product_category_commission_mappings ADD CONSTRAINT commission_mapping_import_platform_fk
|
||||
FOREIGN KEY(platform_id,import_id) REFERENCES commission_imports(platform_id,id) ON DELETE RESTRICT;
|
||||
CREATE UNIQUE INDEX commission_import_category_key ON commission_categories(platform_id,source_category_key)
|
||||
WHERE source_category_key LIKE 'maozierp:%';
|
||||
CREATE INDEX commission_rules_import ON commission_category_rules(import_id);
|
||||
CREATE INDEX commission_mapping_import ON platform_product_category_commission_mappings(import_id);
|
||||
|
||||
-- 已发布版本及数值不可改写;换版仅允许结束有效期,仍保留历史报价引用。
|
||||
CREATE FUNCTION guard_commission_import_history() RETURNS trigger LANGUAGE plpgsql SET search_path=atlas,public AS $$
|
||||
BEGIN
|
||||
IF TG_TABLE_NAME='commission_imports' THEN
|
||||
IF TG_OP='DELETE' OR (to_jsonb(NEW)-'is_current') IS DISTINCT FROM (to_jsonb(OLD)-'is_current') OR NOT OLD.is_current OR NEW.is_current THEN
|
||||
RAISE EXCEPTION '佣金版本不可改写' USING ERRCODE='23514';
|
||||
END IF;
|
||||
ELSIF OLD.import_id IS NOT NULL THEN
|
||||
IF TG_OP='DELETE' OR (to_jsonb(NEW)-'effective_to'-'status') IS DISTINCT FROM (to_jsonb(OLD)-'effective_to'-'status') OR OLD.effective_to IS NOT NULL OR NEW.effective_to IS NULL OR NEW.status<>'archived' THEN
|
||||
RAISE EXCEPTION '已导入的佣金规则不可改写,请导入新版本' USING ERRCODE='23514';
|
||||
END IF;
|
||||
END IF;
|
||||
IF TG_OP='DELETE' THEN RETURN OLD; END IF;
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
CREATE TRIGGER commission_import_history BEFORE UPDATE OR DELETE ON commission_imports FOR EACH ROW EXECUTE FUNCTION guard_commission_import_history();
|
||||
CREATE TRIGGER commission_rule_history BEFORE UPDATE OR DELETE ON commission_category_rules FOR EACH ROW EXECUTE FUNCTION guard_commission_import_history();
|
||||
CREATE TRIGGER commission_mapping_history BEFORE UPDATE OR DELETE ON platform_product_category_commission_mappings FOR EACH ROW EXECUTE FUNCTION guard_commission_import_history();
|
||||
|
||||
-- 规则与导入版本必须同平台;应用代码不能通过关联写入跨平台规则。
|
||||
CREATE FUNCTION check_commission_rule_import_platform() RETURNS trigger LANGUAGE plpgsql SET search_path=atlas,public AS $$
|
||||
BEGIN
|
||||
IF NEW.import_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1 FROM commission_imports i JOIN commission_categories c ON c.platform_id=i.platform_id
|
||||
WHERE i.id=NEW.import_id AND c.id=NEW.commission_category_id
|
||||
) THEN RAISE EXCEPTION '佣金规则与导入平台不一致' USING ERRCODE='23514'; END IF;
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
CREATE TRIGGER commission_rule_import_platform BEFORE INSERT OR UPDATE ON commission_category_rules FOR EACH ROW EXECUTE FUNCTION check_commission_rule_import_platform();
|
||||
@@ -0,0 +1,30 @@
|
||||
SET search_path TO atlas, public;
|
||||
|
||||
-- 包装重量与尺寸可分别由商品通用值或逐 SKU 值填写;空草稿也需要保存模式。
|
||||
ALTER TABLE products
|
||||
ADD COLUMN package_weight_mode TEXT NOT NULL DEFAULT 'common'
|
||||
CHECK (package_weight_mode IN ('common', 'variant')),
|
||||
ADD COLUMN package_dimensions_mode TEXT NOT NULL DEFAULT 'common'
|
||||
CHECK (package_dimensions_mode IN ('common', 'variant'));
|
||||
|
||||
-- 旧编辑器在每个 SKU 都有对应覆盖值时即展示逐 SKU 表单,迁移时保留该显示模式。
|
||||
UPDATE products p
|
||||
SET package_weight_mode = 'variant'
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM product_variants v WHERE v.product_id = p.id AND v.deleted_at IS NULL
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM product_variants v
|
||||
WHERE v.product_id = p.id AND v.deleted_at IS NULL AND v.shipping_weight_g IS NULL
|
||||
);
|
||||
|
||||
UPDATE products p
|
||||
SET package_dimensions_mode = 'variant'
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM product_variants v WHERE v.product_id = p.id AND v.deleted_at IS NULL
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM product_variants v
|
||||
WHERE v.product_id = p.id AND v.deleted_at IS NULL
|
||||
AND (v.package_length_mm IS NULL OR v.package_width_mm IS NULL OR v.package_height_mm IS NULL)
|
||||
);
|
||||
Reference in New Issue
Block a user