Files
ttx-project-doc/superpowers/plans/2026-06-04-shopee-outbound-api-wes-mapping.md
T

42 KiB
Raw Blame History

Shopee Outbound API WES Mapping Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Implement Shopee Automation outbound interfaces plus the user-confirmed 4.1 rack-transfer inventory sync interface, with edi-shopee as the external Shopee-facing entry and WES ShopeeInboundApiHandler as the internal business handler.

Architecture: Shopee calls C:\work\gitlab\shopee\edi-shopee; edi-shopee owns Shopee URL paths, JWT validation/generation, protocol response shape, EDI logging, and forwarding to WES. WES business handling stays in wes-loghub/wms-api/src/main/groovy/com/ittx/wms/api/service/platform/ShopeeInboundApiHandler.groovy, reached by edi-shopee through the existing WES sync API with api=shopee....

Tech Stack: Groovy, Spring Boot, CBT ResponseMessage, WES SyncApiController/ApiService platform dispatch, MySQL/Flyway, edi-shopee HTTP/JWT integration.


Scope

This plan covers the outbound module and the explicitly added 4.1 rack-transfer inventory interface, based on:

  • docs/Shopee_Automation_Vendor_接入协议手册_出库模块映射WES字段_V1.2.md
  • wes-loghub/wms-api/src/main/groovy/com/ittx/wms/api/service/platform/ShopeeInboundApiHandler.groovy
  • C:\work\gitlab\shopee\edi-shopee

Do not implement inbound, basic-data, or the rest of inventory module in this plan. The only inventory interface included is 4.1 库内移库同步占用库存给Vendor接口.

Current State

  • SyncApiController already receives standard platform calls and delegates through ApiService; this remains the internal WES target called by edi-shopee.
  • ApiService already delegates ess.* and shopee.* APIs to PlatformInboundApiService.
  • ShopeeInboundApiHandler exists but has no outbound cases implemented.
  • ShipmentHeader already contains key Shopee fields: cutOffTime, purchaseTime, orderTime, shopeeWave, waveRule, kickOutWave, ticketType, pickType, orderStructure, groupKey.
  • ShopeeWaveService has low-water scheduling and persistence, but the actual upstream wave-number request is still a placeholder.
  • WaveRuleMatchService has Shopee wave binding placeholders and local binding/rollback logic.
  • wms-transfer already contains transfer order services; use those existing transfer entities/services for rack-transfer data extraction before adding any new persistence.
  • edi-shopee already owns outbound HTTP/token patterns and is the Shopee-facing entry for both inbound calls from Shopee and outbound calls to Shopee.

File Structure

wes-loghub

  • Modify: wms-api/src/main/groovy/com/ittx/wms/api/config/domain/PlatformApiConstants.groovy
    • Add Shopee outbound API names.
  • Modify: wms-api/src/main/groovy/com/ittx/wms/api/service/platform/ShopeeInboundApiHandler.groovy
    • Add switch cases and call focused private handler methods.
  • Create: wms-api/src/main/groovy/com/ittx/wms/api/domain/platform/shopee/ShopeeOutboundApiCmds.groovy
    • Store request DTOs for first-phase inbound-to-WES outbound APIs.
  • Create: wms-api/src/main/groovy/com/ittx/wms/api/domain/platform/shopee/ShopeeOutboundApiResults.groovy
    • Store response DTOs when a list result is required.
  • Create: wms-api/src/main/groovy/com/ittx/wms/api/service/platform/ShopeeOutboundShipmentMappingService.groovy
    • Convert Shopee request fields into existing WES entities and dictionaries.
  • Modify: wms-wave/src/main/groovy/com/ittx/wms/wave/service/hairo/ShopeeWaveService.groovy
    • Replace requestShopeeWaveNumbers placeholder with a call to edi-shopee.
  • Modify: wms-wave/src/main/groovy/com/ittx/wms/wave/service/hairo/WaveRuleMatchService.groovy
    • Replace Shopee wave bind placeholder with a call to edi-shopee and parse failed shipment IDs.
  • Modify: wms-transfer/src/main/groovy/com/ittx/wms/transfer/service/TransferOrderHeaderService.groovy or the existing transfer-order creation/release hook that creates rack-transfer reserved stock.
    • Trigger 4.1 WMS-to-Vendor rack-transfer sync after WES has created and reserved the transfer order.
  • Create only if no existing transfer service method is suitable: wms-api/src/main/groovy/com/ittx/wms/api/service/platform/ShopeeRackTransferMappingService.groovy
    • Build the 4.1 request body from existing TransferOrderHeader/detail data.
  • Modify: src/main/resources/application.yml and profile-specific config only if no existing system-parameter path is available.
    • Add internal edi-shopee endpoint only if it cannot be represented by existing API/customer config.

edi-shopee

  • Create or modify: src/main/groovy/com/ittx/edi/erp/service/ShopeeJwtService.groovy
    • Generate and verify HS256 JWT per Shopee protocol.
  • Create or modify: src/main/groovy/com/ittx/edi/erp/service/ShopeeAutomationVendorService.groovy
    • Validate Shopee inbound requests, forward to WES, build Shopee outbound URLs, set headers, call Shopee Automation Vendor APIs, normalize responses.
  • Create or modify: src/main/groovy/com/ittx/edi/erp/controller/ShopeeAutomationController.groovy
    • External endpoints called by Shopee plus internal endpoints consumed by wes-loghub for wave-number request and wave binding.
  • Modify: src/main/resources/application-dev.yml and src/main/resources/application-k8s.yml.
    • Add Shopee account, secret, host template, environment, country id, and timeout configuration.

