forked from lunanting/cangku
493 lines
19 KiB
JavaScript
493 lines
19 KiB
JavaScript
'use strict';
|
||
|
||
const {
|
||
PRODUCT_TABLE,
|
||
getAlibaba1688Product,
|
||
} = require('./alibaba1688ProductService');
|
||
const planningDatabase = require('./planningDatabase');
|
||
const {
|
||
buildAlibaba1688SkuKey,
|
||
buildAlibaba1688SkuFingerprint,
|
||
} = require('./alibaba1688SkuIdentity');
|
||
const productCodeClient = require('./alibaba1688ProductCodeClient');
|
||
const encodingRegistry = require('./alibaba1688ProductEncodingRegistry');
|
||
|
||
const MAX_CODING_SKUS = 200;
|
||
const MAX_PRODUCT_NAME_LENGTH = 255;
|
||
const MAX_COLOR_SPEC_LENGTH = 255;
|
||
|
||
let metaCache = null;
|
||
|
||
function encodingError(message, statusCode = 400, code = 'INVALID_REQUEST') {
|
||
const error = new Error(message);
|
||
error.statusCode = statusCode;
|
||
error.code = code;
|
||
return error;
|
||
}
|
||
|
||
function normalizeText(value, maxLength = 1000) {
|
||
return String(value ?? '').trim().slice(0, maxLength);
|
||
}
|
||
|
||
function requiredText(value, label, maxLength = 255) {
|
||
const raw = String(value ?? '').trim();
|
||
if (!raw) throw encodingError(`${label}不能为空`, 400, 'PRODUCT_CODE_FIELD_REQUIRED');
|
||
if (raw.length > maxLength) {
|
||
throw encodingError(`${label}不能超过 ${maxLength} 个字符`, 400, 'PRODUCT_CODE_FIELD_TOO_LONG');
|
||
}
|
||
return raw;
|
||
}
|
||
|
||
function optionalNumber(value, label, { allowZero = false } = {}) {
|
||
if (value === null || value === undefined || value === '') return null;
|
||
const number = Number(value);
|
||
if (!Number.isFinite(number) || (allowZero ? number < 0 : number <= 0)) {
|
||
throw encodingError(`${label}${allowZero ? '不能小于 0' : '必须大于 0'}`, 400, 'PRODUCT_CODE_NUMBER_INVALID');
|
||
}
|
||
return number;
|
||
}
|
||
|
||
function sourceSkuKey(sku, position) {
|
||
return buildAlibaba1688SkuKey(sku, position);
|
||
}
|
||
|
||
function normalizeCodingRequest(input = {}, productRecord, actor = {}) {
|
||
const sourceSkus = Array.isArray(productRecord?.product?.skus) ? productRecord.product.skus : [];
|
||
if (!sourceSkus.length) {
|
||
throw encodingError('该 1688 商品没有可编码的 SKU', 422, 'PRODUCT_CODE_SKUS_REQUIRED');
|
||
}
|
||
if (sourceSkus.length > MAX_CODING_SKUS) {
|
||
throw encodingError(
|
||
`该商品共有 ${sourceSkus.length} 个 SKU,编码接口单次最多支持 ${MAX_CODING_SKUS} 个;请先确认最终上架规格`,
|
||
422,
|
||
'PRODUCT_CODE_SKU_LIMIT_EXCEEDED'
|
||
);
|
||
}
|
||
|
||
const inputSkus = Array.isArray(input.skuItems) ? input.skuItems : [];
|
||
if (inputSkus.length !== sourceSkus.length) {
|
||
throw encodingError(
|
||
`必须一次提交该商品全部 ${sourceSkus.length} 个 SKU,当前提交了 ${inputSkus.length} 个`,
|
||
422,
|
||
'PRODUCT_CODE_SKU_COUNT_MISMATCH'
|
||
);
|
||
}
|
||
|
||
const selections = {
|
||
operationCenter: requiredText(input.operationCenter, '运营中心', 120),
|
||
categoryName: requiredText(input.categoryName, '编码分类', 255),
|
||
taxStatus: requiredText(input.taxStatus, '是否含税', 80),
|
||
invoiceType: requiredText(input.invoiceType, '开票类型', 80),
|
||
installationService: requiredText(input.installationService, '安装服务', 120),
|
||
installationMode: requiredText(input.installationMode, '安装方式', 80),
|
||
};
|
||
|
||
const normalizedSkus = sourceSkus.map((sourceSku, index) => {
|
||
const raw = inputSkus[index] || {};
|
||
const expectedSourceSkuKey = sourceSkuKey(sourceSku, index);
|
||
if (requiredText(raw.sourceSkuKey, `第 ${index + 1} 个 SKU 的来源标识`, 4000) !== expectedSourceSkuKey) {
|
||
throw encodingError(
|
||
`第 ${index + 1} 个 SKU 已发生变化,请关闭编码窗口后重新加载`,
|
||
409,
|
||
'PRODUCT_CODE_SKU_SNAPSHOT_CHANGED'
|
||
);
|
||
}
|
||
const productName = requiredText(raw.productName, `第 ${index + 1} 个 SKU 的商品名称`, MAX_PRODUCT_NAME_LENGTH);
|
||
const colorSpecRaw = String(raw.colorSpec ?? '').trim();
|
||
const colorNameRaw = String(raw.colorName ?? '').trim();
|
||
if (!colorSpecRaw && !colorNameRaw) {
|
||
throw encodingError(`第 ${index + 1} 个 SKU 缺少颜色及规格`, 400, 'PRODUCT_CODE_SKU_SPEC_REQUIRED');
|
||
}
|
||
if (colorSpecRaw.length > MAX_COLOR_SPEC_LENGTH || colorNameRaw.length > MAX_COLOR_SPEC_LENGTH) {
|
||
throw encodingError(
|
||
`第 ${index + 1} 个 SKU 的颜色及规格不能超过 ${MAX_COLOR_SPEC_LENGTH} 个字符`,
|
||
400,
|
||
'PRODUCT_CODE_FIELD_TOO_LONG'
|
||
);
|
||
}
|
||
const lengthCm = optionalNumber(raw.lengthCm, `第 ${index + 1} 个 SKU 的长度`);
|
||
const widthCm = optionalNumber(raw.widthCm, `第 ${index + 1} 个 SKU 的宽度`);
|
||
const heightCm = optionalNumber(raw.heightCm, `第 ${index + 1} 个 SKU 的高度`);
|
||
if (lengthCm == null || widthCm == null || heightCm == null) {
|
||
throw encodingError(
|
||
`第 ${index + 1} 个 SKU 的长宽高必须填写完整`,
|
||
400,
|
||
'PRODUCT_CODE_SKU_DIMENSIONS_REQUIRED'
|
||
);
|
||
}
|
||
const weight = optionalNumber(raw.weight, `第 ${index + 1} 个 SKU 的重量`, { allowZero: true });
|
||
return {
|
||
sourceSku,
|
||
sourceSkuKey: expectedSourceSkuKey,
|
||
requestItem: {
|
||
productName,
|
||
...(colorSpecRaw ? { colorSpec: colorSpecRaw } : { colorName: colorNameRaw }),
|
||
...(lengthCm == null ? {} : { lengthCm }),
|
||
...(widthCm == null ? {} : { widthCm }),
|
||
...(heightCm == null ? {} : { heightCm }),
|
||
...(weight == null ? {} : { weight }),
|
||
},
|
||
};
|
||
});
|
||
|
||
const operatorName = normalizeText(actor.name || actor.username, 100) || 'maixiang';
|
||
const requestPayload = {
|
||
...selections,
|
||
ali1688ProductId: requiredText(productRecord.productId, '1688 商品 ID', 100),
|
||
ali1688SupplierName: normalizeText(productRecord.storeName, 255),
|
||
sourceSystem: 'maixiang',
|
||
operatorName,
|
||
operatorDepartment: normalizeText(actor.departmentName, 100),
|
||
skuItems: normalizedSkus.map(item => item.requestItem),
|
||
};
|
||
|
||
return {
|
||
requestPayload,
|
||
sourceSkus: normalizedSkus,
|
||
sourceSkuFingerprint: buildAlibaba1688SkuFingerprint(sourceSkus),
|
||
};
|
||
}
|
||
|
||
function resolveUpstreamSkuOrder(upstreamData, responseItems, normalizedRequest) {
|
||
const fallbackCodes = Array.isArray(upstreamData.productCodes) ? upstreamData.productCodes : [];
|
||
const itemsWithCodes = responseItems.map((item, index) => ({
|
||
...item,
|
||
productCode: normalizeText(item?.productCode || fallbackCodes[index], 200),
|
||
}));
|
||
|
||
if (upstreamData.created !== false) {
|
||
const productCodes = (fallbackCodes.length ? fallbackCodes : itemsWithCodes.map(item => item.productCode))
|
||
.map(value => normalizeText(value, 200))
|
||
.filter(Boolean);
|
||
return { orderedItems: itemsWithCodes, productCodes };
|
||
}
|
||
|
||
if (itemsWithCodes.length !== normalizedRequest.sourceSkus.length) {
|
||
throw encodingError(
|
||
'该 1688 商品已在编码服务中存在,但历史 SKU 明细不足以安全匹配当前规格',
|
||
409,
|
||
'PRODUCT_CODE_REUSED_MAPPING_CONFLICT'
|
||
);
|
||
}
|
||
|
||
const usedIndexes = new Set();
|
||
const orderedItems = normalizedRequest.sourceSkus.map((entry) => {
|
||
const requestName = normalizeText(entry.requestItem.productName, MAX_PRODUCT_NAME_LENGTH);
|
||
const requestSpec = normalizeText(entry.requestItem.colorSpec || entry.requestItem.colorName, MAX_COLOR_SPEC_LENGTH);
|
||
const candidateIndexes = itemsWithCodes
|
||
.map((item, index) => ({ item, index }))
|
||
.filter(({ item, index }) => {
|
||
if (usedIndexes.has(index) || !item.productCode) return false;
|
||
if (normalizeText(item.productName, MAX_PRODUCT_NAME_LENGTH) !== requestName) return false;
|
||
const responseSpec = normalizeText(item.colorSpec, MAX_COLOR_SPEC_LENGTH);
|
||
return !responseSpec || responseSpec === requestSpec;
|
||
})
|
||
.map(({ index }) => index);
|
||
if (candidateIndexes.length !== 1) {
|
||
throw encodingError(
|
||
`已有编码无法唯一匹配 SKU“${requestName}”,已停止后续上架`,
|
||
409,
|
||
'PRODUCT_CODE_REUSED_MAPPING_CONFLICT'
|
||
);
|
||
}
|
||
usedIndexes.add(candidateIndexes[0]);
|
||
return itemsWithCodes[candidateIndexes[0]];
|
||
});
|
||
return {
|
||
orderedItems,
|
||
productCodes: orderedItems.map(item => item.productCode),
|
||
};
|
||
}
|
||
|
||
function normalizeUpstreamResult(upstreamData = {}, normalizedRequest) {
|
||
const responseItems = Array.isArray(upstreamData.items) ? upstreamData.items : [];
|
||
const { orderedItems, productCodes } = resolveUpstreamSkuOrder(
|
||
upstreamData,
|
||
responseItems,
|
||
normalizedRequest
|
||
);
|
||
|
||
if (productCodes.length !== normalizedRequest.sourceSkus.length) {
|
||
throw encodingError(
|
||
`编码服务返回 ${productCodes.length} 个商品编码,但当前商品有 ${normalizedRequest.sourceSkus.length} 个 SKU;已停止后续上架,请核对该 1688 ID 的历史编码`,
|
||
409,
|
||
'PRODUCT_CODE_RESPONSE_COUNT_MISMATCH'
|
||
);
|
||
}
|
||
if (new Set(productCodes).size !== productCodes.length) {
|
||
throw encodingError(
|
||
'编码服务返回了重复商品编码,已停止后续上架',
|
||
409,
|
||
'PRODUCT_CODE_RESPONSE_DUPLICATE'
|
||
);
|
||
}
|
||
const mismatchedProductId = responseItems
|
||
.map(item => normalizeText(item?.ali1688ProductId, 100))
|
||
.filter(Boolean)
|
||
.find(value => value !== normalizedRequest.requestPayload.ali1688ProductId);
|
||
if (mismatchedProductId) {
|
||
throw encodingError(
|
||
'编码服务返回的 1688 商品 ID 与当前商品不一致,已停止后续上架',
|
||
409,
|
||
'PRODUCT_CODE_RESPONSE_PRODUCT_MISMATCH'
|
||
);
|
||
}
|
||
|
||
const styleCodes = [...new Set([
|
||
...(Array.isArray(upstreamData.styleCodes) ? upstreamData.styleCodes : []),
|
||
upstreamData.styleCode,
|
||
...orderedItems.map(item => item?.styleCode),
|
||
].map(value => normalizeText(value, 200)).filter(Boolean))];
|
||
|
||
const skuMappings = normalizedRequest.sourceSkus.map((entry, index) => ({
|
||
sourceSkuKey: entry.sourceSkuKey,
|
||
sourceSkuIndex: Number(entry.sourceSku?.index) || index + 1,
|
||
sourceSkuName: normalizeText(entry.sourceSku?.name, 1200),
|
||
sourceSpecs: Array.isArray(entry.sourceSku?.specs) ? entry.sourceSku.specs : [],
|
||
productName: entry.requestItem.productName,
|
||
colorSpec: entry.requestItem.colorSpec || entry.requestItem.colorName || '',
|
||
lengthCm: entry.requestItem.lengthCm ?? null,
|
||
widthCm: entry.requestItem.widthCm ?? null,
|
||
heightCm: entry.requestItem.heightCm ?? null,
|
||
weight: entry.requestItem.weight ?? null,
|
||
styleCode: normalizeText(orderedItems[index]?.styleCode || upstreamData.styleCode || styleCodes[0], 200),
|
||
productCode: productCodes[index],
|
||
}));
|
||
|
||
return {
|
||
styleCode: normalizeText(upstreamData.styleCode || styleCodes[0], 160),
|
||
styleCodes,
|
||
productCodes,
|
||
skuMappings,
|
||
sourceSkuCount: normalizedRequest.sourceSkus.length,
|
||
sourceSkuFingerprint: normalizedRequest.sourceSkuFingerprint,
|
||
upstreamCreated: upstreamData.created !== false,
|
||
};
|
||
}
|
||
|
||
async function getAlibaba1688ProductCodeMeta(options = {}) {
|
||
const now = Date.now();
|
||
if (!options.force && metaCache && metaCache.expiresAt > now) return metaCache.value;
|
||
const response = await (options.getMeta || productCodeClient.getProductCodeMeta)(options);
|
||
metaCache = {
|
||
value: response.data,
|
||
expiresAt: now + 5 * 60 * 1000,
|
||
};
|
||
return response.data;
|
||
}
|
||
|
||
async function encodeAlibaba1688Product(productId, input = {}, actor = {}, options = {}) {
|
||
const productRecord = await (options.getProduct || getAlibaba1688Product)(productId);
|
||
const shouldLookupExisting = Boolean(options.getExistingEncoding)
|
||
|| (!options.getProduct && !options.claim);
|
||
if (shouldLookupExisting) {
|
||
const existing = await (
|
||
options.getExistingEncoding || encodingRegistry.getEncodingByProductId
|
||
)(productId);
|
||
if (existing?.status === 'encoded') {
|
||
return {
|
||
alreadyEncoded: true,
|
||
encoding: existing,
|
||
};
|
||
}
|
||
}
|
||
const normalized = normalizeCodingRequest(input, productRecord, actor);
|
||
if (!options.getProduct || options.persistProductFingerprint) {
|
||
await (options.persistProductFingerprint || planningDatabase.execute)(
|
||
`UPDATE ${PRODUCT_TABLE}
|
||
SET sku_count = ?, sku_fingerprint = ?
|
||
WHERE product_id = ? AND sync_version = ?
|
||
AND (sku_fingerprint IS NULL OR sku_fingerprint = '' OR sku_fingerprint = ?)`,
|
||
[
|
||
normalized.sourceSkus.length,
|
||
normalized.sourceSkuFingerprint,
|
||
productRecord.productId,
|
||
Number(productRecord.syncVersion) || 1,
|
||
normalized.sourceSkuFingerprint,
|
||
]
|
||
);
|
||
}
|
||
const preparedProduct = await (options.getPreparedProduct || options.getProduct || getAlibaba1688Product)(
|
||
productRecord.productId
|
||
);
|
||
const preparedFingerprint = preparedProduct.skuFingerprint
|
||
|| buildAlibaba1688SkuFingerprint(preparedProduct.product?.skus);
|
||
if (preparedFingerprint !== normalized.sourceSkuFingerprint) {
|
||
throw encodingError(
|
||
'商品在编码确认前发生了 SKU 变化,请关闭编码窗口后重新加载',
|
||
409,
|
||
'PRODUCT_CODE_SKU_SNAPSHOT_CHANGED'
|
||
);
|
||
}
|
||
const claim = await (options.claim || encodingRegistry.claimEncoding)(
|
||
productRecord.productId,
|
||
normalized.requestPayload,
|
||
actor,
|
||
{
|
||
sourceSkuCount: normalized.sourceSkus.length,
|
||
sourceSkuFingerprint: normalized.sourceSkuFingerprint,
|
||
}
|
||
);
|
||
if (!claim.claimed) {
|
||
return {
|
||
alreadyEncoded: true,
|
||
encoding: claim.record,
|
||
};
|
||
}
|
||
|
||
let upstreamResponse = null;
|
||
try {
|
||
const outboundPayload = claim.requestPayload || normalized.requestPayload;
|
||
upstreamResponse = await (options.createCodes || productCodeClient.createProductCodes)(outboundPayload, options);
|
||
const result = normalizeUpstreamResult(upstreamResponse.data, normalized);
|
||
const latestProduct = await (options.getLatestProduct || options.getProduct || getAlibaba1688Product)(
|
||
productRecord.productId
|
||
);
|
||
const latestFingerprint = latestProduct.skuFingerprint
|
||
|| buildAlibaba1688SkuFingerprint(latestProduct.product?.skus);
|
||
if (latestFingerprint !== normalized.sourceSkuFingerprint) {
|
||
throw encodingError(
|
||
'商品在编码请求执行期间发生了 SKU 变化;远端编码已保留,但已禁止自动匹配和上架',
|
||
409,
|
||
'PRODUCT_CODE_SKU_CHANGED_DURING_ENCODING'
|
||
);
|
||
}
|
||
const encoding = await (options.complete || encodingRegistry.completeEncoding)(
|
||
productRecord.productId,
|
||
claim.token,
|
||
{
|
||
...result,
|
||
response: upstreamResponse.data,
|
||
}
|
||
);
|
||
return {
|
||
alreadyEncoded: false,
|
||
encoding,
|
||
};
|
||
} catch (error) {
|
||
try {
|
||
const conflictCodes = new Set([
|
||
'PRODUCT_CODE_RESPONSE_COUNT_MISMATCH',
|
||
'PRODUCT_CODE_RESPONSE_DUPLICATE',
|
||
'PRODUCT_CODE_RESPONSE_PRODUCT_MISMATCH',
|
||
'PRODUCT_CODE_REUSED_MAPPING_CONFLICT',
|
||
'PRODUCT_CODE_SKU_CHANGED_DURING_ENCODING',
|
||
]);
|
||
const saveFailure = conflictCodes.has(error.code)
|
||
? (options.conflict || encodingRegistry.conflictEncoding)
|
||
: (options.fail || encodingRegistry.failEncoding);
|
||
await saveFailure(
|
||
productRecord.productId,
|
||
claim.token,
|
||
error,
|
||
upstreamResponse?.data || null
|
||
);
|
||
} catch (saveError) {
|
||
console.error('保存1688商品编码失败状态失败:', saveError.message);
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function assertAlibaba1688PlanItemsEncoded(items, options = {}) {
|
||
const rows = Array.isArray(items) ? items : [];
|
||
const hydratedRows = [];
|
||
for (const row of rows) {
|
||
const sourceRow = row?.sourceRow && typeof row.sourceRow === 'object' ? row.sourceRow : {};
|
||
const declaredAlibaba1688 = normalizeText(row?.channelName, 40).toLowerCase() === '1688'
|
||
|| normalizeText(sourceRow.sourceChannel, 40).toLowerCase() === '1688'
|
||
|| normalizeText(sourceRow.source, 80) === 'alibaba_1688_products';
|
||
if (!declaredAlibaba1688) {
|
||
hydratedRows.push(row);
|
||
continue;
|
||
}
|
||
const productId = normalizeText(row?.marketItemId || row?.sourceRow?.productId, 80);
|
||
if (!productId || !/^[A-Za-z0-9_-]+$/.test(productId)) {
|
||
throw encodingError('1688 选品缺少有效商品 ID', 400, 'ALIBABA_1688_PRODUCT_ID_INVALID');
|
||
}
|
||
const product = await (options.getProduct || getAlibaba1688Product)(productId, { required: false });
|
||
if (!product) {
|
||
throw encodingError(
|
||
`未找到 1688 商品 ${productId},不能进入选品流程`,
|
||
404,
|
||
'ALIBABA_1688_PRODUCT_NOT_FOUND'
|
||
);
|
||
}
|
||
const encoding = await (options.getEncoding || encodingRegistry.getEncodingByProductId)(productId);
|
||
if (!encoding || encoding.status !== 'encoded') {
|
||
throw encodingError(
|
||
`1688 商品 ${productId} 尚未完成编码,不能进入选品流程`,
|
||
409,
|
||
'ALIBABA_1688_PRODUCT_NOT_ENCODED'
|
||
);
|
||
}
|
||
const sourceSkus = Array.isArray(product.product?.skus) ? product.product.skus : [];
|
||
const currentSkuCount = sourceSkus.length;
|
||
const calculatedFingerprint = currentSkuCount
|
||
? buildAlibaba1688SkuFingerprint(sourceSkus)
|
||
: '';
|
||
const currentFingerprint = product.skuFingerprint || calculatedFingerprint;
|
||
const expectedSourceSkuKeys = sourceSkus.map((sku, index) => sourceSkuKey(sku, index));
|
||
const productCodes = Array.isArray(encoding.productCodes) ? encoding.productCodes : [];
|
||
const skuMappings = Array.isArray(encoding.skuMappings) ? encoding.skuMappings : [];
|
||
const mappingSourceSkuKeys = skuMappings.map(mapping => normalizeText(mapping?.sourceSkuKey, 4000));
|
||
const mappingProductCodes = skuMappings.map(mapping => normalizeText(mapping?.productCode, 200));
|
||
const expectedSourceSkuKeySet = new Set(expectedSourceSkuKeys);
|
||
const productCodeSet = new Set(productCodes);
|
||
const encodingIsComplete = currentSkuCount > 0
|
||
&& Number(product.skuCount) === currentSkuCount
|
||
&& (!product.skuFingerprint || product.skuFingerprint === calculatedFingerprint)
|
||
&& Number(encoding.sourceSkuCount) === currentSkuCount
|
||
&& encoding.sourceSkuFingerprint === currentFingerprint
|
||
&& productCodes.length === currentSkuCount
|
||
&& new Set(productCodes).size === currentSkuCount
|
||
&& productCodes.every(Boolean)
|
||
&& skuMappings.length === currentSkuCount
|
||
&& new Set(mappingSourceSkuKeys).size === currentSkuCount
|
||
&& new Set(mappingProductCodes).size === currentSkuCount
|
||
&& mappingSourceSkuKeys.every(key => expectedSourceSkuKeySet.has(key))
|
||
&& mappingProductCodes.every(code => productCodeSet.has(code));
|
||
if (!encodingIsComplete) {
|
||
throw encodingError(
|
||
`1688 商品 ${productId} 的 SKU 已在编码后发生变化,请先处理编码映射冲突`,
|
||
409,
|
||
'ALIBABA_1688_PRODUCT_ENCODING_STALE'
|
||
);
|
||
}
|
||
hydratedRows.push({
|
||
...row,
|
||
channelName: '1688',
|
||
sourceRow: {
|
||
...(row.sourceRow && typeof row.sourceRow === 'object' ? row.sourceRow : {}),
|
||
...product,
|
||
encoding: {
|
||
status: encoding.status,
|
||
styleCode: encoding.styleCode,
|
||
styleCodes: encoding.styleCodes,
|
||
productCodes: encoding.productCodes,
|
||
skuMappings: encoding.skuMappings,
|
||
sourceSkuCount: encoding.sourceSkuCount,
|
||
sourceSkuFingerprint: encoding.sourceSkuFingerprint,
|
||
encodedAt: encoding.encodedAt,
|
||
},
|
||
source: 'alibaba_1688_products',
|
||
sourceChannel: '1688',
|
||
},
|
||
});
|
||
}
|
||
return hydratedRows;
|
||
}
|
||
|
||
module.exports = {
|
||
MAX_CODING_SKUS,
|
||
MAX_PRODUCT_NAME_LENGTH,
|
||
MAX_COLOR_SPEC_LENGTH,
|
||
sourceSkuKey,
|
||
normalizeCodingRequest,
|
||
resolveUpstreamSkuOrder,
|
||
normalizeUpstreamResult,
|
||
getAlibaba1688ProductCodeMeta,
|
||
encodeAlibaba1688Product,
|
||
assertAlibaba1688PlanItemsEncoded,
|
||
};
|