支持91商品携带店铺编号
This commit is contained in:
@@ -184,6 +184,8 @@ test('syncDeliveryTasksForOrderWithDeps creates kuaishou cloud task from cloudte
|
||||
assert.equal(context.kuaishouCloudFulfillment.binding.skuId, 28)
|
||||
assert.equal(context.kuaishouCloudFulfillment.binding.skuName, '套装-浪漫天命')
|
||||
assert.deepEqual(context.kuaishouCloudFulfillment.binding.cloudSourceKeys, ['account-a', 'account-b'])
|
||||
assert.equal(context.kuaishouCloudFulfillment.consume.shopId, 'shop-1')
|
||||
assert.equal(context.kuaishouCloudFulfillment.consume.shopName, '测试店铺')
|
||||
assert.deepEqual(context.kuaishouCloudFulfillment.deliveryItems, [
|
||||
{
|
||||
cloudSkuId: 28,
|
||||
|
||||
@@ -225,7 +225,7 @@ export async function syncDeliveryTasksForOrderWithDeps(
|
||||
},
|
||||
consume: {
|
||||
status: 'pending',
|
||||
shopId: String(kuaishouConsumeConfig.shopId || kuaishouShopConfig.shopId || '').trim(),
|
||||
shopId: String(kuaishouConsumeConfig.shopId || kuaishouShopConfig.shopId || order.shop_id || '').trim(),
|
||||
shopName: String(kuaishouConsumeConfig.shopName || kuaishouShopConfig.kshopName || order.shop_name || '').trim(),
|
||||
autoConsumeEnabled: kuaishouConsumeConfig.autoConsumeAfterDispatch === true,
|
||||
consumedAt: null,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { buildOpen91SourceEvent } from './order-service.js'
|
||||
import {
|
||||
buildOpen91SourceEvent,
|
||||
parseOpen91ProductNo,
|
||||
} from './order-service.js'
|
||||
|
||||
test('buildOpen91SourceEvent maps 91 productNo into source item identities', () => {
|
||||
const event = buildOpen91SourceEvent({
|
||||
@@ -29,3 +32,39 @@ test('buildOpen91SourceEvent maps 91 productNo into source item identities', ()
|
||||
assert.equal(event.items[0].quantity, 2)
|
||||
assert.equal(event.items[0].snapshot.productNo, 'KS-CLOUD-SKU-001')
|
||||
})
|
||||
|
||||
test('parseOpen91ProductNo splits product name and kuaishou shop id by four dashes', () => {
|
||||
assert.deepEqual(
|
||||
parseOpen91ProductNo('测试-关联商品1----3676797936'),
|
||||
{
|
||||
rawProductNo: '测试-关联商品1----3676797936',
|
||||
productName: '测试-关联商品1',
|
||||
shopId: '3676797936',
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
test('buildOpen91SourceEvent uses productNo suffix as order shop id', () => {
|
||||
const event = buildOpen91SourceEvent({
|
||||
orderNo: 'P91KS202605130002',
|
||||
productNo: '测试-关联商品1----3676797936',
|
||||
buyNum: 1,
|
||||
maxAmount: '0.01',
|
||||
timestamp: 1778670308,
|
||||
version: '1.0',
|
||||
sign: 'SIGN',
|
||||
}, {
|
||||
shopId: '91kaquan',
|
||||
shopName: '91卡券',
|
||||
})
|
||||
|
||||
assert.equal(event.shopId, '3676797936')
|
||||
assert.equal(event.items[0].externalItemId, '测试-关联商品1----3676797936')
|
||||
assert.equal(event.items[0].externalSkuCode, '测试-关联商品1----3676797936')
|
||||
assert.equal(event.items[0].externalSkuName, '测试-关联商品1')
|
||||
assert.equal(event.items[0].skuCode, '测试-关联商品1')
|
||||
assert.equal(event.items[0].skuName, '测试-关联商品1')
|
||||
assert.equal(event.items[0].snapshot.productNo, '测试-关联商品1----3676797936')
|
||||
assert.equal(event.items[0].snapshot.productName, '测试-关联商品1')
|
||||
assert.equal(event.items[0].snapshot.shopId, '3676797936')
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ import { buildOpen91OutTradeNo } from '../../open-91/response.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { parseAmountToFen } from '../../../utils/money.js'
|
||||
import { nowIso } from '../../../utils/time.js'
|
||||
import { findKuaishouEticketShopConfig } from '../kuaishou-eticket/source-config-service.js'
|
||||
import type { OrderItemRow, OrderRow } from '../../../types/repository/rows.js'
|
||||
|
||||
export const OPEN_91_PENDING_CONFIG_STATUS = 'pending_config'
|
||||
@@ -21,14 +22,19 @@ type JsonObject = Record<string, any>
|
||||
|
||||
export function buildOpen91SourceEvent(payload: JsonObject = {}, config: JsonObject = {}) {
|
||||
const normalized = normalizeOpen91CreatePayload(payload)
|
||||
const productNo = normalized.productNo
|
||||
const productInfo = parseOpen91ProductNo(normalized.productNo)
|
||||
const productNo = productInfo.rawProductNo
|
||||
const productName = productInfo.productName
|
||||
const shopId = productInfo.shopId || String(config.shopId || OPEN_91_PROVIDER).trim() || OPEN_91_PROVIDER
|
||||
const shopConfig = productInfo.shopId ? findKuaishouEticketShopConfig(productInfo.shopId) : null
|
||||
const shopName = String(shopConfig?.kshopName || config.shopName || '91卡券').trim() || '91卡券'
|
||||
const maxAmountFen = parseAmountToFen(normalized.maxAmount)
|
||||
|
||||
return {
|
||||
provider: OPEN_91_PROVIDER,
|
||||
platform: OPEN_91_PLATFORM,
|
||||
shopId: String(config.shopId || OPEN_91_PROVIDER).trim() || OPEN_91_PROVIDER,
|
||||
shopName: String(config.shopName || '91卡券').trim() || '91卡券',
|
||||
shopId,
|
||||
shopName,
|
||||
platformOrderId: normalized.orderNo,
|
||||
orderStatus: 'paid',
|
||||
payStatus: 'paid',
|
||||
@@ -48,25 +54,29 @@ export function buildOpen91SourceEvent(payload: JsonObject = {}, config: JsonObj
|
||||
itemId: productNo,
|
||||
externalItemId: productNo,
|
||||
externalSkuCode: productNo,
|
||||
externalSkuName: productNo,
|
||||
skuCode: productNo,
|
||||
skuName: productNo,
|
||||
externalSkuName: productName,
|
||||
skuCode: productName,
|
||||
skuName: productName,
|
||||
quantity: normalized.buyNum,
|
||||
spec: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
orderNo: normalized.orderNo,
|
||||
productNo,
|
||||
productName,
|
||||
shopId: productInfo.shopId,
|
||||
maxAmount: normalized.maxAmount,
|
||||
},
|
||||
snapshot: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
orderNo: normalized.orderNo,
|
||||
productNo,
|
||||
productName,
|
||||
shopId: productInfo.shopId,
|
||||
maxAmount: normalized.maxAmount,
|
||||
maxAmountFen,
|
||||
externalItemId: productNo,
|
||||
externalSkuCode: productNo,
|
||||
externalSkuName: productNo,
|
||||
externalSkuName: productName,
|
||||
callbackUrl: normalized.callbackUrl,
|
||||
},
|
||||
},
|
||||
@@ -74,6 +84,37 @@ export function buildOpen91SourceEvent(payload: JsonObject = {}, config: JsonObj
|
||||
}
|
||||
}
|
||||
|
||||
export function parseOpen91ProductNo(value: unknown) {
|
||||
const rawProductNo = String(value || '').trim()
|
||||
const delimiter = '----'
|
||||
const delimiterIndex = rawProductNo.lastIndexOf(delimiter)
|
||||
|
||||
if (delimiterIndex <= 0) {
|
||||
return {
|
||||
rawProductNo,
|
||||
productName: rawProductNo,
|
||||
shopId: '',
|
||||
}
|
||||
}
|
||||
|
||||
const productName = rawProductNo.slice(0, delimiterIndex).trim()
|
||||
const shopId = rawProductNo.slice(delimiterIndex + delimiter.length).trim()
|
||||
|
||||
if (!productName || !shopId) {
|
||||
return {
|
||||
rawProductNo,
|
||||
productName: rawProductNo,
|
||||
shopId: '',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rawProductNo,
|
||||
productName,
|
||||
shopId,
|
||||
}
|
||||
}
|
||||
|
||||
export async function upsertOpen91PendingOrder(payload: JsonObject = {}, config: JsonObject = {}) {
|
||||
const event = buildOpen91SourceEvent(payload, config)
|
||||
const now = nowIso()
|
||||
|
||||
+20
-1
@@ -141,6 +141,13 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.catalog-toolbar-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.match-checker {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 1fr) auto;
|
||||
@@ -156,6 +163,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sku-list-panel {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
min-width: 0;
|
||||
padding-top: var(--space-2);
|
||||
border-top: 1px solid var(--border-default);
|
||||
}
|
||||
|
||||
.sku-name-cell {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
@@ -186,7 +201,10 @@
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
min-width: 0;
|
||||
padding-top: var(--space-2);
|
||||
padding: var(--space-4);
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
|
||||
.override-actions {
|
||||
@@ -242,6 +260,7 @@
|
||||
|
||||
.overview-actions,
|
||||
.catalog-toolbar,
|
||||
.catalog-toolbar-actions,
|
||||
.override-actions {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
+50
-40
@@ -362,15 +362,17 @@ function createRuleId() {
|
||||
<main class="catalog-panel">
|
||||
<div class="catalog-toolbar">
|
||||
<div class="section-head">
|
||||
<h3>商品列表与匹配状态</h3>
|
||||
<h3>匹配状态与覆盖规则</h3>
|
||||
<p v-if="selectedSource">
|
||||
当前账号:{{ formatSourceLabel(selectedSource) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="catalog-toolbar-actions">
|
||||
<el-button :loading="skuLoading" :disabled="!selectedSourceKey" @click="loadSkuList()">
|
||||
刷新商品
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="skuErrorMessage"
|
||||
@@ -394,45 +396,6 @@ function createRuleId() {
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="skuLoading"
|
||||
:data="skuItems"
|
||||
border
|
||||
stripe
|
||||
class="sku-table"
|
||||
empty-text="当前账号暂无可展示商品"
|
||||
>
|
||||
<el-table-column label="cloudtentacles 商品" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<div class="sku-name-cell">
|
||||
<strong>{{ row.name || '-' }}</strong>
|
||||
<small>#{{ row.id }} · {{ row.description || row.name || '-' }}</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="91 自动匹配名" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<code class="match-name">{{ row.name || '-' }}</code>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" width="120">
|
||||
<template #default="{ row }">{{ formatCloudPrice(row.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" prop="inventory" width="120" sortable />
|
||||
<el-table-column label="发货限制" width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.buyLimitMin || 1 }} - {{ row.buyLimitMax || 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="Number(row.inventory || 0) > 0 ? 'success' : 'danger'" effect="light">
|
||||
{{ Number(row.inventory || 0) > 0 ? '可履约' : '无库存' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<section class="override-panel">
|
||||
<div class="catalog-toolbar">
|
||||
<div class="section-head">
|
||||
@@ -522,6 +485,53 @@ function createRuleId() {
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sku-list-panel">
|
||||
<div class="section-head">
|
||||
<h3>cloudtentacles 商品列表</h3>
|
||||
<p>用于核对商品名、库存和覆盖规则里的发货商品。</p>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="skuLoading"
|
||||
:data="skuItems"
|
||||
height="420"
|
||||
border
|
||||
stripe
|
||||
class="sku-table"
|
||||
empty-text="当前账号暂无可展示商品"
|
||||
>
|
||||
<el-table-column label="cloudtentacles 商品" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<div class="sku-name-cell">
|
||||
<strong>{{ row.name || '-' }}</strong>
|
||||
<small>#{{ row.id }} · {{ row.description || row.name || '-' }}</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="91 自动匹配名" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<code class="match-name">{{ row.name || '-' }}</code>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" width="120">
|
||||
<template #default="{ row }">{{ formatCloudPrice(row.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" prop="inventory" width="120" sortable />
|
||||
<el-table-column label="发货限制" width="150">
|
||||
<template #default="{ row }">
|
||||
{{ row.buyLimitMin || 1 }} - {{ row.buyLimitMax || 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="Number(row.inventory || 0) > 0 ? 'success' : 'danger'" effect="light">
|
||||
{{ Number(row.inventory || 0) > 0 ? '可履约' : '无库存' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
</main>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"orderNo": "2614700005853561",
|
||||
"version": "1.0",
|
||||
"maxAmount": "0.01",
|
||||
"productNo": "测试-关联商品1",
|
||||
"productNo": "测试-关联商品1----3676797936",
|
||||
"timestamp": 1779853484,
|
||||
"callbackUrl": ""
|
||||
},
|
||||
@@ -16,6 +16,12 @@
|
||||
}
|
||||
|
||||
|
||||
这是91 卡券发送来的查询信息, 可以获取 商品名字, 订单号 orderNo 和 最大允许金额(可以看作正常金额) maxAmount 和时间等等
|
||||
这是最重要的信息
|
||||
这是91 卡券发送来的查询信息, 可以获取 商品名字, 订单号 orderNo 和 最大允许金额(可以看作正常金额) maxAmount 和时间等等。
|
||||
|
||||
`productNo` 可以使用四个 `----` 携带快手店铺编号,例如:
|
||||
|
||||
`测试-关联商品1----3676797936`
|
||||
|
||||
系统会把 `测试-关联商品1` 当作商品名字匹配 cloudtentacles,把 `3676797936` 当作快手小店 shopId 用于核销。
|
||||
|
||||
这是最重要的信息
|
||||
|
||||
Reference in New Issue
Block a user