Interface Inventory

Protocol alignment from docs/Shopee_Automation_Vendor_接入协议手册_出库模块映射WES字段_V1.2.md:

  • Outbound module title count: 45 entries under 3.1 to 3.4.
  • Outbound module URL count: 35 concrete, unique /api/v2/automation/.../outbound/... APIs.
  • Reused/deprecated/explanation count: 10 title entries.
  • Added inventory URL count: 1 user-confirmed 4.1 rack-transfer API.
  • Total implemented protocol API inventory in this plan: 36 concrete APIs.
  • Other inventory/rack-transfer URLs after 4.1 are not part of this implementation plan.

Concrete Outbound APIs

No. Section Direction Shopee/edi-shopee path WES api constant Implementation note
1 3.1.1 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/create_sub_picking_task shopee.outbound.salesorder.createSubPickingTask WES 下发销售 SubPickingTask
2 3.1.2 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/cancel_sub_picking_task shopee.outbound.salesorder.cancelSubPickingTask WES 取消销售 SubPickingTask
3 3.1.3 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/update_task_flag shopee.outbound.salesorder.updateTaskFlag 更新 urgent flag
4 3.1.5 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/start_picking_task shopee.outbound.salesorder.startPickingTask Vendor 开始拣货任务
5 3.1.6 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/bind_picking_task shopee.outbound.salesorder.bindPickingTask Vendor 实绑拣货任务
6 3.1.7 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/sync_picking_detail shopee.outbound.salesorder.syncPickingDetail 增量拣货明细
7 3.1.8 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/change_picking_device shopee.outbound.salesorder.changePickingDevice SubPickingTask 换箱,不产生新任务
8 3.1.9 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/complete_picking_task shopee.outbound.salesorder.completePickingTask 完成拣货任务
9 3.1.10 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/search_picking_process_guide shopee.outbound.salesorder.searchPickingProcessGuide 查询流程指引
10 3.1.11 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/query_picking_detail shopee.outbound.salesorder.queryPickingDetail 查询 Vendor 拣货任务列表
11 3.2.1 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/create_order shopee.outbound.salesorder.createOrder WMS 给 Vendor 下销售订单
12 3.2.2 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/cancel_order shopee.outbound.salesorder.cancelOrderToVendor WMS 向 Vendor 撤回订单,协议标题为“捞单”
13 3.2.3 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/cancel_order shopee.outbound.salesorder.vendorCancelOrder Vendor 向 WMS 撤单
14 3.2.4 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/lock_unlock_order shopee.outbound.salesorder.lockUnlockOrder WMS 下发锁单/解锁
15 3.2.5 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/cache_task_number shopee.outbound.salesorder.cacheTaskNumber 获取号段缓存
16 3.2.7 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/create_picking_task shopee.outbound.salesorder.createPickingTask 创建 picking task
17 3.2.8 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/append_order shopee.outbound.salesorder.appendOrder 追加订单明细
18 3.2.9 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/change_device shopee.outbound.salesorder.changeDevice Flow Pick 换箱,产生新任务
19 3.2.10 Vendor -> WMS /api/v2/automation/toshopee/outbound/salesorder/vendor_complete_task shopee.outbound.salesorder.vendorCompleteTask Vendor 完成 task
20 3.2.11 WMS -> Vendor /api/v2/automation/tovendor/outbound/salesorder/search_lock_order_list shopee.outbound.salesorder.searchLockOrderList 查询冻结状态订单列表
21 3.3.1 WMS -> Vendor /api/v2/automation/tovendor/outbound/rtsmto/create_require shopee.outbound.rtsmto.createRequire RTS/MTO 需求下发
22 3.3.2 WMS -> Vendor /api/v2/automation/tovendor/outbound/rtsmto/cancel_require shopee.outbound.rtsmto.cancelRequire RTS/MTO 需求取消
23 3.3.3 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/sync_occupied_require shopee.outbound.rtsmto.syncOccupiedRequire 同步占用信息
24 3.3.6 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/create_picking_task shopee.outbound.rtsmto.createPickingTask RTS/MTO 创建 picking task
25 3.3.7 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/bind_picking_task shopee.outbound.rtsmto.bindPickingTask RTS/MTO 实绑任务
26 3.3.8 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/append_order shopee.outbound.rtsmto.appendOrder RTS/MTO 追加
27 3.3.9 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/sync_picking_detail shopee.outbound.rtsmto.syncPickingDetail RTS/MTO 增量拣货明细
28 3.3.10 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/complete_task shopee.outbound.rtsmto.completeTask RTS/MTO 完成 task
29 3.3.11 Vendor -> WMS /api/v2/automation/toshopee/outbound/rtsmto/change_picking_device shopee.outbound.rtsmto.changePickingDevice RTS/MTO 换箱;协议原文 URL 有多余空格,实施时按无空格路径
30 3.4.1 WMS -> Vendor /api/v2/automation/tovendor/outbound/mto/create_picking_task shopee.outbound.mto.createPickingTask MTO 拣货任务下发
31 3.4.2 WMS -> Vendor /api/v2/automation/tovendor/outbound/mto/cancel_picking_task shopee.outbound.mto.cancelPickingTask MTO 取消拣货任务
32 3.4.5 Vendor -> WMS /api/v2/automation/toshopee/outbound/mto/start_picking_task shopee.outbound.mto.startPickingTask MTO 开始拣货
33 3.4.6 Vendor -> WMS /api/v2/automation/toshopee/outbound/mto/bind_picking_task shopee.outbound.mto.bindPickingTask MTO 真实绑定
34 3.4.7 Vendor -> WMS /api/v2/automation/toshopee/outbound/mto/sync_picking_detail shopee.outbound.mto.syncPickingDetail MTO 增量拣货明细
35 3.4.8 Vendor -> WMS /api/v2/automation/toshopee/outbound/mto/complete_picking_task shopee.outbound.mto.completePickingTask MTO 完成拣货任务

