feat(edit): 编辑上报细节优化
This commit is contained in:
@@ -218,6 +218,125 @@ describe('首次发布 HTTP / 数据库 / Worker 闭环', () => {
|
||||
await app?.close();
|
||||
});
|
||||
|
||||
it('商品颜色按属性 ID 支持多选保存、重读和发布,其他单值属性仍限制数量', async () => {
|
||||
const f = await fixture(1);
|
||||
const dictionaryId = `color-${randomUUID()}`;
|
||||
await db.workspaceTransaction(workspace, async tx => {
|
||||
const ownCategory = (await tx.query<{ id: string }>(
|
||||
`INSERT INTO atlas.platform_product_categories(platform_id,external_category_id,external_type_id,name,path)
|
||||
VALUES($1,$2,'1002','颜色多选测试','颜色多选测试') RETURNING id`,
|
||||
[platform, String(Date.now()) + '96'],
|
||||
)).rows[0].id;
|
||||
const color = (await tx.query<{ id: string }>(
|
||||
`INSERT INTO atlas.platform_product_category_attributes
|
||||
(platform_id,platform_product_category_id,platform_attribute_id,name,value_type,is_required,is_aspect,is_collection,max_value_count,dictionary_id)
|
||||
VALUES($1,$2,'10096','Цвет товара','dictionary',true,true,false,1,$3) RETURNING id`,
|
||||
[platform, ownCategory, dictionaryId],
|
||||
)).rows[0].id;
|
||||
await tx.query(`INSERT INTO atlas.platform_product_category_attributes
|
||||
(platform_id,platform_product_category_id,platform_attribute_id,name,value_type,is_aspect,is_collection,max_value_count)
|
||||
VALUES($1,$2,'200001','单值测试','text',true,false,1)`, [platform, ownCategory]);
|
||||
await tx.query(`INSERT INTO atlas.platform_catalog_dictionary_values
|
||||
(platform_id,platform_product_category_id,attribute_id,dictionary_id,external_value_id,value)
|
||||
VALUES($1,$2,$3,$4,'61579','红色'),($1,$2,$3,$4,'61581','蓝色')`,
|
||||
[platform, ownCategory, color, dictionaryId]);
|
||||
await tx.query(`INSERT INTO atlas.platform_catalog_resource_states
|
||||
(platform_id,scope_key,scope,category_id,attribute_id,last_success_at,is_complete)
|
||||
VALUES($1,'attributes:' || $2::text,'attributes',$2::uuid,NULL,now(),true),
|
||||
($1,'dictionary:' || $3::text,'dictionary',$2::uuid,$3::uuid,now(),true)`,
|
||||
[platform, ownCategory, color]);
|
||||
await tx.query('UPDATE atlas.product_platform_profiles SET platform_product_category_id=$2 WHERE id=$1', [f.profile, ownCategory]);
|
||||
await tx.query('UPDATE atlas.product_variant_platform_listings SET pricing_quote_id=NULL WHERE profile_id=$1', [f.profile]);
|
||||
await tx.query(`UPDATE atlas.media_assets SET width_px=800,height_px=800
|
||||
WHERE id IN (SELECT asset_id FROM atlas.product_variant_listing_media WHERE profile_id=$1)`, [f.profile]);
|
||||
});
|
||||
const before = (await request(`/products/${f.product}/editor`)).body.data;
|
||||
const colorDraft = {
|
||||
scope: 'variant', variant_id: f.ids[0], platform_attribute_id: '10096', complex_id: '0',
|
||||
values: ['61579', '61581'], language_code: null, source: 'manual', confirmation_status: 'confirmed',
|
||||
};
|
||||
const draft = {
|
||||
ai_source_material: before.source_material,
|
||||
ai_source_overridden_fields: before.source_material_state.overridden_fields,
|
||||
common: { ...before.product.common, default_physical_spec: before.product.default_physical_spec,
|
||||
default_package_spec: before.product.default_package_spec },
|
||||
profile: { target_store_id: store, fulfillment: before.profile.fulfillment, title: before.profile.title,
|
||||
description: before.profile.description, tags: before.profile.tags,
|
||||
platform_product_category_id: before.profile.category.id, category_source: 'manual' },
|
||||
attributes: [colorDraft],
|
||||
variants: before.variants.map((v: any) => ({
|
||||
id: v.id, client_key: null, display_name: v.display_name, source_sku: v.source_sku,
|
||||
specification_values: v.specification_values, purchase_cost_cny: v.purchase_cost_cny,
|
||||
title: v.title, tags: v.tags, description: v.description, physical_spec: v.physical_spec,
|
||||
package_spec: v.package_spec, offer_id: v.listing.offer_id,
|
||||
media_asset_ids: v.media.map((m: any) => m.asset_id),
|
||||
})),
|
||||
};
|
||||
const save = (attributes = draft.attributes) => request(`/products/${f.product}/editor-draft`, 'PUT',
|
||||
{ ...draft, attributes }, { 'Idempotency-Key': randomUUID(), 'If-Match': '"1:1"' });
|
||||
const invalidSingle = await save([{ ...colorDraft, platform_attribute_id: '200001', values: ['one', 'two'] }]);
|
||||
assert.equal(invalidSingle.status, 422);
|
||||
assert.equal(invalidSingle.body.error.code, 'ATTRIBUTE_VALUE_COUNT_INVALID');
|
||||
const invalidDictionary = await save([{ ...colorDraft, values: ['61579', '99999999'] }]);
|
||||
assert.equal(invalidDictionary.status, 422);
|
||||
const saved = await save();
|
||||
assert.equal(saved.status, 200, JSON.stringify(saved));
|
||||
const after = (await request(`/products/${f.product}/editor`)).body.data;
|
||||
assert.deepEqual(after.attributes.find((a: any) => a.platform_attribute_id === '10096').values, ['61579', '61581']);
|
||||
// fixture 使用预先确认的模拟报价;草稿保存后恢复其有效标记,仅验证颜色发布映射。
|
||||
await db.workspaceTransaction(workspace, tx => tx.query(
|
||||
`UPDATE atlas.product_variant_platform_listings listing SET pricing_valid=true,
|
||||
pricing_quote_id=(SELECT quote.id FROM atlas.pricing_quotes quote
|
||||
WHERE quote.profile_id=listing.profile_id AND quote.product_variant_id=listing.product_variant_id LIMIT 1)
|
||||
WHERE listing.profile_id=$1`, [f.profile]));
|
||||
const job = await request(`/product-platform-profiles/${f.profile}/publish-jobs`, 'POST',
|
||||
{ variant_ids: f.ids }, { 'Idempotency-Key': randomUUID(), 'If-Match': `"${after.product.revision}:${after.profile.revision}"` });
|
||||
assert.equal(job.status, 202, JSON.stringify(job));
|
||||
const snapshot = await db.workspaceTransaction(workspace, async tx =>
|
||||
(await tx.query<{ snapshot: { attributes: Array<{ id: number; values: unknown[] }> } }>(
|
||||
'SELECT snapshot FROM atlas.product_publish_job_items WHERE job_id=$1', [job.body.data.publish_job.id])).rows[0].snapshot);
|
||||
assert.deepEqual(snapshot.attributes.find(a => a.id === 10096)?.values, [
|
||||
{ dictionary_value_id: 61579, value: '红色' },
|
||||
{ dictionary_value_id: 61581, value: '蓝色' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('简介保留 SKU 覆盖和 SPU 继承的换行,Ozon 请求转为 br 且不修改快照', async () => {
|
||||
const f = await fixture();
|
||||
const common = 'Общее описание\r\n\r\nХарактеристики:\r\n• Один & два';
|
||||
const own = 'Описание SKU\n\nРазмер < 10\n• Красный';
|
||||
await db.workspaceTransaction(workspace, async tx => {
|
||||
await tx.query(`INSERT INTO atlas.platform_product_category_attributes
|
||||
(platform_id,platform_product_category_id,platform_attribute_id,name,value_type,is_required,is_aspect,max_value_count)
|
||||
VALUES($1,$2,'4191','简介','text',false,false,1) ON CONFLICT DO NOTHING`, [platform, category]);
|
||||
await tx.query('UPDATE atlas.product_platform_profiles SET description=$2 WHERE id=$1', [f.profile, common]);
|
||||
await tx.query('UPDATE atlas.product_variant_platform_listings SET description=$3 WHERE profile_id=$1 AND product_variant_id=$2', [f.profile,f.ids[0],own]);
|
||||
});
|
||||
const created = await publish(f);
|
||||
assert.equal(created.status, 202, JSON.stringify(created));
|
||||
const job = created.body.data.publish_job as ProductPublishJob;
|
||||
const snapshots = () => db.workspaceTransaction(workspace, async tx => (await tx.query<{
|
||||
variant_id: string; snapshot: { attributes: Array<{ id: number; values: Array<{ value: string }> }> };
|
||||
}>('SELECT variant_id,snapshot FROM atlas.product_publish_job_items WHERE job_id=$1', [job.id])).rows);
|
||||
const before = await snapshots();
|
||||
for (const row of before) assert.equal(row.snapshot.attributes.find(a=>a.id===4191)?.values[0].value,
|
||||
row.variant_id === f.ids[0] ? own : common);
|
||||
let submitted = false;
|
||||
await run(job, async (_client, _key, path, body) => {
|
||||
if (path === '/v3/product/info/list') return { items: [] };
|
||||
assert.equal(path, '/v3/product/import');
|
||||
submitted = true;
|
||||
const items = (body as { items: Array<{ attributes: Array<{ id: number; values: Array<{ value: string }> }> }> }).items;
|
||||
assert.deepEqual(new Set(items.map(item=>item.attributes.find(a=>a.id===4191)?.values[0].value)), new Set([
|
||||
'Описание SKU<br><br>Размер < 10<br>• Красный',
|
||||
'Общее описание<br><br>Характеристики:<br>• Один & два',
|
||||
]));
|
||||
return { result: { task_id: 778899 } };
|
||||
});
|
||||
assert.equal(submitted,true);
|
||||
assert.deepEqual(await snapshots(), before);
|
||||
});
|
||||
|
||||
it('多个主题标签合并为一个 Ozon 属性值,SKU 覆盖与继承均保留', async () => {
|
||||
const f = await fixture();
|
||||
await db.workspaceTransaction(workspace, async (tx) => {
|
||||
|
||||
Reference in New Issue
Block a user