42 KiB
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.mdwes-loghub/wms-api/src/main/groovy/com/ittx/wms/api/service/platform/ShopeeInboundApiHandler.groovyC:\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
SyncApiControlleralready receives standard platform calls and delegates throughApiService; this remains the internal WES target called byedi-shopee.ApiServicealready delegatesess.*andshopee.*APIs toPlatformInboundApiService.ShopeeInboundApiHandlerexists but has no outbound cases implemented.ShipmentHeaderalready contains key Shopee fields:cutOffTime,purchaseTime,orderTime,shopeeWave,waveRule,kickOutWave,ticketType,pickType,orderStructure,groupKey.ShopeeWaveServicehas low-water scheduling and persistence, but the actual upstream wave-number request is still a placeholder.WaveRuleMatchServicehas Shopee wave binding placeholders and local binding/rollback logic.wms-transferalready contains transfer order services; use those existing transfer entities/services for rack-transfer data extraction before adding any new persistence.edi-shopeealready 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
requestShopeeWaveNumbersplaceholder with a call toedi-shopee.
- Replace
- Modify:
wms-wave/src/main/groovy/com/ittx/wms/wave/service/hairo/WaveRuleMatchService.groovy- Replace Shopee wave bind placeholder with a call to
edi-shopeeand parse failed shipment IDs.
- Replace Shopee wave bind placeholder with a call to
- Modify:
wms-transfer/src/main/groovy/com/ittx/wms/transfer/service/TransferOrderHeaderService.groovyor the existing transfer-order creation/release hook that creates rack-transfer reserved stock.- Trigger
4.1WMS-to-Vendor rack-transfer sync after WES has created and reserved the transfer order.
- Trigger
- 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.1request body from existingTransferOrderHeader/detail data.
- Build the
- Modify:
src/main/resources/application.ymland profile-specific config only if no existing system-parameter path is available.- Add internal
edi-shopeeendpoint only if it cannot be represented by existing API/customer config.
- Add internal
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-loghubfor wave-number request and wave binding.
- External endpoints called by Shopee plus internal endpoints consumed by
- Modify:
src/main/resources/application-dev.ymlandsrc/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.1to3.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.1rack-transfer API. - Total implemented protocol API inventory in this plan: 36 concrete APIs.
- Other inventory/rack-transfer URLs after
4.1are 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.prioritybyerpOrderCode in ticket_number_list. - Return
success_list,fail_list, andnot_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/waveNumbersPOST /internal/shopee/automation/bindWavePOST /internal/shopee/automation/cancelOrderPOST /internal/shopee/automation/flowPickChangeBoxPOST /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-shopeeinternal 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
requestShopeeWaveBindvia 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:
buildShopeeWaveBindLockKeyisShopeeWaveCodeAvailablerollbackShipmentShopeeWaverollbackWaveShipments
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
-10013099or 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_headerandshipment_detail, withticketType = 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_headerhas Shopee fields. -
shipment_detailrows 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-shopeecalls/api/v2/automation/tovendor/inventory/racktransfer/create_rt_order. -
JWT header is present.
-
Shopee success maps to CBT success.
-
Retryable
-10013099remains 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-shopeeand savesshopee_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, andedi-shopeeJWT service.
Self-Review
- Spec coverage: Covers
edi-shopeeas Shopee-facing entry, WES field mapping, wave number request, wave binding, user-confirmed4.1rack-transfer sync, JWT ownership inedi-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-shopee,edi-shopee再转发到 WES/api/wms/api/sync/in?api=shopee...,WES 不直接作为 Shopee 外部入口。