Added Inventory API

No. Section Direction Shopee/edi-shopee path WES api constant Implementation note
36 4.1 WMS -> Vendor /api/v2/automation/tovendor/inventory/racktransfer/create_rt_order shopee.inventory.racktransfer.createRtOrder WES 给 Vendor 同步移库占用库存;只纳入 4.1,不扩展 4.2-4.7

Reused or Deprecated Title Entries

Section Title Handling
3.1.4 Vendor绑定/解绑device(空占)【复用basic接口】废弃 不新增出库接口
3.2.6 Vendor绑定解绑设备【复用basic接口】废弃 不新增出库接口
3.2.12 复用接口列表 说明项,不新增接口
3.3.4 Vendor绑定/解绑device【废弃】 不新增出库接口
3.3.5 Vendor向wms获取号段缓存【复用】 复用 3.2.5
3.3.12 查询Vendor拣货任务列表【复用】 复用 3.1.11
3.3.13 更新需求的urgent_flag【复用】 复用 3.1.3
3.4.3 WMS向Vendor更新task的urgent flag【复用3.1.3】 复用 3.1.3
3.4.4 Vendor绑定/解绑device【复用basic接口】废弃 不新增出库接口
3.4.9 查询Vendor拣货任务列表【复用】 复用 3.1.11

Interface Names

Add constants for all 35 concrete outbound APIs above plus the 4.1 rack-transfer API. The first implementation slice may start with the high-risk sales, wave-number, and rack-transfer sync interfaces, but the constant inventory must stay aligned to the table and to Task 1 Step 1.

Two cancel_order protocol paths exist with different directions, so they use separate WES API identifiers: cancelOrderToVendor for tovendor and vendorCancelOrder for toshopee.

Use these names as WES internal API identifiers. Shopee does not call WES directly; Shopee calls edi-shopee, and edi-shopee forwards to WES SyncApiController with /api/wms/api/sync/in?api=<constant>&format=json.


Task 1: Add Shopee Constants and DTOs

Files:

  • Modify: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\config\domain\PlatformApiConstants.groovy

  • Create: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\domain\platform\shopee\ShopeeOutboundApiCmds.groovy

  • Create: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\domain\platform\shopee\ShopeeOutboundApiResults.groovy

  • Step 1: Add constants

Add the full outbound constant set and the 4.1 rack-transfer constant under PlatformApiConstants.ShopeeInterfaceType. The first slice may only implement a subset in ShopeeInboundApiHandler, but unimplemented constants must return ApiMessages.MSG_INTF_0005 until their task lands:

