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

1186 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 protocol sections `4.1`-`5.8`, 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, inventory/rack-transfer module, and basic-data module interfaces present in this protocol file, based on:
- `出库单接口文档/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 module interfaces in this plan. Include all concrete interfaces from `3.1`-`3.4`, `4.1`-`4.7`, and `5.1`-`5.8`.
## 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.
- Confirmed: `4.2`-`4.5` Vendor-to-WES inventory/rack-transfer interfaces all enter WES through `ShopeeInboundApiHandler`; the handler then reuses existing `wms-transfer` and `wms-inventory` services.
- Confirmed: WES-to-Vendor basic-data interfaces in `5.2`-`5.8` are driven by scheduled jobs using a three-step `.confirm` -> `.get` -> `.uploadStatus` flow. Protocol `5.5` remains Vendor-to-WES and is not part of that scheduled outbound flow.
- Confirmed: Shopee extension fields are persisted through the dictionary mechanism only. Do not add WES entity columns for Shopee-only fields; use different dictionary group types for different usage locations.
- Confirmed: All 50 concrete interfaces in this plan must be implemented in this delivery. New Shopee-specific implementation classes must live under a `shopee` subpackage; keep only the existing platform entry handler in `service/platform`.
- `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`
- Keep as the existing platform dispatch entry; add switch cases for all 50 concrete APIs and delegate to services in the `shopee` subpackage.
- Create: `wms-api/src/main/groovy/com/ittx/wms/api/domain/platform/shopee/ShopeeOutboundApiCmds.groovy`
- Store request DTOs for all 50 Shopee protocol 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/shopee/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.
- Do not modify WES transfer-order business hooks for `4.1` in this phase.
- Only implement the `4.1`, `4.6`, and `4.7` interface surfaces and `edi-shopee` forwarding paths; the real移库业务触发点 remains explicitly out of scope.
- Create only if no existing transfer service method is suitable: `wms-api/src/main/groovy/com/ittx/wms/api/service/platform/shopee/ShopeeRackTransferMappingService.groovy`
- Define `4.1`, `4.6`, and `4.7` request contracts and response normalization only; do not read `TransferOrderHeader`/detail data until the business trigger is confirmed later.
- 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-zzw.yml`.
- Read Shopee JWT/account/host settings from `customerMap.shopee`; do not introduce a second config path in `application-dev.yml` or `application-k8s.yml` for this phase.
## Interface Inventory
Protocol alignment from `出库单接口文档/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.
- Inventory/rack-transfer URL count: 7 concrete APIs under `4.1` to `4.7`.
- Basic-data URL count: 8 concrete APIs under `5.1` to `5.8`.
- Total implemented protocol API inventory in this plan: 50 concrete APIs.
### 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 完成拣货任务 |
### Concrete Inventory and Basic APIs
| 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 同步移库占用库存 |
| 37 | 4.2 | Vendor -> WMS | `/api/v2/automation/toshopee/inventory/racktransfer/occupy_device` | `shopee.inventory.racktransfer.occupyDevice` | Vendor 占用或释放 PickingDevice |
| 38 | 4.3 | Vendor -> WMS | `/api/v2/automation/toshopee/inventory/racktransfer/submit_picking_sku_list` | `shopee.inventory.racktransfer.submitPickingSkuList` | Vendor 整箱同步移库拣货明细 |
| 39 | 4.4 | Vendor -> WMS | `/api/v2/automation/toshopee/inventory/racktransfer/sync_picking_sku_list` | `shopee.inventory.racktransfer.syncPickingSkuList` | Vendor 定时回传 WMS 创建移库单拣货明细 |
| 40 | 4.5 | Vendor -> WMS | `/api/v2/automation/toshopee/inventory/racktransfer/handle_diff_stock` | `shopee.inventory.racktransfer.handleDiffStock` | Vendor 盘点差异调减同步给 WMS |
| 41 | 4.6 | WMS -> Vendor | `/api/v2/automation/tovendor/inventory/racktransfer/query_picking_list` | `shopee.inventory.racktransfer.queryPickingList` | WES 查询 Vendor 移库拣货明细用于对账 |
| 42 | 4.7 | WMS -> Vendor | `/api/v2/automation/tovendor/inventory/racktransfer/update_rt_order` | `shopee.inventory.racktransfer.updateRtOrder` | WES 更新移库单优先级或取消状态 |
| 43 | 5.1 | Vendor -> WMS | `/api/v2/automation/toshopee/basic/user/query_user_exist` | `shopee.basic.user.queryUserExist` | Vendor 查询 WES 用户是否存在 |
| 44 | 5.2 | WMS -> Vendor | `/api/v2/automation/tovendor/basic/inventory/batch_status_update` | `shopee.basic.inventory.batchStatusUpdate` | WES 同步批次状态给 Vendor |
| 45 | 5.3 | WMS -> Vendor | `/api/v2/automation/tovendor/basic/inventory/query_sku_batch_inventory` | `shopee.basic.inventory.querySkuBatchInventory` | WES 查询 Vendor 批次库存信息 |
| 46 | 5.4 | WMS -> Vendor | `/api/v2/automation/tovendor/basic/inventory/sync_reconciliation_result` | `shopee.basic.inventory.syncReconciliationResult` | WES 同步库存对账结果给 Vendor |
| 47 | 5.5 | Vendor -> WMS | `/api/v2/automation/toshopee/basic/sku/query_sku_detail` | `shopee.basic.sku.querySkuDetail` | Vendor 查询 WES SKU 信息 |
| 48 | 5.6 | WMS -> Vendor | `/api/v2/automation/tovendor/basic/sku/sync_sku_info` | `shopee.basic.sku.syncSkuInfo` | WES 同步 SKU 信息给 Vendor |
| 49 | 5.7 | WMS -> Vendor | `/api/v2/automation/tovendor/basic/robot/query_agv_status` | `shopee.basic.robot.queryAgvStatus` | WES 查询 Vendor AGV 设备信息 |
| 50 | 5.8 | WMS -> Vendor | `/api/v2/automation/tovendor/basic/inventory/query_vendor_unit_list` | `shopee.basic.inventory.queryVendorUnitList` | WES 查询 Vendor Unit 信息 |
### 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 50 concrete APIs above. All 50 must be implemented in this delivery; 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 50-interface constant set under `PlatformApiConstants.ShopeeInterfaceType`. Do not leave any of these 50 constants as `ApiMessages.MSG_INTF_0005` in the final implementation:
```groovy
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'
static final String RACK_TRANSFER_OCCUPY_DEVICE = 'shopee.inventory.racktransfer.occupyDevice'
static final String RACK_TRANSFER_SUBMIT_PICKING_SKU_LIST = 'shopee.inventory.racktransfer.submitPickingSkuList'
static final String RACK_TRANSFER_SYNC_PICKING_SKU_LIST = 'shopee.inventory.racktransfer.syncPickingSkuList'
static final String RACK_TRANSFER_HANDLE_DIFF_STOCK = 'shopee.inventory.racktransfer.handleDiffStock'
static final String RACK_TRANSFER_QUERY_PICKING_LIST = 'shopee.inventory.racktransfer.queryPickingList'
static final String RACK_TRANSFER_UPDATE_RT_ORDER = 'shopee.inventory.racktransfer.updateRtOrder'
static final String BASIC_USER_QUERY_USER_EXIST = 'shopee.basic.user.queryUserExist'
static final String BASIC_INVENTORY_BATCH_STATUS_UPDATE = 'shopee.basic.inventory.batchStatusUpdate'
static final String BASIC_INVENTORY_QUERY_SKU_BATCH_INVENTORY = 'shopee.basic.inventory.querySkuBatchInventory'
static final String BASIC_INVENTORY_SYNC_RECONCILIATION_RESULT = 'shopee.basic.inventory.syncReconciliationResult'
static final String BASIC_SKU_QUERY_SKU_DETAIL = 'shopee.basic.sku.querySkuDetail'
static final String BASIC_SKU_SYNC_SKU_INFO = 'shopee.basic.sku.syncSkuInfo'
static final String BASIC_ROBOT_QUERY_AGV_STATUS = 'shopee.basic.robot.queryAgvStatus'
static final String BASIC_INVENTORY_QUERY_VENDOR_UNIT_LIST = 'shopee.basic.inventory.queryVendorUnitList'
```
- [ ] **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:
```groovy
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:
```powershell
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\shopee\ShopeeOutboundShipmentMappingService.groovy`
- [ ] **Step 1: Inject the mapping service**
Add:
```groovy
@Autowired
ShopeeOutboundShipmentMappingService shopeeOutboundShipmentMappingSvc
```
- [ ] **Step 2: Add switch cases**
Use the constants from Task 1. Add switch cases for all 50 concrete APIs and delegate to focused services under `com.ittx.wms.api.service.platform.shopee`.
```groovy
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)
case PlatformApiConstants.ShopeeInterfaceType.RACK_TRANSFER_OCCUPY_DEVICE:
return shopeeRackTransferSvc.occupyDevice(session, data)
case PlatformApiConstants.ShopeeInterfaceType.BASIC_SKU_QUERY_SKU_DETAIL:
return shopeeBasicDataSvc.querySkuDetail(session, data)
```
Final implementation must not return `ApiMessages.MSG_INTF_0005` for any of the 50 concrete APIs in the inventory.
- [ ] **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:
```groovy
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:
```powershell
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\shopee\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` | dictionary only if business needs query/display; otherwise request log only |
| `can_group_picking` | dictionary only |
| `group_key` | `shipment_header.groupKey` |
| `ticket_type` | `shipment_header.ticketType`; `shipmentType` defaults to `XSCK` |
| `max_order_size` attr | dictionary only |
| `channel_id` attr | dictionary only |
| `fulfillment_chain_id` attr | dictionary only |
Use WES entity fields only for existing core business facts already present in the domain. All Shopee-only extension attributes go through the dictionary mechanism, not new columns.
- [ ] **Step 2: Implement idempotency**
Use `warehouseCode + erpOrderCode`:
```sql
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 all Shopee extension attributes to dictionary**
For `single_attr_list`, `multi_attr_list`, and Shopee-only scalar fields, write values to the existing field dictionary mechanism. Different usage locations must use different dictionary group types so downstream logic can distinguish meaning.
Minimum group type split:
| Usage location | Dictionary group type |
| --- | --- |
| Outbound sales/sub-picking task header attrs | `SHOPEE_OUTBOUND_TASK_HEADER` |
| Outbound order attrs | `SHOPEE_OUTBOUND_ORDER_HEADER` |
| RTS/MTO demand attrs | `SHOPEE_RTSMTO_DEMAND_HEADER` |
| MTO picking task attrs | `SHOPEE_MTO_TASK_HEADER` |
| Inventory/rack-transfer attrs | `SHOPEE_RACK_TRANSFER_HEADER` |
| Basic-data sync attrs | `SHOPEE_BASIC_DATA` |
If no compatible dictionary table/service exists in the current branch, add a separate dictionary task before implementation; do not add Shopee-only fields to WES domain entities:
```powershell
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:
```powershell
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: `C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\shopee\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:
```groovy
[
success_list: successCodes,
fail_list: failCodes,
not_exist_list: notExistCodes
]
```
- [ ] **Step 3: Compile**
Run:
```powershell
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: `C:\work\gitlab\shopee\edi-shopee\src\main\resources\application-zzw.yml`
- [ ] **Step 1: Add JWT generation**
Implement HS256 with Base64 URL encoding:
```groovy
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: Read config from `customerMap.shopee`**
JWT and Shopee host configuration live in `application-zzw.yml` under `customerMap.shopee`:
```yaml
customerMap:
shopee:
account: xxx
secret: xxx
env: uat
cid: cn
connectTimeoutMillis: 3000
readTimeoutMillis: 10000
```
`ShopeeJwtService` and `ShopeeAutomationVendorService` must read `account`, `secret`, host environment, country id, and timeout values from `customerMap.shopee`. Do not hard-code secrets in code and do not add duplicate Shopee JWT config under another profile file in this phase.
- [ ] **Step 4: Compile edi-shopee**
Run:
```powershell
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:
```groovy
@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:
```groovy
[retcode: -10000000, message: 'auth failed', data: [:]]
```
- [ ] **Step 3: Forward valid Shopee inbound requests to WES**
Call WES existing sync API from `edi-shopee`:
```text
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:
```groovy
[retcode: 0, message: 'success', data: rsp.data ?: [:]]
```
On WES business error, do not implement final Shopee `retcode` mapping yet. Return a generic Shopee failure shape, keep the WES message sanitized, and log the original WES error for later mapping design. Do not expose WES stack traces.
- [ ] **Step 4: Add outbound call method**
Implement:
```groovy
ResponseMessage postToShopee(String module, String subModule, String interfaceName, Map body)
```
Build URL:
```text
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:
```groovy
[
'Content-Type': 'application/json',
'Authorization': shopeeJwtSvc.generateToken(account, secret)
]
```
- [ ] **Step 6: Normalize response from Shopee outbound calls**
Shopee response:
```json
{"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:
```powershell
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:
```groovy
[
warehouseCode: warehouseCode,
waveType: waveType,
needQty: needQty
]
```
Response data should be a list of wave codes:
```groovy
[
[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:
```powershell
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:
```groovy
[
warehouseCode: warehouseCode,
shopeeWaveCode: shopeeWaveCode,
shipmentIds: shipmentIds
]
```
Response:
```groovy
[
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:
```powershell
cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-wave:compileGroovy --no-problems-report
```
### Task 9A: Implement Inventory and Basic Interface Surfaces 4.1-5.8
**Files:**
- Do not modify: `C:\work\gitlab\shopee\wes-loghub\wms-transfer\src\main\groovy\com\ittx\wms\transfer\service\TransferOrderHeaderService.groovy`
- Do not modify: `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\shopee\ShopeeRackTransferMappingService.groovy`
- [ ] **Step 1: Keep 4.1 business trigger out of scope**
Do not search for or wire the WES移库单创建、定位、锁定、占用完成事件 in this phase.
Implementation target for `4.1` is limited to:
- `edi-shopee` internal endpoint: `POST /internal/shopee/automation/rackTransfer/createRtOrder`
- Shopee outbound call: `POST /api/v2/automation/tovendor/inventory/racktransfer/create_rt_order`
- Request validation, JWT header generation, response normalization, logging, and retry classification.
- [ ] **Step 2: Define the 4.1 request contract without wiring WES transfer data**
Endpoint request shape accepted by `edi-shopee`:
```groovy
[
whs_id: request.whs_id,
rt_order_id: request.rt_order_id,
attr_list: [
[attr_key: 'urgent_flag', attr_value_id: '1', attr_value_name: '1'],
[attr_key: 'oos_order_qty', attr_value_id: '0', attr_value_name: '0']
].findAll { it.attr_value_id != null },
ctime: request.ctime,
operator: request.operator,
ticket_type: 6,
sku_info_list: request.sku_info_list.collect {
[
sku_id: it.sku_id,
block_type: it.block_type,
qty: it.qty,
sku_quality: it.sku_quality
]
}
]
```
Keep `ticket_type = 6` fixed for Rack Transfer. The later WES business trigger will be responsible for building this body from real transfer entities.
- [ ] **Step 3: Add 4.2-4.5 Vendor-to-WES rack-transfer handlers**
Expose these Shopee-facing `edi-shopee` endpoints and forward them to WES sync API with the WES api name below. WES dispatch must be:
```text
edi-shopee -> /api/wms/api/sync/in?api=<WES api>&format=json -> PlatformInboundApiService -> ShopeeInboundApiHandler
```
| Section | Path | WES api |
| --- | --- | --- |
| 4.2 | `/api/v2/automation/toshopee/inventory/racktransfer/occupy_device` | `shopee.inventory.racktransfer.occupyDevice` |
| 4.3 | `/api/v2/automation/toshopee/inventory/racktransfer/submit_picking_sku_list` | `shopee.inventory.racktransfer.submitPickingSkuList` |
| 4.4 | `/api/v2/automation/toshopee/inventory/racktransfer/sync_picking_sku_list` | `shopee.inventory.racktransfer.syncPickingSkuList` |
| 4.5 | `/api/v2/automation/toshopee/inventory/racktransfer/handle_diff_stock` | `shopee.inventory.racktransfer.handleDiffStock` |
Inside `ShopeeInboundApiHandler`, route to existing `wms-transfer` / `wms-inventory` services first. Do not add a separate WES controller for these four endpoints. Add new service methods only when existing transfer/inventory APIs cannot express the protocol action.
- [ ] **Step 4: Add 4.6-4.7 WMS-to-Vendor rack-transfer interface endpoints only**
Use internal `edi-shopee` endpoints for:
| Section | Internal endpoint | Shopee path |
| --- | --- | --- |
| 4.6 | `/internal/shopee/automation/rackTransfer/queryPickingList` | `/api/v2/automation/tovendor/inventory/racktransfer/query_picking_list` |
| 4.7 | `/internal/shopee/automation/rackTransfer/updateRtOrder` | `/api/v2/automation/tovendor/inventory/racktransfer/update_rt_order` |
Do not bind these endpoints to WES对账任务、移库取消、优先级更新、人工操作 or any other business trigger in this phase.
Keep the `4.1` internal endpoint:
Use the same internal client pattern as wave-number and wave-bind calls:
```text
POST {ediShopeeBaseUrl}/internal/shopee/automation/rackTransfer/createRtOrder
```
`edi-shopee` then calls Shopee:
```text
POST /api/v2/automation/tovendor/inventory/racktransfer/create_rt_order
```
Failure handling:
- Preserve Shopee `retcode` and message in the EDI call log.
- Do not finalize retry/non-retry mapping in this phase.
- Use existing transport failure handling for timeout/network errors only.
- [ ] **Step 5: Add 5.1 and 5.5 Vendor-to-WES basic handlers**
Expose Shopee-facing `edi-shopee` endpoints and forward to WES:
| Section | Path | WES api |
| --- | --- | --- |
| 5.1 | `/api/v2/automation/toshopee/basic/user/query_user_exist` | `shopee.basic.user.queryUserExist` |
| 5.5 | `/api/v2/automation/toshopee/basic/sku/query_sku_detail` | `shopee.basic.sku.querySkuDetail` |
Reuse existing WES user and SKU services. Do not create duplicate user/SKU lookup SQL if a service already exists.
- [ ] **Step 6: Add 5.2-5.4 and 5.6-5.8 scheduled WMS-to-Vendor basic calls**
Confirmed trigger source: scheduled jobs. Each WES-to-Vendor basic-data sync uses this three-step internal flow:
```text
<basic api>.confirm -> <basic api>.get -> <basic api>.uploadStatus
```
Use this pattern for the WES-to-Vendor basic-data interfaces. `5.5` remains a Vendor-to-WES query handled in Step 5.
| Section | Step | Internal endpoint | Shopee path |
| --- | --- | --- | --- |
| 5.2 | `.confirm` | `/internal/shopee/automation/basic/inventory/batchStatusUpdate.confirm` | `/api/v2/automation/tovendor/basic/inventory/batch_status_update` |
| 5.2 | `.get` | `/internal/shopee/automation/basic/inventory/batchStatusUpdate.get` | `/api/v2/automation/tovendor/basic/inventory/batch_status_update` |
| 5.2 | `.uploadStatus` | `/internal/shopee/automation/basic/inventory/batchStatusUpdate.uploadStatus` | `/api/v2/automation/tovendor/basic/inventory/batch_status_update` |
| 5.3 | `.confirm` | `/internal/shopee/automation/basic/inventory/querySkuBatchInventory.confirm` | `/api/v2/automation/tovendor/basic/inventory/query_sku_batch_inventory` |
| 5.3 | `.get` | `/internal/shopee/automation/basic/inventory/querySkuBatchInventory.get` | `/api/v2/automation/tovendor/basic/inventory/query_sku_batch_inventory` |
| 5.3 | `.uploadStatus` | `/internal/shopee/automation/basic/inventory/querySkuBatchInventory.uploadStatus` | `/api/v2/automation/tovendor/basic/inventory/query_sku_batch_inventory` |
| 5.4 | `.confirm` | `/internal/shopee/automation/basic/inventory/syncReconciliationResult.confirm` | `/api/v2/automation/tovendor/basic/inventory/sync_reconciliation_result` |
| 5.4 | `.get` | `/internal/shopee/automation/basic/inventory/syncReconciliationResult.get` | `/api/v2/automation/tovendor/basic/inventory/sync_reconciliation_result` |
| 5.4 | `.uploadStatus` | `/internal/shopee/automation/basic/inventory/syncReconciliationResult.uploadStatus` | `/api/v2/automation/tovendor/basic/inventory/sync_reconciliation_result` |
| 5.6 | `.confirm` | `/internal/shopee/automation/basic/sku/syncSkuInfo.confirm` | `/api/v2/automation/tovendor/basic/sku/sync_sku_info` |
| 5.6 | `.get` | `/internal/shopee/automation/basic/sku/syncSkuInfo.get` | `/api/v2/automation/tovendor/basic/sku/sync_sku_info` |
| 5.6 | `.uploadStatus` | `/internal/shopee/automation/basic/sku/syncSkuInfo.uploadStatus` | `/api/v2/automation/tovendor/basic/sku/sync_sku_info` |
| 5.7 | `.confirm` | `/internal/shopee/automation/basic/robot/queryAgvStatus.confirm` | `/api/v2/automation/tovendor/basic/robot/query_agv_status` |
| 5.7 | `.get` | `/internal/shopee/automation/basic/robot/queryAgvStatus.get` | `/api/v2/automation/tovendor/basic/robot/query_agv_status` |
| 5.7 | `.uploadStatus` | `/internal/shopee/automation/basic/robot/queryAgvStatus.uploadStatus` | `/api/v2/automation/tovendor/basic/robot/query_agv_status` |
| 5.8 | `.confirm` | `/internal/shopee/automation/basic/inventory/queryVendorUnitList.confirm` | `/api/v2/automation/tovendor/basic/inventory/query_vendor_unit_list` |
| 5.8 | `.get` | `/internal/shopee/automation/basic/inventory/queryVendorUnitList.get` | `/api/v2/automation/tovendor/basic/inventory/query_vendor_unit_list` |
| 5.8 | `.uploadStatus` | `/internal/shopee/automation/basic/inventory/queryVendorUnitList.uploadStatus` | `/api/v2/automation/tovendor/basic/inventory/query_vendor_unit_list` |
The previous one-call internal endpoint names are retained only as conceptual API names, not as the scheduled-job contract:
| Section | Internal endpoint | Shopee path |
| --- | --- | --- |
| 5.2 | `/internal/shopee/automation/basic/inventory/batchStatusUpdate` | `/api/v2/automation/tovendor/basic/inventory/batch_status_update` |
| 5.3 | `/internal/shopee/automation/basic/inventory/querySkuBatchInventory` | `/api/v2/automation/tovendor/basic/inventory/query_sku_batch_inventory` |
| 5.4 | `/internal/shopee/automation/basic/inventory/syncReconciliationResult` | `/api/v2/automation/tovendor/basic/inventory/sync_reconciliation_result` |
| 5.6 | `/internal/shopee/automation/basic/sku/syncSkuInfo` | `/api/v2/automation/tovendor/basic/sku/sync_sku_info` |
| 5.7 | `/internal/shopee/automation/basic/robot/queryAgvStatus` | `/api/v2/automation/tovendor/basic/robot/query_agv_status` |
| 5.8 | `/internal/shopee/automation/basic/inventory/queryVendorUnitList` | `/api/v2/automation/tovendor/basic/inventory/query_vendor_unit_list` |
These calls stay in `edi-shopee`. Scheduled jobs call `.confirm`, `.get`, then `.uploadStatus`; do not bind them to ad hoc WES business events in this phase.
- [ ] **Step 7: Compile transfer, API, inventory, and edi-shopee modules**
Run:
```powershell
cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-transfer:compileGroovy :wms-api:compileGroovy --no-problems-report
cd C:\work\gitlab\shopee\edi-shopee
.\gradlew compileGroovy
```
### Task 9: Add Sales Order and Demand Pool Interfaces
**Files:**
- Modify DTO file from Task 1.
- Modify `ShopeeInboundApiHandler.groovy`.
- Modify `C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\shopee\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:
```powershell
cd C:\work\gitlab\shopee\wes-loghub
.\gradlew :wms-api:compileGroovy :wms-wave:compileGroovy --no-problems-report
```
### Task 10: Error Code and Response Contract Placeholder
**Files:**
- Modify: `ShopeeInboundApiHandler.groovy`
- Modify: `C:\work\gitlab\shopee\wes-loghub\wms-api\src\main\groovy\com\ittx\wms\api\service\platform\shopee\ShopeeOutboundShipmentMappingService.groovy`
- [ ] **Step 1: Keep retcode mapping as a placeholder**
WES standard `ResponseMessage` is acceptable inside current sync API. Final mapping from WES errors to Shopee protocol `retcode` is not decided yet and must not be implemented in this phase.
Boundary adapter behavior for now:
```groovy
[
retcode: rsp.hasError() ? -1 : 0,
message: rsp.hasError() ? sanitize(rsp.message) : 'success',
data: rsp.data ?: [:]
]
```
Keep original WES error code/message in logs for later retcode mapping. Do not change `ResponseMessage` globally.
- [ ] **Step 2: Defer retry mapping**
Do not create a retry table or classify business `retcode` values yet.
Only transport-level failures such as timeout, connection refused, and upstream unavailable use existing retry behavior. Business retcode retry rules stay pending until the final error-code mapping is confirmed.
### 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:
```powershell
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:
```powershell
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:
```powershell
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-5.8 smoke tests**
Trigger or post representative `edi-shopee` endpoints from each added module.
4.1 WMS-to-Vendor rack-transfer body:
```powershell
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.
Vendor-to-WES samples:
```powershell
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:9210/api/v2/automation/toshopee/inventory/racktransfer/occupy_device' `
-ContentType 'application/json' `
-Headers @{ Authorization = '<valid-shopee-jwt>' } `
-Body '<4.2 json body from protocol>'
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:9210/api/v2/automation/toshopee/basic/user/query_user_exist' `
-ContentType 'application/json' `
-Headers @{ Authorization = '<valid-shopee-jwt>' } `
-Body '<5.1 json body from protocol>'
```
WMS-to-Vendor basic-data samples:
```powershell
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:9210/internal/shopee/automation/basic/sku/syncSkuInfo.confirm' `
-ContentType 'application/json' `
-Body '<5.6 confirm json body>'
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:9210/internal/shopee/automation/basic/sku/syncSkuInfo.get' `
-ContentType 'application/json' `
-Body '<5.6 get json body from protocol>'
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:9210/internal/shopee/automation/basic/sku/syncSkuInfo.uploadStatus' `
-ContentType 'application/json' `
-Body '<5.6 uploadStatus json body>'
```
- [ ] **Step 5: Manual internal WES smoke tests**
Use the WES sync API only as an internal verification target:
```powershell
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, all concrete interfaces from `3.1`-`3.4`, `4.1`-`4.7`, and `5.1`-`5.8`, JWT ownership in `edi-shopee`, and verification.
- Scope control: Excludes inbound module interfaces; includes outbound, inventory/rack-transfer, and basic-data interfaces from this protocol file; Shopee extension fields do not add WES entity columns and are persisted through dictionary group types.
- 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 外部入口。