diff --git a/apps/backend/src/services/order/delivery-task-service.test.ts b/apps/backend/src/services/order/delivery-task-service.test.ts index 7eddf458..6368a218 100644 --- a/apps/backend/src/services/order/delivery-task-service.test.ts +++ b/apps/backend/src/services/order/delivery-task-service.test.ts @@ -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, diff --git a/apps/backend/src/services/order/delivery-task-service.ts b/apps/backend/src/services/order/delivery-task-service.ts index a19d0553..01b2f0e3 100644 --- a/apps/backend/src/services/order/delivery-task-service.ts +++ b/apps/backend/src/services/order/delivery-task-service.ts @@ -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, diff --git a/apps/backend/src/services/platforms/ninetyone/order-service.test.ts b/apps/backend/src/services/platforms/ninetyone/order-service.test.ts index 27a8cb52..9bc9b47e 100644 --- a/apps/backend/src/services/platforms/ninetyone/order-service.test.ts +++ b/apps/backend/src/services/platforms/ninetyone/order-service.test.ts @@ -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') +}) diff --git a/apps/backend/src/services/platforms/ninetyone/order-service.ts b/apps/backend/src/services/platforms/ninetyone/order-service.ts index 0a66dae9..25aef7df 100644 --- a/apps/backend/src/services/platforms/ninetyone/order-service.ts +++ b/apps/backend/src/services/platforms/ninetyone/order-service.ts @@ -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 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() diff --git a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillment.css b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillment.css index f229302f..31127b27 100644 --- a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillment.css +++ b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillment.css @@ -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; } diff --git a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue index 7b7af0b3..9d762c33 100644 --- a/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue +++ b/apps/frontend/src/views/admin/fulfillment/kuaishou-cloud/AdminKuaishouCloudFulfillmentView.vue @@ -362,14 +362,16 @@ function createRuleId() {
-

商品列表与匹配状态

+

匹配状态与覆盖规则

当前账号:{{ formatSourceLabel(selectedSource) }}

- - 刷新商品 - +
+ + 刷新商品 + +
- - - - - - - - - - - - - - - - - - -
@@ -522,6 +485,53 @@ function createRuleId() {
+ +
+
+

cloudtentacles 商品列表

+

用于核对商品名、库存和覆盖规则里的发货商品。

+
+ + + + + + + + + + + + + + + + + + + +
diff --git a/docs/履约配置/open91请求.md b/docs/履约配置/open91请求.md index 0dbb1071..0cadb837 100644 --- a/docs/履约配置/open91请求.md +++ b/docs/履约配置/open91请求.md @@ -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 用于核销。 + +这是最重要的信息