static final String SALES_CREATE_SUB_PICKING_TASK = 'shopee.outbound.salesorder.createSubPickingTask'
static final String SALES_CANCEL_SUB_PICKING_TASK = 'shopee.outbound.salesorder.cancelSubPickingTask'
static final String SALES_UPDATE_TASK_FLAG = 'shopee.outbound.salesorder.updateTaskFlag'
static final String SALES_ORDER_CREATE = 'shopee.outbound.salesorder.createOrder'
static final String SALES_CANCEL_ORDER_TO_VENDOR = 'shopee.outbound.salesorder.cancelOrderToVendor'
static final String SALES_SEARCH_LOCK_ORDER_LIST = 'shopee.outbound.salesorder.searchLockOrderList'
static final String SALES_VENDOR_CANCEL_ORDER = 'shopee.outbound.salesorder.vendorCancelOrder'
static final String SALES_LOCK_UNLOCK_ORDER = 'shopee.outbound.salesorder.lockUnlockOrder'
static final String SALES_CACHE_TASK_NUMBER = 'shopee.outbound.salesorder.cacheTaskNumber'
static final String SALES_START_PICKING_TASK = 'shopee.outbound.salesorder.startPickingTask'
static final String SALES_BIND_PICKING_TASK = 'shopee.outbound.salesorder.bindPickingTask'
static final String SALES_SYNC_PICKING_DETAIL = 'shopee.outbound.salesorder.syncPickingDetail'
static final String SALES_CHANGE_PICKING_DEVICE = 'shopee.outbound.salesorder.changePickingDevice'
static final String SALES_COMPLETE_PICKING_TASK = 'shopee.outbound.salesorder.completePickingTask'
static final String SALES_SEARCH_PICKING_PROCESS_GUIDE = 'shopee.outbound.salesorder.searchPickingProcessGuide'
static final String SALES_QUERY_PICKING_DETAIL = 'shopee.outbound.salesorder.queryPickingDetail'
static final String SALES_CREATE_PICKING_TASK = 'shopee.outbound.salesorder.createPickingTask'
static final String SALES_APPEND_ORDER = 'shopee.outbound.salesorder.appendOrder'
static final String SALES_CHANGE_DEVICE = 'shopee.outbound.salesorder.changeDevice'
static final String SALES_VENDOR_COMPLETE_TASK = 'shopee.outbound.salesorder.vendorCompleteTask'
static final String RTSMTO_CREATE_REQUIRE = 'shopee.outbound.rtsmto.createRequire'
static final String RTSMTO_CANCEL_REQUIRE = 'shopee.outbound.rtsmto.cancelRequire'
static final String RTSMTO_SYNC_OCCUPIED_REQUIRE = 'shopee.outbound.rtsmto.syncOccupiedRequire'
static final String RTSMTO_CREATE_PICKING_TASK = 'shopee.outbound.rtsmto.createPickingTask'
static final String RTSMTO_BIND_PICKING_TASK = 'shopee.outbound.rtsmto.bindPickingTask'
static final String RTSMTO_APPEND_ORDER = 'shopee.outbound.rtsmto.appendOrder'
static final String RTSMTO_SYNC_PICKING_DETAIL = 'shopee.outbound.rtsmto.syncPickingDetail'
static final String RTSMTO_COMPLETE_TASK = 'shopee.outbound.rtsmto.completeTask'
static final String RTSMTO_CHANGE_PICKING_DEVICE = 'shopee.outbound.rtsmto.changePickingDevice'
static final String MTO_CREATE_PICKING_TASK = 'shopee.outbound.mto.createPickingTask'
static final String MTO_CANCEL_PICKING_TASK = 'shopee.outbound.mto.cancelPickingTask'
static final String MTO_START_PICKING_TASK = 'shopee.outbound.mto.startPickingTask'
static final String MTO_BIND_PICKING_TASK = 'shopee.outbound.mto.bindPickingTask'
static final String MTO_SYNC_PICKING_DETAIL = 'shopee.outbound.mto.syncPickingDetail'
static final String MTO_COMPLETE_PICKING_TASK = 'shopee.outbound.mto.completePickingTask'
static final String RACK_TRANSFER_CREATE_RT_ORDER = 'shopee.inventory.racktransfer.createRtOrder'
  • Step 2: Add DTO shells

Create DTOs with only fields present in the protocol tables and mapped to WES fields. Use Groovy classes, no Lombok, no new validation framework.

Minimum classes:

package com.ittx.wms.api.domain.platform.shopee

import java.io.Serializable

class ShopeeCreateSubPickingTaskCmd implements Serializable {
    String whs_id
    String sub_pickup_id
    Integer urgent_flag
    Long cut_off_time
    Long ship_by_date
    Long purchase_time
    Long order_time
    Long ctime
    Integer can_group_picking
    String group_key
    List<ShopeeSkuInfoCmd> sku_info_list = []
    List<ShopeeSingleAttrCmd> single_attr_list = []
    List<ShopeeMultiAttrCmd> multi_attr_list = []
    Integer ticket_type
}

class ShopeeSkuInfoCmd implements Serializable {
    String sku_id
    BigDecimal qty
    Integer block_type
    Integer quality
}

class ShopeeSingleAttrCmd implements Serializable {
    String attr_key
    String attr_value_id
    String attr_value_type
    String attr_value_name
}

class ShopeeMultiAttrCmd implements Serializable {
    String attr_key
    List<ShopeeAttrCmd> attr_value_list = []
}

class ShopeeAttrCmd implements Serializable {
    String attr_value_id
    String attr_value_type
    String attr_value_name
}

class ShopeeCancelSubPickingTaskCmd implements Serializable {
    String whs_id
    String sub_pickup_id
}

class ShopeeCreateRtOrderCmd implements Serializable {
    String whs_id
    String rt_order_id
    List<ShopeeRtAttrCmd> attr_list = []
    Long ctime
    String operator
    Integer ticket_type
    List<ShopeeRtSkuInfoCmd> sku_info_list = []
}

class ShopeeRtAttrCmd implements Serializable {
    String attr_key
    String attr_value_id
    String attr_value_name
}

class ShopeeRtSkuInfoCmd implements Serializable {
    String sku_id
    Integer block_type
    BigDecimal qty
    Integer sku_quality
}

class ShopeeUpdateTaskFlagCmd implements Serializable {
    String whs_id
    Integer biz_type
    Integer ticket_type
    List<String> ticket_number_list = []
    Integer urgent_flag
}
  • Step 3: Compile-check DTO syntax

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy --no-problems-report

Expected: Groovy compile succeeds, or dependency resolution fails due local Nexus credentials. If Nexus fails, record the exact failure.

Task 2: Wire ShopeeInboundApiHandler

Files:

  • Modify: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\ShopeeInboundApiHandler.groovy

  • Create: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\ShopeeOutboundShipmentMappingService.groovy

  • Step 1: Inject the mapping service

Add:

@Autowired
ShopeeOutboundShipmentMappingService shopeeOutboundShipmentMappingSvc
  • Step 2: Add switch cases

Use the constants from Task 1:

