feat(pdd-sync): 默认拉取已发货订单并支持配置单次拉单上限与抓取状态
This commit is contained in:
@@ -159,8 +159,15 @@ router.post(
|
|||||||
'/pdd/sync',
|
'/pdd/sync',
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
async (req) => {
|
async (req) => {
|
||||||
const body = (req.body || {}) as { shopId?: string; pageSize?: number }
|
const body = (req.body || {}) as {
|
||||||
|
shopId?: string
|
||||||
|
pageSize?: number
|
||||||
|
orderType?: number
|
||||||
|
}
|
||||||
const shopId = String(body.shopId || '').trim()
|
const shopId = String(body.shopId || '').trim()
|
||||||
|
const config = getPddSourcesConfig()
|
||||||
|
const pageSize = Math.min(100, Math.max(1, Number(body.pageSize || config.pageSize || 50)))
|
||||||
|
const orderType = Number(body.orderType ?? config.orderType ?? 2)
|
||||||
|
|
||||||
if (shopId) {
|
if (shopId) {
|
||||||
const store = getPddStoreById(shopId)
|
const store = getPddStoreById(shopId)
|
||||||
@@ -170,10 +177,10 @@ router.post(
|
|||||||
errorCode: 'pdd_store_not_found',
|
errorCode: 'pdd_store_not_found',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return syncStoreOrders(store, { pageSize: body.pageSize || 10 })
|
return syncStoreOrders(store, { pageSize, orderType })
|
||||||
}
|
}
|
||||||
|
|
||||||
return syncAllPddStores({ pageSize: body.pageSize || 10 })
|
return syncAllPddStores({ pageSize, orderType })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
successMessage: '拼多多同步已触发',
|
successMessage: '拼多多同步已触发',
|
||||||
|
|||||||
@@ -124,13 +124,13 @@ export class PddClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Query recent orders page from PDD MMS.
|
* Query recent orders page from PDD MMS.
|
||||||
* 单页查询拼多多商户近期订单(默认查询待发货订单)。
|
* 单页查询拼多多商户近期订单(默认查询已发货订单)。
|
||||||
*/
|
*/
|
||||||
public async queryRecentOrders(options: QueryOrdersOptions = {}): Promise<PddOrderQueryResult> {
|
public async queryRecentOrders(options: QueryOrdersOptions = {}): Promise<PddOrderQueryResult> {
|
||||||
const {
|
const {
|
||||||
pageNumber = 1,
|
pageNumber = 1,
|
||||||
pageSize = 20,
|
pageSize = 50,
|
||||||
orderType = 1, // 1: 待发货 (Pending shipment)
|
orderType = 2, // 2: 已发货 (Shipped, default)
|
||||||
afterSaleStatus = 0,
|
afterSaleStatus = 0,
|
||||||
groupType = 0,
|
groupType = 0,
|
||||||
remarkStatus = -1,
|
remarkStatus = -1,
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export interface PddStoreConfigItem {
|
|||||||
export interface PddSourcesConfig {
|
export interface PddSourcesConfig {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
pollIntervalSeconds: number
|
pollIntervalSeconds: number
|
||||||
|
orderType: number // 0: 全部, 1: 待发货, 2: 已发货 (默认 2)
|
||||||
|
pageSize: number // 单次拉取数量 (默认 50)
|
||||||
stores: PddStoreConfigItem[]
|
stores: PddStoreConfigItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,9 +169,21 @@ export function normalizePddSourcesConfig(rawValue: unknown): PddSourcesConfig {
|
|||||||
? Math.min(3600, Math.max(10, rawInterval))
|
? Math.min(3600, Math.max(10, rawInterval))
|
||||||
: 20
|
: 20
|
||||||
|
|
||||||
|
const rawOrderType = Number(source.orderType)
|
||||||
|
const orderType = Number.isInteger(rawOrderType) && [0, 1, 2].includes(rawOrderType)
|
||||||
|
? rawOrderType
|
||||||
|
: 2
|
||||||
|
|
||||||
|
const rawPageSize = Number(source.pageSize)
|
||||||
|
const pageSize = Number.isInteger(rawPageSize)
|
||||||
|
? Math.min(100, Math.max(10, rawPageSize))
|
||||||
|
: 50
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: source.enabled !== false,
|
enabled: source.enabled !== false,
|
||||||
pollIntervalSeconds,
|
pollIntervalSeconds,
|
||||||
|
orderType,
|
||||||
|
pageSize,
|
||||||
stores,
|
stores,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,6 +192,8 @@ function createDefaultPddSourcesConfig(): PddSourcesConfig {
|
|||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
pollIntervalSeconds: 20,
|
pollIntervalSeconds: 20,
|
||||||
|
orderType: 2,
|
||||||
|
pageSize: 50,
|
||||||
stores: [],
|
stores: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { logError, logInfo, logWarn } from '../../../utils/logger.js'
|
|||||||
export interface SyncStoreOrdersOptions {
|
export interface SyncStoreOrdersOptions {
|
||||||
pageNumber?: number | undefined
|
pageNumber?: number | undefined
|
||||||
pageSize?: number | undefined
|
pageSize?: number | undefined
|
||||||
orderType?: number | undefined // 1: 待发货 (默认)
|
orderType?: number | undefined // 2: 已发货 (默认), 1: 待发货, 0: 全部
|
||||||
startTime?: number | undefined
|
startTime?: number | undefined
|
||||||
endTime?: number | undefined
|
endTime?: number | undefined
|
||||||
}
|
}
|
||||||
@@ -71,9 +71,10 @@ export async function syncStoreOrders(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = new PddClient(store)
|
const client = new PddClient(store)
|
||||||
const pageSize = Math.min(50, Math.max(1, Number(options.pageSize || 10)))
|
const sourcesConfig = getPddSourcesConfig()
|
||||||
|
const pageSize = Math.min(100, Math.max(1, Number(options.pageSize || sourcesConfig.pageSize || 50)))
|
||||||
const pageNumber = Math.max(1, Number(options.pageNumber || 1))
|
const pageNumber = Math.max(1, Number(options.pageNumber || 1))
|
||||||
const orderType = options.orderType ?? 1 // 1: 待发货 (Pending shipment)
|
const orderType = options.orderType ?? sourcesConfig.orderType ?? 2 // 2: 已发货 (Shipped, default)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const queryOptions: QueryOrdersOptions = {
|
const queryOptions: QueryOrdersOptions = {
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
import type { JsonObject } from '../../types/json.js'
|
import type { JsonObject } from '../../types/json.js'
|
||||||
import { logInfo } from '../../utils/logger.js'
|
import { logInfo } from '../../utils/logger.js'
|
||||||
import { syncAllPddStores } from '../platforms/pdd/pdd-sync-service.js'
|
import { syncAllPddStores } from '../platforms/pdd/pdd-sync-service.js'
|
||||||
|
import { getPddSourcesConfig } from '../platforms/pdd/pdd-source-config-service.js'
|
||||||
|
|
||||||
export async function runPddOrderSyncJob(job: JsonObject) {
|
export async function runPddOrderSyncJob(job: JsonObject) {
|
||||||
const config =
|
const config =
|
||||||
job.config && typeof job.config === 'object' && !Array.isArray(job.config)
|
job.config && typeof job.config === 'object' && !Array.isArray(job.config)
|
||||||
? (job.config as JsonObject)
|
? (job.config as JsonObject)
|
||||||
: {}
|
: {}
|
||||||
const pageSize = Math.min(50, Math.max(1, Number(config.pageSize || 10)))
|
const sourcesConfig = getPddSourcesConfig()
|
||||||
|
const pageSize = Math.min(100, Math.max(1, Number(config.pageSize || sourcesConfig.pageSize || 50)))
|
||||||
|
const orderType = Number(config.orderType ?? sourcesConfig.orderType ?? 2)
|
||||||
|
|
||||||
const syncResult = await syncAllPddStores({ pageSize })
|
const syncResult = await syncAllPddStores({ pageSize, orderType })
|
||||||
|
|
||||||
if (!syncResult.enabled) {
|
if (!syncResult.enabled) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Row,
|
Row,
|
||||||
|
Select,
|
||||||
Space,
|
Space,
|
||||||
Spin,
|
Spin,
|
||||||
Statistic,
|
Statistic,
|
||||||
@@ -76,6 +77,8 @@ export function PddPlatformPanel() {
|
|||||||
const [config, setConfig] = useState<AdminPddConfig>({
|
const [config, setConfig] = useState<AdminPddConfig>({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
pollIntervalSeconds: 20,
|
pollIntervalSeconds: 20,
|
||||||
|
orderType: 2,
|
||||||
|
pageSize: 50,
|
||||||
stores: [],
|
stores: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -144,13 +147,18 @@ export function PddPlatformPanel() {
|
|||||||
async function handleSyncAll() {
|
async function handleSyncAll() {
|
||||||
setSyncingAll(true)
|
setSyncingAll(true)
|
||||||
try {
|
try {
|
||||||
const response = await triggerAdminPddSync()
|
const response = await triggerAdminPddSync({
|
||||||
|
pageSize: config.pageSize || 50,
|
||||||
|
orderType: config.orderType ?? 2,
|
||||||
|
})
|
||||||
const data = response?.data as AdminPddSyncAllResponse | undefined
|
const data = response?.data as AdminPddSyncAllResponse | undefined
|
||||||
if (data && Array.isArray(data.results)) {
|
if (data && Array.isArray(data.results)) {
|
||||||
const totalFetched = data.results.reduce((acc, r) => acc + (r.fetchedCount || 0), 0)
|
const totalFetched = data.results.reduce((acc, r) => acc + (r.fetchedCount || 0), 0)
|
||||||
const totalUpserted = data.results.reduce((acc, r) => acc + (r.upsertedCount || 0), 0)
|
const totalUpserted = data.results.reduce((acc, r) => acc + (r.upsertedCount || 0), 0)
|
||||||
|
const statusLabel =
|
||||||
|
config.orderType === 1 ? '待发货' : config.orderType === 0 ? '全部' : '已发货'
|
||||||
showSuccess(
|
showSuccess(
|
||||||
`全量店铺同步完成:共抓取 ${totalFetched} 笔,入库/更新 ${totalUpserted} 笔待发货订单`,
|
`全量店铺【${statusLabel}】同步完成:共抓取 ${totalFetched} 笔,入库/更新 ${totalUpserted} 笔订单`,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
showSuccess('全量店铺同步完成')
|
showSuccess('全量店铺同步完成')
|
||||||
@@ -169,7 +177,11 @@ export function PddPlatformPanel() {
|
|||||||
async function handleSyncSingleStore(store: AdminPddStoreItem) {
|
async function handleSyncSingleStore(store: AdminPddStoreItem) {
|
||||||
setSyncingShopId(store.shopId)
|
setSyncingShopId(store.shopId)
|
||||||
try {
|
try {
|
||||||
const response = await triggerAdminPddSync({ shopId: store.shopId })
|
const response = await triggerAdminPddSync({
|
||||||
|
shopId: store.shopId,
|
||||||
|
pageSize: config.pageSize || 50,
|
||||||
|
orderType: config.orderType ?? 2,
|
||||||
|
})
|
||||||
const data = response?.data as AdminPddSyncResult | undefined
|
const data = response?.data as AdminPddSyncResult | undefined
|
||||||
if (data) {
|
if (data) {
|
||||||
showSuccess(
|
showSuccess(
|
||||||
@@ -206,7 +218,7 @@ export function PddPlatformPanel() {
|
|||||||
}))
|
}))
|
||||||
if (result.valid) {
|
if (result.valid) {
|
||||||
showSuccess(
|
showSuccess(
|
||||||
`【${store.shopName}】Cookie 校验通过,在线待发货订单数: ${result.totalOrders ?? 0}`,
|
`【${store.shopName}】Cookie 校验通过,在售/历史订单总数: ${result.totalOrders ?? 0}`,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
showError(`【${store.shopName}】Cookie 校验未通过: ${result.message}`)
|
showError(`【${store.shopName}】Cookie 校验未通过: ${result.message}`)
|
||||||
@@ -458,7 +470,7 @@ export function PddPlatformPanel() {
|
|||||||
return (
|
return (
|
||||||
<Space direction="vertical" size={2}>
|
<Space direction="vertical" size={2}>
|
||||||
<Tag icon={<CheckCircleOutlined />} color="success">
|
<Tag icon={<CheckCircleOutlined />} color="success">
|
||||||
有效 (待发货: {testRes.totalOrders ?? 0})
|
有效 (在售/历史: {testRes.totalOrders ?? 0})
|
||||||
</Tag>
|
</Tag>
|
||||||
{testRes.testedAt && (
|
{testRes.testedAt && (
|
||||||
<Text type="secondary" style={{ fontSize: '11px' }}>
|
<Text type="secondary" style={{ fontSize: '11px' }}>
|
||||||
@@ -501,7 +513,9 @@ export function PddPlatformPanel() {
|
|||||||
测试CK
|
测试CK
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="立即从拼多多 MMS 抓取待发货订单并同步至总平台">
|
<Tooltip
|
||||||
|
title={`立即从拼多多 MMS 抓取【${config.orderType === 1 ? '待发货' : config.orderType === 0 ? '全部' : '已发货'}】订单并同步至总平台`}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
@@ -541,6 +555,9 @@ export function PddPlatformPanel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const orderTypeDesc =
|
||||||
|
config.orderType === 1 ? '待发货' : config.orderType === 0 ? '全部订单' : '已发货'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="platform-panel-stack">
|
<section className="platform-panel-stack">
|
||||||
{/* Metrics Dashboard / 状态指标概览卡片 */}
|
{/* Metrics Dashboard / 状态指标概览卡片 */}
|
||||||
@@ -636,7 +653,7 @@ export function PddPlatformPanel() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={<span style={{ color: '#64748b' }}>错峰轮询频次</span>}
|
title={<span style={{ color: '#64748b' }}>调度频次与拉单范围</span>}
|
||||||
value={config.pollIntervalSeconds}
|
value={config.pollIntervalSeconds}
|
||||||
suffix={<span style={{ fontSize: 14, color: '#8c8c8c' }}>秒 / 轮</span>}
|
suffix={<span style={{ fontSize: 14, color: '#8c8c8c' }}>秒 / 轮</span>}
|
||||||
valueStyle={{ color: '#722ed1', fontWeight: 600, fontSize: 20 }}
|
valueStyle={{ color: '#722ed1', fontWeight: 600, fontSize: 20 }}
|
||||||
@@ -651,8 +668,11 @@ export function PddPlatformPanel() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
多店铺平滑均摊时间片
|
单次拉取 {config.pageSize || 50} 单
|
||||||
</Text>
|
</Text>
|
||||||
|
<Tag color="purple" style={{ margin: 0, fontSize: 11 }}>
|
||||||
|
{orderTypeDesc}
|
||||||
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -715,7 +735,7 @@ export function PddPlatformPanel() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Row gutter={[24, 16]} align="middle">
|
<Row gutter={[24, 16]} align="middle">
|
||||||
<Col xs={24} sm={8}>
|
<Col xs={24} sm={12} md={6}>
|
||||||
<Space direction="vertical" size={4}>
|
<Space direction="vertical" size={4}>
|
||||||
<Text strong>自动拉单调度状态</Text>
|
<Text strong>自动拉单调度状态</Text>
|
||||||
<Space align="center">
|
<Space align="center">
|
||||||
@@ -730,29 +750,73 @@ export function PddPlatformPanel() {
|
|||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={24} sm={16}>
|
<Col xs={24} sm={12} md={6}>
|
||||||
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
|
<Text strong>抓取订单状态</Text>
|
||||||
|
<Select
|
||||||
|
value={config.orderType ?? 2}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onChange={(val) => setConfig((prev) => ({ ...prev, orderType: Number(val) }))}
|
||||||
|
options={[
|
||||||
|
{ value: 2, label: '已发货 (默认推荐)' },
|
||||||
|
{ value: 1, label: '待发货' },
|
||||||
|
{ value: 0, label: '全部订单' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs={24} sm={12} md={6}>
|
||||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
<Text strong>错峰轮询间隔</Text>
|
<Text strong>错峰轮询间隔</Text>
|
||||||
<Space align="center" wrap>
|
<InputNumber
|
||||||
<InputNumber
|
min={10}
|
||||||
min={10}
|
max={3600}
|
||||||
max={3600}
|
style={{ width: '100%' }}
|
||||||
addonAfter="秒"
|
addonAfter="秒"
|
||||||
value={config.pollIntervalSeconds}
|
value={config.pollIntervalSeconds}
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
setConfig((prev) => ({
|
setConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pollIntervalSeconds: Number(val || 20),
|
pollIntervalSeconds: Number(val || 20),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
</Space>
|
||||||
(建议 15~30 秒。多店铺自动平滑错峰拉取待发货订单,保证准实时入库且防止高频风控)
|
</Col>
|
||||||
</Text>
|
|
||||||
</Space>
|
<Col xs={24} sm={12} md={6}>
|
||||||
|
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||||
|
<Text strong>单次拉单上限 (pageSize)</Text>
|
||||||
|
<InputNumber
|
||||||
|
min={10}
|
||||||
|
max={100}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
addonAfter="单/次"
|
||||||
|
value={config.pageSize ?? 50}
|
||||||
|
onChange={(val) =>
|
||||||
|
setConfig((prev) => ({
|
||||||
|
...prev,
|
||||||
|
pageSize: Number(val || 50),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
style={{ marginTop: 14 }}
|
||||||
|
message={
|
||||||
|
<span>
|
||||||
|
当前拉单规则:默认抓取<strong>【{orderTypeDesc}】</strong>订单,单次拉取最新
|
||||||
|
<strong>【{config.pageSize || 50}】</strong>单并错峰均摊。
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
description="当拼多多买家下单并完成虚拟发货(或服务市场工具发货为已发货)后,系统将自动拉取入库并派发至打手大厅;您也可以随时点击右侧【立即拉单】即时手动触发增量抓取。"
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Stores CRUD Table Card / 拼多多店铺管理表格卡片 */}
|
{/* Stores CRUD Table Card / 拼多多店铺管理表格卡片 */}
|
||||||
@@ -911,7 +975,7 @@ export function PddPlatformPanel() {
|
|||||||
<div>{modalTestResult.message}</div>
|
<div>{modalTestResult.message}</div>
|
||||||
{modalTestResult.totalOrders !== undefined && (
|
{modalTestResult.totalOrders !== undefined && (
|
||||||
<div style={{ marginTop: 4, fontWeight: 500 }}>
|
<div style={{ marginTop: 4, fontWeight: 500 }}>
|
||||||
当前在线待发货订单数: {modalTestResult.totalOrders} 笔
|
当前在线在售/历史订单总数: {modalTestResult.totalOrders} 笔
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ export function testAdminPddCookie(payload: {
|
|||||||
return apiPost<AdminPddTestCookieResult>('/api/v1/admin/platform-config/pdd/test-cookie', payload)
|
return apiPost<AdminPddTestCookieResult>('/api/v1/admin/platform-config/pdd/test-cookie', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function triggerAdminPddSync(payload: { shopId?: string; pageSize?: number } = {}) {
|
export function triggerAdminPddSync(
|
||||||
|
payload: { shopId?: string; pageSize?: number; orderType?: number } = {},
|
||||||
|
) {
|
||||||
return apiPost<AdminPddSyncAllResponse | AdminPddSyncResult>(
|
return apiPost<AdminPddSyncAllResponse | AdminPddSyncResult>(
|
||||||
'/api/v1/admin/platform-config/pdd/sync',
|
'/api/v1/admin/platform-config/pdd/sync',
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ export interface AdminPddStoreItem {
|
|||||||
export interface AdminPddConfig {
|
export interface AdminPddConfig {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
pollIntervalSeconds: number
|
pollIntervalSeconds: number
|
||||||
|
orderType?: number // 0: 全部, 1: 待发货, 2: 已发货 (默认 2)
|
||||||
|
pageSize?: number // 单次拉取单数 (默认 50)
|
||||||
stores: AdminPddStoreItem[]
|
stores: AdminPddStoreItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user