fix(t238): accept ERP minute timestamps
This commit is contained in:
@@ -1260,7 +1260,7 @@ func (staticFreightSource) QueryOrder(
|
|||||||
string,
|
string,
|
||||||
) (domain.FreightSourceBatch, error) {
|
) (domain.FreightSourceBatch, error) {
|
||||||
shop := "测试店铺"
|
shop := "测试店铺"
|
||||||
created := "2026-07-28 08:00:00"
|
created := "2026-07-28 08:00"
|
||||||
quantityOne := 1
|
quantityOne := 1
|
||||||
quantityTwo := 2
|
quantityTwo := 2
|
||||||
return domain.FreightSourceBatch{
|
return domain.FreightSourceBatch{
|
||||||
|
|||||||
@@ -924,6 +924,8 @@ func parseERPTime(value *string) (*time.Time, error) {
|
|||||||
time.RFC3339Nano,
|
time.RFC3339Nano,
|
||||||
"2006-01-02 15:04:05",
|
"2006-01-02 15:04:05",
|
||||||
"2006-01-02T15:04:05",
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02 15:04",
|
||||||
|
"2006-01-02T15:04",
|
||||||
}
|
}
|
||||||
for _, layout := range layouts {
|
for _, layout := range layouts {
|
||||||
var parsed time.Time
|
var parsed time.Time
|
||||||
|
|||||||
@@ -56,6 +56,76 @@ func TestFreightNormalizationRejectsConflictingIdentityAndInvalidTime(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseERPTimeAcceptsConfirmedMinuteAndSecondPrecision(t *testing.T) {
|
||||||
|
shanghaiExpected := time.Date(2026, 7, 29, 4, 34, 0, 0, time.UTC)
|
||||||
|
shanghaiSecondExpected := time.Date(
|
||||||
|
2026,
|
||||||
|
7,
|
||||||
|
29,
|
||||||
|
4,
|
||||||
|
34,
|
||||||
|
56,
|
||||||
|
0,
|
||||||
|
time.UTC,
|
||||||
|
)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
expected time.Time
|
||||||
|
}{
|
||||||
|
{"space minute", "2026-07-29 12:34", shanghaiExpected},
|
||||||
|
{"T minute", "2026-07-29T12:34", shanghaiExpected},
|
||||||
|
{"space second", "2026-07-29 12:34:56", shanghaiSecondExpected},
|
||||||
|
{"T second", "2026-07-29T12:34:56", shanghaiSecondExpected},
|
||||||
|
{
|
||||||
|
"RFC3339",
|
||||||
|
"2026-07-29T12:34:56+08:00",
|
||||||
|
shanghaiSecondExpected,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
actual, err := parseERPTime(&testCase.value)
|
||||||
|
if err != nil || actual == nil || !actual.Equal(testCase.expected) {
|
||||||
|
t.Fatalf(
|
||||||
|
"parseERPTime(%q) = %v, %v; want %v",
|
||||||
|
testCase.value,
|
||||||
|
actual,
|
||||||
|
err,
|
||||||
|
testCase.expected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseERPTimeRejectsUnknownMinuteFormats(t *testing.T) {
|
||||||
|
for _, value := range []string{
|
||||||
|
"2026-02-30 12:34",
|
||||||
|
"2026-07-29 12",
|
||||||
|
"2026-07-29 12:34 extra",
|
||||||
|
"29/07/2026 12:34",
|
||||||
|
} {
|
||||||
|
if actual, err := parseERPTime(&value); err == nil || actual != nil {
|
||||||
|
t.Fatalf("parseERPTime(%q) = %v, %v", value, actual, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFreightNormalizationAcceptsDetailMinutePrecisionTime(t *testing.T) {
|
||||||
|
source := validFreightSource()
|
||||||
|
value := "2026-07-29 12:34"
|
||||||
|
source.Orders[0].SourceCreatedAt = &value
|
||||||
|
service := &FreightService{ids: &sequenceIDs{}}
|
||||||
|
result, err := service.normalize(source)
|
||||||
|
expected := time.Date(2026, 7, 29, 4, 34, 0, 0, time.UTC)
|
||||||
|
if err != nil || len(result.Orders) != 1 ||
|
||||||
|
result.Orders[0].SourceCreatedAt == nil ||
|
||||||
|
!result.Orders[0].SourceCreatedAt.Equal(expected) {
|
||||||
|
t.Fatalf("normalize minute time = %+v, %v", result.Orders, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFreightSourceErrorCodesAreSourceNeutral(t *testing.T) {
|
func TestFreightSourceErrorCodesAreSourceNeutral(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
err error
|
err error
|
||||||
|
|||||||
@@ -298,6 +298,9 @@ T-222 返回货运头、全部当前商品明细和 revision/hash 状态。T-223
|
|||||||
T-227 的 Go source 使用当前内存会话,按 `listTotal -> list 分页 -> listByStock` 查询;
|
T-227 的 Go source 使用当前内存会话,按 `listTotal -> list 分页 -> listByStock` 查询;
|
||||||
完整单号由请求同步调用,日期范围由后台 worker 调用。每次先校验会话;列表最多 100 条、
|
完整单号由请求同步调用,日期范围由后台 worker 调用。每次先校验会话;列表最多 100 条、
|
||||||
每页 20 条,详情每批最多 100 个外部 stock ID。
|
每页 20 条,详情每批最多 100 个外部 stock ID。
|
||||||
|
顺运宝详情 `created` 已确认可能只有分钟精度;无时区的
|
||||||
|
`yyyy-MM-dd HH:mm[:ss]` 或 `yyyy-MM-ddTHH:mm[:ss]` 均严格按 Asia/Shanghai 解释并转 UTC,
|
||||||
|
RFC3339 保留其显式时区。其他未知时间格式继续返回 `ERP_RESPONSE_INVALID`。
|
||||||
未配置、未登录、找不到货运单、响应协议错误和暂时不可用分别落为
|
未配置、未登录、找不到货运单、响应协议错误和暂时不可用分别落为
|
||||||
`ERP_NOT_CONFIGURED`、`ERP_SESSION_REQUIRED`、`ERP_FREIGHT_NOT_FOUND`、
|
`ERP_NOT_CONFIGURED`、`ERP_SESSION_REQUIRED`、`ERP_FREIGHT_NOT_FOUND`、
|
||||||
`ERP_RESPONSE_INVALID` 和 `ERP_UNAVAILABLE`,不返回 ERP 原始错误 body。
|
`ERP_RESPONSE_INVALID` 和 `ERP_UNAVAILABLE`,不返回 ERP 原始错误 body。
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
## 当前快照
|
## 当前快照
|
||||||
|
|
||||||
- 日期:2026-07-29
|
- 日期:2026-07-29
|
||||||
- 阶段:T-238 已规划顺运宝详情分钟精度时间兼容,待实现
|
- 阶段:T-238 已完成顺运宝详情分钟精度时间兼容
|
||||||
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-219
|
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-219
|
||||||
均按文档提交、实现提交的顺序纳入历史
|
均按文档提交、实现提交的顺序纳入历史
|
||||||
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
|
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
同步记录、货运头和全部明细,canonical hash 控制 revision,Admin 已有 `/freight`、
|
同步记录、货运头和全部明细,canonical hash 控制 revision,Admin 已有 `/freight`、
|
||||||
`/freight/import`、`/freight/{id}` 与对应 JSON API。T-237 已将完整单号改为 55 秒
|
`/freight/import`、`/freight/{id}` 与对应 JSON API。T-237 已将完整单号改为 55 秒
|
||||||
同步响应,成功后直接进入可见货运列表;超时/取消使用独立 cleanup context 写
|
同步响应,成功后直接进入可见货运列表;超时/取消使用独立 cleanup context 写
|
||||||
`FAILED`,日期范围仍后台异步。HTTP `WriteTimeout` 为 70 秒。T-238 已定位真实详情
|
`FAILED`,日期范围仍后台异步。HTTP `WriteTimeout` 为 70 秒。T-238 已兼容真实详情
|
||||||
`created` 为分钟精度,而当前 parser 只接受带秒格式,待兼容后复测。
|
`created` 的分钟精度,仍按 Asia/Shanghai 严格解析并转 UTC。
|
||||||
- ERP Go 迁移:T-225 已用脱敏 fixture 固定 `internal/platform/shunyunbao` 的 header、
|
- ERP Go 迁移:T-225 已用脱敏 fixture 固定 `internal/platform/shunyunbao` 的 header、
|
||||||
单号/日期查询、分页、详情批量和字段 allowlist,并使货运用例依赖来源中立错误。T-226
|
单号/日期查询、分页、详情批量和字段 allowlist,并使货运用例依赖来源中立错误。T-226
|
||||||
已增加受锁保护的 Go 内存 Cookie jar、验证码 ticket、登录和用户校验,以及 ADMIN 的
|
已增加受锁保护的 Go 内存 Cookie jar、验证码 ticket、登录和用户校验,以及 ADMIN 的
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
- Android Studio:未安装;`winget` 静默安装卡住后已终止,不阻塞命令行构建
|
- Android Studio:未安装;`winget` 静默安装卡住后已终止,不阻塞命令行构建
|
||||||
- 测试:T-219 Android Debug/Release 单元测试与构建和根 `init.ps1` 通过;
|
- 测试:T-219 Android Debug/Release 单元测试与构建和根 `init.ps1` 通过;
|
||||||
Debug APK `1.4.16 (21)` 已覆盖安装到 PKG110
|
Debug APK `1.4.16 (21)` 已覆盖安装到 PKG110
|
||||||
- 后端测试:T-226 至 T-237 已运行 `go test ./...`、`go test -race ./...`、`go vet ./...`
|
- 后端测试:T-226 至 T-238 已运行 `go test ./...`、`go test -race ./...`、`go vet ./...`
|
||||||
和三个 Go 入口构建;T-227 增加 Go source 的伪 ERP 会话预检、完整单号、日期分页去重、
|
和三个 Go 入口构建;T-227 增加 Go source 的伪 ERP 会话预检、完整单号、日期分页去重、
|
||||||
详情 allowlist 和稳定错误码覆盖;根 `init.ps1` 的 Android 测试/Debug APK 与 Go 标准
|
详情 allowlist 和稳定错误码覆盖;根 `init.ps1` 的 Android 测试/Debug APK 与 Go 标准
|
||||||
验证也通过,均未访问真实 ERP;
|
验证也通过,均未访问真实 ERP;
|
||||||
|
|||||||
+12
-7
@@ -4,7 +4,7 @@ title: 兼容顺运宝详情分钟精度时间
|
|||||||
phase: 2
|
phase: 2
|
||||||
deps:
|
deps:
|
||||||
- T-237
|
- T-237
|
||||||
status: PLANNED
|
status: DONE
|
||||||
created: 2026-07-29
|
created: 2026-07-29
|
||||||
context_ref: 8d5b88f
|
context_ref: 8d5b88f
|
||||||
work_branch: null
|
work_branch: null
|
||||||
@@ -14,6 +14,7 @@ write_paths:
|
|||||||
- docs/current-state.md
|
- docs/current-state.md
|
||||||
- backend-api/internal/usecase/freight_service.go
|
- backend-api/internal/usecase/freight_service.go
|
||||||
- backend-api/internal/usecase/freight_service_test.go
|
- backend-api/internal/usecase/freight_service_test.go
|
||||||
|
- backend-api/internal/transport/httpapi/admin_handlers_test.go
|
||||||
---
|
---
|
||||||
|
|
||||||
## 问题 / 背景
|
## 问题 / 背景
|
||||||
@@ -43,12 +44,12 @@ write_paths:
|
|||||||
|
|
||||||
## 验收要点
|
## 验收要点
|
||||||
|
|
||||||
- [ ] 详情 `yyyy-MM-dd HH:mm` 可规范化并按 Asia/Shanghai 得到正确 UTC 时间。
|
- [x] 详情 `yyyy-MM-dd HH:mm` 可规范化并按 Asia/Shanghai 得到正确 UTC 时间。
|
||||||
- [ ] `yyyy-MM-ddTHH:mm` 同样可解析;既有 RFC3339 和带秒格式不回归。
|
- [x] `yyyy-MM-ddTHH:mm` 同样可解析;既有 RFC3339 和带秒格式不回归。
|
||||||
- [ ] 非法日期和未知格式仍返回错误,不放宽为模糊解析。
|
- [x] 非法日期和未知格式仍返回错误,不放宽为模糊解析。
|
||||||
- [ ] 完整单号同步不再因已确认的 16 位详情时间返回 `ERP_RESPONSE_INVALID`。
|
- [x] 完整单号同步不再因已确认的 16 位详情时间返回 `ERP_RESPONSE_INVALID`。
|
||||||
- [ ] 两份真实响应 JSON 保持未跟踪,不进入 Git。
|
- [x] 两份真实响应 JSON 保持未跟踪,不进入 Git。
|
||||||
- [ ] 标准 Go 测试、race、vet 和三个入口构建通过。
|
- [x] 标准 Go 测试、race、vet 和三个入口构建通过。
|
||||||
|
|
||||||
## 边界
|
## 边界
|
||||||
|
|
||||||
@@ -62,3 +63,7 @@ write_paths:
|
|||||||
- 2026-07-29:创建任务。以只输出字段名、类型、长度和结构断言的方式检查两份本机响应;
|
- 2026-07-29:创建任务。以只输出字段名、类型、长度和结构断言的方式检查两份本机响应;
|
||||||
排除 envelope、list、detail id、details、数量、SKU 回退和字段长度,定位为详情
|
排除 envelope、list、detail id、details、数量、SKU 回退和字段长度,定位为详情
|
||||||
`created` 分钟精度与用例层严格 layout 不一致。
|
`created` 分钟精度与用例层严格 layout 不一致。
|
||||||
|
- 2026-07-29:严格增加空格/`T` 分隔的分钟 precision layout,保持 Asia/Shanghai 和 UTC
|
||||||
|
规范化语义。脱敏单元测试覆盖分钟、秒、RFC3339 和未知格式,Admin/Gin/SQLite 完整单号
|
||||||
|
集成 fixture 改用分钟精度并返回 `201/SUCCEEDED`。标准 Go 测试、race、vet 及三个入口
|
||||||
|
构建均通过;两份真实响应 JSON 未暂存。
|
||||||
|
|||||||
Reference in New Issue
Block a user