case PlatformApiConstants.ShopeeInterfaceType.SALES_CREATE_SUB_PICKING_TASK:
    return createSubPickingTask(session, data)
case PlatformApiConstants.ShopeeInterfaceType.SALES_CANCEL_SUB_PICKING_TASK:
    return cancelSubPickingTask(session, data)
case PlatformApiConstants.ShopeeInterfaceType.SALES_UPDATE_TASK_FLAG:
    return updateTaskFlag(session, data)

For APIs not implemented in this first slice, return ApiMessages.MSG_INTF_0005 until their task is started.

  • Step 3: Add parse/validate methods

Implement each method with the same pattern as EssInboundApiHandler: parse, validate required fields, set CURRENT_WAREHOUSE, call service.

Example:

private ResponseMessage createSubPickingTask(TtxSession session, String data) {
    ShopeeCreateSubPickingTaskCmd cmd
    try {
        cmd = convertValue(data, ShopeeCreateSubPickingTaskCmd.class)
    } catch (Throwable ignored) {
        return ResponseMessageFactory.error(ApiMessages.MSG_INTF_0003)
    }
    ResponseMessage validateRsp = validateCreateSubPickingTask(cmd)
    if (validateRsp.hasError()) return validateRsp
    session.params[WmsConstants.CURRENT_WAREHOUSE] = cmd.whs_id
    return shopeeOutboundShipmentMappingSvc.createSubPickingTask(session, cmd)
}
  • Step 4: Compile

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy --no-problems-report

Expected: Compile succeeds or reports only external dependency credential issues.

Task 3: Implement Sales SubPickingTask Mapping

Files:

  • Modify: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\ShopeeOutboundShipmentMappingService.groovy

  • Reuse: ShipmentHeaderService, ShipmentDetailService, existing config/dictionary service if available

  • Step 1: Implement createSubPickingTask

Map fields:

Shopee field WES field
whs_id shipment_header.warehouseCode
sub_pickup_id shipment_header.erpOrderCode and shipment_header.shopeeWave
urgent_flag shipment_header.priority
cut_off_time shipment_header.cutOffTime
ship_by_date shipment_header.scheduledShipDate
purchase_time shipment_header.purchaseTime
order_time shipment_header.orderTime
ctime if no dedicated WES field exists, do not add one; keep request log only
can_group_picking do not add a field until confirmed; current ShipmentHeader has no field
group_key shipment_header.groupKey
ticket_type shipment_header.ticketType; shipmentType defaults to XSCK
max_order_size attr shipment_header.orderSize only if field exists before implementation; otherwise map through rule dictionary
channel_id attr shipment_header.channelId only if field exists before implementation; otherwise map through rule dictionary
fulfillment_chain_id attr shipment_header.fulfillmentChainId only if field exists before implementation; otherwise map through rule dictionary

Use existing fields only. If a field is not present in ShipmentHeader.groovy at execution time, do not add it just for convenience; write it into the existing Shopee field dictionary flow if available, or document the gap.

  • Step 2: Implement idempotency

Use warehouseCode + erpOrderCode:

select id from shipment_header where warehouseCode = ? and erpOrderCode = ? and processType = 'NORMAL'

If found, update mutable Shopee fields and replace details only if the task is still in pool. If task has started, return a non-retryable parameter/business error.

  • Step 3: Create details

For each sku_info_list row:

Shopee field WES field
sku_id shipment_detail.itemCode
qty shipment_detail.requestQty
block_type shipment_detail.shelfLifeSts
quality shipment_detail.inventorySts

Validate SKU existence by reusing existing item/detail service behavior. Do not hand-roll item master queries if a service already validates item code.

  • Step 4: Map attributes to dictionary

For single_attr_list and multi_attr_list, write missing values to the existing field dictionary mechanism if it exists. If no shopee_field_dictionary table/service exists in the current branch, create a separate migration only after confirming the table is absent:

rg -n "shopee_field_dictionary|FieldDictionary|field_dictionary" C:\work\gitlab\shopee\wes-loghub

If absent, add a later task before implementation; do not create it inside this task ad hoc.

  • Step 5: Compile

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy :wms-shipping:compileGroovy --no-problems-report

Task 4: Implement Cancel and Urgent Flag Update

Files:

  • Modify: ShopeeOutboundShipmentMappingService.groovy

  • Step 1: Implement cancel

Input: ShopeeCancelSubPickingTaskCmd.

Behavior:

  • Find by warehouseCode = whs_id, erpOrderCode = sub_pickup_id.

  • If not found, return non-retryable business error.

  • If already picked/closed, return non-retryable business error.

  • Otherwise reuse existing cancellation/status-flow service. If no exact cancel service applies, mark processType/status via the same base cancel path used by manual shipment cancel, not a raw SQL-only status mutation.

  • Ensure wave matching excludes canceled shipments.

  • Step 2: Implement urgent flag update

Input: ShopeeUpdateTaskFlagCmd.

Behavior:

  • Batch update shipment_header.priority by erpOrderCode in ticket_number_list.
  • Return success_list, fail_list, and not_exist_list.
  • Do not fail the entire request because one order is missing.

Return shape:

[
    success_list: successCodes,
    fail_list: failCodes,
    not_exist_list: notExistCodes
]
  • Step 3: Compile

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy :wms-shipping:compileGroovy --no-problems-report

Task 5: Implement edi-shopee JWT Service

Files:

  • Create: C:\work\gitlab\shopee\edi-shopee\src\main\groovy\com\ittx\edi\erp\service\ShopeeJwtService.groovy

  • Modify config files under C:\work\gitlab\shopee\edi-shopee\src\main\resources\

  • Step 1: Add JWT generation

Implement HS256 with Base64 URL encoding:

String generateToken(String account, String secret, Long timestampSeconds = System.currentTimeMillis().intdiv(1000L)) {
    Map header = [alg: 'HS256', typ: 'JWT']
    Map payload = [timestamp: timestampSeconds, account: account]
    String signingInput = base64UrlJson(header) + '.' + base64UrlJson(payload)
    String signature = hmacSha256Base64Url(signingInput, secret)
    return signingInput + '.' + signature
}
  • Step 2: Add token validation

Validation must:

  • Split token into 3 segments.

  • Verify signature using configured secret.

  • Parse payload timestamp.

  • Reject if absolute clock drift is greater than 300 seconds.

  • Reject if account does not match configured account.

  • Step 3: Add config

Use profile values like:

shopee:
  automation:
    account: xxx
    secret: xxx
    env: uat
    cid: cn
    connectTimeoutMillis: 3000
    readTimeoutMillis: 10000

Do not hard-code secrets in code.

  • Step 4: Compile edi-shopee

Run:

cd C:\work\gitlab\shopee\edi-shopee
.\gradlew compileGroovy

Task 6: Implement edi-shopee Shopee Automation Gateway

Files:

  • Create: C:\work\gitlab\shopee\edi-shopee\src\main\groovy\com\ittx\edi\erp\service\ShopeeAutomationVendorService.groovy

  • Create: C:\work\gitlab\shopee\edi-shopee\src\main\groovy\com\ittx\edi\erp\controller\ShopeeAutomationController.groovy

  • Step 1: Add Shopee inbound controller methods

Expose Shopee-facing POST endpoints in edi-shopee, not in WES:

@PostMapping('/api/v2/automation/toshopee/outbound/salesorder/create_sub_picking_task')
Object createSubPickingTask(@RequestHeader('Authorization') String authorization,
                            @RequestBody Map body) {
    return shopeeAutomationVendorSvc.forwardShopeeInbound(
            authorization,
            'shopee.outbound.salesorder.createSubPickingTask',
            body)
}

Add the same pattern for implemented Shopee inbound interfaces:

  • /api/v2/automation/toshopee/outbound/salesorder/cancel_sub_picking_task

  • /api/v2/automation/toshopee/outbound/salesorder/update_task_flag

  • sales order and demand pool paths when their WES handlers are implemented.

  • Step 2: Validate JWT before forwarding to WES

For every Shopee inbound request:

  • Read Authorization.
  • Validate JWT using ShopeeJwtService.
  • Reject invalid account, signature, or timestamp over 5 minutes.
  • Return Shopee response shape directly:
[retcode: -10000000, message: 'auth failed', data: [:]]
  • Step 3: Forward valid Shopee inbound requests to WES

Call WES existing sync API from edi-shopee:

POST {wesBaseUrl}/api/wms/api/sync/in?api={apiName}&format=json
Content-Type: application/json
Body: original Shopee body

Convert WES ResponseMessage back to Shopee response shape:

[retcode: 0, message: 'success', data: rsp.data ?: [:]]

On WES business error, map to the configured Shopee error code and message. Do not expose WES stack traces.

  • Step 4: Add outbound call method

Implement:

ResponseMessage postToShopee(String module, String subModule, String interfaceName, Map body)

Build URL:

https://wms-automation.ssc.{env}.shopee.{cid}/api/v2/automation/{direction}/{module}/{sub_module}/{interface_name}

For production, omit {env} according to the protocol.

  • Step 5: Add headers for edi-shopee outbound calls to Shopee

Headers:

[
    'Content-Type': 'application/json',
    'Authorization': shopeeJwtSvc.generateToken(account, secret)
]
  • Step 6: Normalize response from Shopee outbound calls

Shopee response:

{"retcode":0,"message":"success","data":{}}

Convert to CBT:

  • retcode == 0 -> ResponseMessageFactory.success(data)

  • otherwise -> ResponseMessageFactory.error(message) and include retcode in data or parameters

  • Step 7: Add internal endpoints for WES-to-Shopee calls

Internal controller methods:

  • POST /internal/shopee/automation/waveNumbers
  • POST /internal/shopee/automation/bindWave
  • POST /internal/shopee/automation/cancelOrder
  • POST /internal/shopee/automation/flowPickChangeBox
  • POST /internal/shopee/automation/rackTransfer/createRtOrder

Keep these endpoints internal. They are for wes-loghub to call edi-shopee; Shopee-facing inbound endpoints are the /api/v2/automation/toshopee/... methods above, while WES-to-Vendor calls use tovendor paths built by edi-shopee.

  • Step 8: Compile

Run:

cd C:\work\gitlab\shopee\edi-shopee
.\gradlew compileGroovy

Task 7: Replace WES ShopeeWaveService Placeholder

Files:

  • Modify: C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\ShopeeWaveService.groovy

  • Step 1: Add an internal edi-shopee client method

Prefer a small private method in ShopeeWaveService or a focused bean if an existing HTTP utility is available.

Request:

[
    warehouseCode: warehouseCode,
    waveType: waveType,
    needQty: needQty
]

Response data should be a list of wave codes:

[
    [code: 'SW202606040001', waveType: '1'],
    [code: 'SW202606040002', waveType: '1']
]
  • Step 2: Implement requestShopeeWaveNumbers

Replace the placeholder log-only method with:

  • Call edi-shopee internal endpoint.

  • Convert returned codes to ShopeeWave.

  • Return empty list on call failure and log a process history error.

  • Do not generate fake local wave numbers.

  • Step 3: Verify low-water scheduler

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-wave:compileGroovy --no-problems-report

Task 8: Replace WES Wave Binding Placeholder

Files:

  • Modify: C:\work\gitlab\shopee\wes-loghub\wms-wave\src\main\groovy\com\ittx\wms\wave\service\hairo\WaveRuleMatchService.groovy

  • Step 1: Implement requestShopeeWaveBind via edi-shopee

Request:

[
    warehouseCode: warehouseCode,
    shopeeWaveCode: shopeeWaveCode,
    shipmentIds: shipmentIds
]

Response:

[
    success_list: [1L, 2L],
    fail_list: [3L]
]
  • Step 2: Parse partial failure

Current code treats bind failure as whole batch failure. Change only if Shopee returns per-order failure. If partial failure exists:

  • Mark failed shipments with kickOutWave.
  • Continue binding successful shipment IDs.
  • Return success if at least one shipment bound.

If Shopee returns whole request failure, keep existing whole-batch behavior.

  • Step 3: Keep lock and rollback semantics

Do not remove:

  • buildShopeeWaveBindLockKey
  • isShopeeWaveCodeAvailable
  • rollbackShipmentShopeeWave
  • rollbackWaveShipments

These protect local WES consistency around external calls.

  • Step 4: Compile

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-wave:compileGroovy --no-problems-report

Task 9: Implement 4.1 Rack-Transfer Create RT Order Sync

Files:

  • Modify: C:\work\gitlab\shopee\wes-loghub\wms-transfer\src\main\groovy\com\ittx\wms\transfer\service\TransferOrderHeaderService.groovy

  • Modify only if needed: C:\work\gitlab\shopee\wes-loghub\wms-transfer\src\main\groovy\com\ittx\wms\transfer\service\TransferOrderDetailService.groovy

  • Create only if needed: C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\ShopeeRackTransferMappingService.groovy

  • Step 1: Find the existing transfer-order reserved-stock completion point

Start from:

rg -n "locate|定位|lock|锁定|TransferOrderHeader|TransferOrderDetail" C:\work\gitlab\shopee\wes-loghub\wms-transfer -g "*.groovy"

Use the existing point where the WES rack-transfer order has been created and inventory has been reserved/locked. Do not call Vendor before WES has a durable rt_order_id.

  • Step 2: Build the 4.1 request body from existing transfer data

Request shape:

[
    whs_id: transferOrderHeader.warehouseCode,
    rt_order_id: transferOrderHeader.transferOrderCode,
    attr_list: [
        [attr_key: 'urgent_flag', attr_value_id: urgentFlag as String, attr_value_name: urgentFlag as String],
        [attr_key: 'oos_order_qty', attr_value_id: oosOrderQty as String, attr_value_name: oosOrderQty as String]
    ].findAll { it.attr_value_id != null },
    ctime: transferOrderHeader.createdDate?.time?.intdiv(1000L),
    operator: transferOrderHeader.createdBy,
    ticket_type: 6,
    sku_info_list: detailRows.collect {
        [
            sku_id: it.itemCode,
            block_type: mapInventoryStatusToShopeeBlockType(it.inventorySts),
            qty: it.quantity,
            sku_quality: mapInventoryStatusToShopeeQuality(it.inventorySts)
        ]
    }
]

If urgent_flag or oos_order_qty is not represented on current transfer entities, omit that attr item instead of adding a field. Keep ticket_type = 6 fixed for Rack Transfer.

  • Step 3: Call edi-shopee internal endpoint

Use the same internal client pattern as wave-number and wave-bind calls:

POST {ediShopeeBaseUrl}/internal/shopee/automation/rackTransfer/createRtOrder

edi-shopee then calls Shopee:

POST /api/v2/automation/tovendor/inventory/racktransfer/create_rt_order

Failure handling:

  • For retryable Shopee errors -10013099 or timeout, record process history and let existing retry infrastructure retry the sync.

  • For non-retryable errors -10013001, -10013023, -10013049, -10013098, record the response and block/flag the transfer order according to the current WES transfer failure pattern.

  • Step 4: Compile transfer and API modules

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-transfer:compileGroovy :wms-api:compileGroovy --no-problems-report

Task 9: Add Sales Order and Demand Pool Interfaces

Files:

  • Modify DTO file from Task 1.

  • Modify ShopeeInboundApiHandler.groovy.

  • Modify ShopeeOutboundShipmentMappingService.groovy.

  • Step 1: Implement sales order create/pull/revoke/lock

Use the same handler pattern as Tasks 2-4.

Minimum mappings:

  • Sales order create maps to shipment_header and shipment_detail, with ticketType = 2.

  • Pull order queries eligible WES orders by Shopee identifiers and current state.

  • Revoke order cancels or marks excluded, using existing shipment cancel/status services.

  • Lock/unlock writes existing lock fields if present; otherwise reuse standard lock service.

  • Step 2: Implement RTS/MTO demand create/cancel/sync occupy

Minimum mappings:

  • RTS demand -> shipmentType = RTS, ticketType = 3.

  • MTO demand -> shipmentType = MTO, ticketType = 4.

  • MTO task -> ticketType = 5.

  • RT -> ticketType = 6, does not use Shopee wave number.

  • Step 3: Guard one-wave-one-order types

Ensure WaveRuleMatchService one-wave-one-order logic keeps:

  • ticketType = 1

  • ticketType = 5

  • ticketType = 6

  • Step 4: Compile

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy :wms-wave:compileGroovy --no-problems-report

Task 10: Error Code and Response Contract

Files:

  • Modify: ShopeeInboundApiHandler.groovy

  • Modify: ShopeeOutboundShipmentMappingService.groovy

  • Step 1: Normalize WES response

WES standard ResponseMessage is acceptable inside current sync API. If raw Shopee response shape is required later, add one adapter at the boundary only:

[
    retcode: rsp.hasError() ? mappedCode : 0,
    message: rsp.hasError() ? translatedText : 'success',
    data: rsp.data ?: [:]
]

Do not change ResponseMessage globally.

  • Step 2: Map retry behavior

Use the protocol:

  • Parameter/business errors -> no auto retry.
  • System, DB, timeout, upstream unavailable -> auto retry.

Represent this either as mapped retcode or process-history reason. Do not invent a new retry table unless current sync-fail infrastructure cannot express it.

Task 11: Verification and Documentation

Files:

  • Modify: C:\work\gitlab\shopee\docs\统一汇总.md

  • Modify or create: focused API implementation note under C:\work\gitlab\shopee\docs\

  • Step 1: Run WES compile

Run:

cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy :wms-wave:compileGroovy :wms-shipping:compileGroovy --no-problems-report
  • Step 2: Run edi-shopee compile

Run:

cd C:\work\gitlab\shopee\edi-shopee
.\gradlew compileGroovy
  • Step 3: Manual Shopee-facing smoke tests

Use one JSON body per implemented interface through edi-shopee, which then forwards to WES:

Invoke-RestMethod `
  -Method Post `
  -Uri 'http://localhost:9210/api/v2/automation/toshopee/outbound/salesorder/create_sub_picking_task' `
  -ContentType 'application/json' `
  -Headers @{ Authorization = '<valid-shopee-jwt>' } `
  -Body '<json body from protocol>'

Expected:

  • Response is Shopee shape with retcode = 0.

  • shipment_header has Shopee fields.

  • shipment_detail rows exist.

  • Duplicate request is idempotent.

  • Step 4: Manual 4.1 WES-to-Vendor smoke test

Trigger or post the internal edi-shopee endpoint with a rack-transfer body:

Invoke-RestMethod `
  -Method Post `
  -Uri 'http://localhost:9210/internal/shopee/automation/rackTransfer/createRtOrder' `
  -ContentType 'application/json' `
  -Body '{
    "whs_id":"SGL",
    "rt_order_id":"RTSGL00020250102001",
    "attr_list":[{"attr_key":"urgent_flag","attr_value_id":"1","attr_value_name":"1"}],
    "ctime":1780000000,
    "operator":"xxx@shopee.com",
    "ticket_type":6,
    "sku_info_list":[{"sku_id":"18377383_9999","block_type":0,"qty":2,"sku_quality":0}]
  }'

Expected:

  • edi-shopee calls /api/v2/automation/tovendor/inventory/racktransfer/create_rt_order.

  • JWT header is present.

  • Shopee success maps to CBT success.

  • Retryable -10013099 remains retryable.

  • Step 5: Manual internal WES smoke tests

Use the WES sync API only as an internal verification target:

Invoke-RestMethod `
  -Method Post `
  -Uri 'http://localhost:9001/api/wms/api/sync/in?api=shopee.outbound.salesorder.createSubPickingTask&format=json' `
  -ContentType 'application/json' `
  -Body '<json body from protocol>'
  • Step 6: Manual wave tests

Check:

  • Low-water scheduler calls edi-shopee and saves shopee_wave.

  • Wave binding calls edi-shopee.

  • Failed bind marks kickOutWave.

  • Successful bind writes shipment_header.shopeeWave.

  • Step 7: Update docs

Update:

  • docs/统一汇总.md: move implemented items from "待实现" to "已落地".
  • Add a concise implementation note linking ShopeeInboundApiHandler, ShopeeWaveService, WaveRuleMatchService, and edi-shopee JWT service.

Self-Review

  • Spec coverage: Covers edi-shopee as Shopee-facing entry, WES field mapping, wave number request, wave binding, user-confirmed 4.1 rack-transfer sync, JWT ownership in edi-shopee, and verification.
  • Scope control: Excludes inbound/basic modules and excludes inventory except 4.1; avoids adding fields unless absent fields are explicitly confirmed as required.
  • Type consistency: DTO names and handler method names match task references.
  • Confirmed entry contract: Shopee 调 edi-shopeeedi-shopee 再转发到 WES /api/wms/api/sync/in?api=shopee...WES 不直接作为 Shopee 外部入口。