Commit Graph
54 Commits
Author SHA1 Message Date
QiuSW 4581ee5687 feat: capture private purchase failure diagnostics end to end (#364) 2026-10-08 15:05:32 +08:00
QiuSW bb3990e432 feat(#356): support pasted purchase order filters 2026-10-05 16:33:38 +08:00
QiuSW ddc18f5370 merge(#353,#354,#355): integrate shop filter, pagination and list timeout 2026-10-05 15:02:12 +08:00
QiuSW a24c2068ef fix(#355): extend SYB product list timeout to 60 seconds 2026-10-05 14:50:22 +08:00
QiuSW 6752b149d8 feat(#353): filter SYB products by managed shops 2026-10-05 11:54:32 +08:00
QiuSWandClaude Opus 5.5 baa20af530 fix(syb): #340 phase 3 — recompute race protection, fingerprint, wording
Merge origin/main (1e582cd, #350/#351 inner-code fixes) — no conflicts.

sybproductfilter/recompute.go:
1. Row-level protection in RecomputeExecute: the write phase is split out
   into writeRecomputeChanges(ctx, tx, planned), independently testable.
   For every planned change it takes the same clause.Locking{Strength:
   "UPDATE"} row lock purchase.Service.create and returnmatch's
   matchOneWithLock take, then re-checks under that lock: a purchase task
   or active return match that appeared after planning skips the row
   (counted), and a row already at its target mark is left alone. The
   execute response and audit log now report ACTUAL writes/skips
   (plan-time skips + write-time skips), not the initial plan.
2. Preview/execute binding: RecomputePreview returns a `fingerprint`
   (sha256 over the sorted id:direction:ruleId list). RecomputeExecute now
   requires it, recomputes the plan inside the same transaction and
   compares before writing; a mismatch returns RECOMPUTE_PREVIEW_STALE
   (HTTP 409, "数据或规则已变化,请重新预览后再执行") and writes nothing.
   Frontend passes the preview's fingerprint to execute and re-previews
   automatically on that error.
3. syb-product-filters List gains a per-rule `markedCount` (one grouped
   COUNT(*)...GROUP BY excluded_rule_id query, no N+1): the REAL current
   count of syb_product rows marked by that rule. The disable-structure-
   rule confirm dialog now quotes this instead of the stale lastHitCount
   sync snapshot; the 上次同步命中 column still shows lastHitCount.
4. recomputeChanges now selects only id/order_code/shopee_item_id/
   raw_json/pdd_purchase_excluded instead of full syb_product rows.

Wording (user-approved deviation from the prototype text):
- syb-sync-runs detail: 其中无需采购 N 条 -> 其中本次规则命中 N 条.
- syb-product-filters: 上次同步标记 reverted back to 上次同步命中 (both
  tables); the confirm-dialog text now cites markedCount, not lastHitCount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-29 15:34:20 +08:00
QiuSWandClaude Opus 5.5 316d5aa412 feat(syb): #340 phase 2 — merge main, recompute samples, frontend UI
Merge origin/main (through #345/e7c049d) into feat/340-syb-excluded-products.
Resolved conflicts in sybimport/service.go, handler.go, service_test.go
(kept #342's createdFrom/createdTo AND #340's purchaseType, all combined
with processStage), and took origin/main's syb-products/index.vue as the
base for the new UI work below. Renamed the migration version file from
1789801100000 to 1789801500000 (next free slot after main's highest,
1789801400000) — content unchanged, version comes from the filename.

Backend: sybproductfilter recompute preview now also returns up to 20
sample rows (order code, shopee item id, change direction, matched rule)
alongside the existing counts; execute stays count + audit-log only.

Frontend (feat/340 issue "## 设计证据"/"## 页面", prototype v1):
- SYB 订单商品页: 采购类型 filter (需 PDD 采购 default / 无需 PDD 采购 / 全部),
  处理阶段 gains 无需采购 (pdd_excluded); selecting 退货待确认/已用退货/无需采购
  auto-switches 采购类型 to 全部; pdd_excluded rows are tickable only for
  匹配退货 (never collection/purchase/AI-match/image-search); rows show the
  stage tag plus 规则:<kind> <keyword>; detail drawer shows 采购类型 and rule.
- SYB 过滤规则页: 跳过导入 -> 标记为无需 PDD 采购 wording, 上次同步命中 ->
  上次同步标记, rewritten scope note, admin-only 按当前规则重算 button with
  preview dialog (4 counts + up to 20 samples) -> confirm -> execute.
- SYB 同步记录: 结构/关键词过滤命中 -> 结构/关键词过滤标记 (see #340 point 8;
  the run-level "其中无需采购 N 条" count is derivable client-side from the
  existing char/keyword counts, no backend field added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-29 10:59:12 +08:00
QiuSW 4280115e11 feat: batch use pending return matches #349 2026-09-29 10:21:28 +08:00
QiuSW a4c7b5e97b feat: add pending return match batch cancellation #347 2026-09-29 09:40:16 +08:00
QiuSWandClaude Opus 5.5 f2c8307c17 feat(web): return-matching UI on SYB products and yeeke returns pages (#338)
SYB 订单商品页 (web/src/views/goauto/syb-products/index.vue):
- 「匹配退货」按钮,作用于勾选行中处于参与匹配阶段的商品(与服务端
  returnmatch.participatingStages 同一口径),canPurchase 门禁,忙碌时
  loading,无候选时禁用。
- 批量结果对话框:成功匹配/无候选/阶段不参与跳过/并发冲突跳过四类计数
  + 逐行原因表格。
- 处理阶段筛选与展示新增「退货待确认」「已用退货」两个选项/标签(复用
  sybimport 现有的、基于 purchase.ProcessStages 的阶段筛选机制,无需
  额外服务端改动——process_stage.go 上一次提交已让这两个阶段参与其中)。
- 新增「匹配到的退货商品」列(缩略图/退货订单号/规格/销毁截止),逐行
  「查看对比」「备注」「取消匹配」(后两者仅 canPurchase 可见)。
- 已有效匹配的行不可勾选(isReturnMatchCandidate 排除 return_pending/
  return_used 两个阶段)。
- 匹配详情对话框:SYB/yeeke 双方字段与归一化规格文字并排对比、备注
  编辑、操作日志列表、确认(仅 matched 状态可用)/取消匹配(matched
  或 confirmed 可用)按钮,取消前均有二次确认。

退货商品页 (web/src/views/goauto/yeeke-returns/index.vue):
- 新增「匹配状态」筛选(全部/未匹配/退货待确认/已用退货),对所有用户
  可见(纯只读筛选,不需要 canPurchase)。
- 新增「被占用 SYB 订单商品」列(订单号 + 当前阶段,点击跳转 SYB 商品
  页)。

New file web/src/api/goauto/return-matches.js: listReturnMatches/
getReturnMatch/batchMatchReturns/confirmReturnMatch/cancelReturnMatch/
remarkReturnMatch.

Backend support for the match column: returnmatch.Service.List now
returns ListItem (match + orderSn/image/variationName/destroyDeadline)
via one bounded join over the page's yeeke_return_item ids, so the SYB
product page's match column never needs a second request per row
(service.go, service_test.go: TestList_EnrichesWithYeekeReturnDisplayFields).

采购创建拦截的 UI 呈现沿用现有机制:ProcessStageReturnPending/
ProcessStageReturnUsed 让 isPurchaseCandidate(row) 天然为 false(与其余
非 purchase_ready 阶段的既有行为一致),「采购」按钮不显示,处理阶段列
展示服务端返回的拦截原因文案;未额外改动采购创建流程/UI。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-24 12:03:10 +08:00
QiuSWandClaude Opus 5.5 248eae01ee feat(yeeke): switch returns admin list to item-level rows with shop filter (#337)
Change GET /api/admin/v1/yeeke-returns to return one row per
yeeke_return_item joined with its parent package, with a placeholder
row (hasItem:false) for packages that have no items. Add
GET /yeeke-returns/shops (distinct sorted shop names) and
GET /yeeke-returns/items/:itemId (item + parent package + sibling
items) as static-prefixed routes registered before the /:packageId
wildcard so they are not swallowed by it. Shop filter is now an exact
match instead of a partial LIKE. Remove trackingNo/itemCount from the
list projection while keeping them on package detail.

Update the admin web list to item-level rows (reference image column,
店铺 dropdown sourced from /shops) and retitle the detail drawer to
"退货商品详情", switching it to the item-detail endpoint for real rows
and the existing package-detail endpoint for placeholder rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-23 16:37:56 +08:00
QiuSWandClaude Opus 5 ab292d6a17 feat(yeeke): add admin read-only return-package/sync-run modules (#337)
Add read-only server endpoints for yeeke return packages (paginated list
with order/tracking/shop/item/variation/claim-status/create-time filters,
plus package detail with items) and extend the existing sync-runs endpoint
with trigger/status/started-at filters, backward compatible. Split the
admin menu into two modules per the ticket's confirmed scope adjustment:
yeeke_returns (packages) and the new yeeke_sync_runs, both under the
采集采购 menu group, wired via a new sys_menu migration mirroring #237's
precedent. Add the corresponding Vue pages, API wrappers and Go tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-23 14:23:51 +08:00
QiuSW bb1a410e8a feat(#329): add device purchaser ownership 2026-09-21 09:59:00 +08:00
QiuSW e89de1a085 feat: queue and reconcile SYB purchase order numbers (#305) 2026-09-18 10:13:20 +08:00
QiuSWandClaude Opus 5 03647dd7dd feat(web): 采集任务页增加来源筛选与「取消未开始的任务」按钮 (#298)
页面混显三种来源却只有 goodsId 和状态两个筛选,批量取消时无法限定范围。

- 服务端 AdminList 增加 source 过滤,非法值报参数错误而非静默忽略。
- 前端增加来源筛选与来源列;状态补 cancelled(info 色,取消不是错误,不与
  failed 共用红色)。
- 「取消未开始的任务」按钮带实时数量,确认框列出将被取消的任务,并写明范围是
  整个筛选条件而非当前页。hasMore 时提示还有未处理的任务。

`[必须]` 取消范围包含 goodsId。少了这一维,按 goods_id 筛出两条、按钮却取消
三十几条——那正是当初放弃「两个固定按钮」、改用「筛选 + 一个按钮」想避免的事。
BatchCancel 的 goodsId 与 AdminList 用同一种匹配方式,否则两边范围会悄悄错开。

实施:sonnet 子代理;goodsId 范围一致性由复核补入。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 16:39:07 +08:00
QiuSW be491c7692 feat: expose image search batch API to web (#280) 2026-09-15 09:02:08 +08:00
QiuSW 652bf30f5a feat(goauto): add SYB product variation filters (#269) 2026-09-11 16:20:26 +08:00
QiuSW b1594dc7bd feat(syb): link latest device collection and match specs #253 2026-09-10 11:34:22 +08:00
QiuSW 9088e6b734 fix(shopee): bound matching waits and recover drawer loading #254 2026-09-10 09:37:02 +08:00
QiuSW 57b0f1595f feat(client-api): add scoped editable client keys (#237) 2026-09-07 16:20:46 +08:00
QiuSW bf58ad0005 merge: integrate main and purchase fixes for release (#234) 2026-09-07 11:58:24 +08:00
QiuSW 58e058f8ac feat: 一键匹配并确认蝦皮颜色尺码 (#194)
(cherry picked from commit 041cd8d03f)
2026-09-03 15:05:53 +08:00
QiuSW 379a83fe94 feat(device): 安全恢复原设备身份 (#201) 2026-09-03 10:41:20 +08:00
QiuSW 81c1a7ead2 fix: add scheduled job execution history (#199) 2026-09-02 23:12:48 +08:00
QiuSW d7924619b6 feat: 增加蝦皮规格定时与手动自动匹配 (#195) 2026-09-01 23:41:11 +08:00
QiuSW 041cd8d03f feat: 一键匹配并确认蝦皮颜色尺码 (#194) 2026-09-01 22:20:48 +08:00
QiuSW 3ef0f72ff9 feat: batch AI match SYB purchase specs (#188) 2026-09-01 12:13:33 +08:00
QiuSWandClaude Sonnet 5 5338dd0072 fix(web): give AI spec-suggestion calls a timeout above the AI provider's
request.js's shared axios instance times out every admin call at 10s,
but AI matching settings allow the server to wait up to 600s
(aimatching.maxTimeoutSecs) for a real chat/completions round trip.
For shopee product 49915704844 the size dimension had no deterministic
match and genuinely called the model; the frontend cut the connection
before the server replied, which the browser reports as a generic
network failure ("服务器连接异常") that has nothing to do with the
actual cause. Override the timeout on just these two calls instead of
the shared default, which stays tight for every other (fast) admin
request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkjpksKeizaRpYXfgD74Lf
2026-08-31 16:37:04 +08:00
QiuSWandClaude Sonnet 5 21b38c4444 feat(goauto): suggest color/size specs via AI, human-confirmed save (#172)
Add batch AI suggestion for Shopee color/size spec mapping: a
specContextVersion snapshot guards both the new suggest endpoints and
must be echoed back, short id binding keeps the model from ever
returning a value outside the server-supplied PDD candidates, and
suggestions only prefill the draft above AutoConfirmMinConfidence -
low-confidence answers show the reason but stay unapplied. Every
AI-sourced mapping is still written as pending by the existing
SetMapping rule and needs an explicit operator confirm, same as
before for manual edits.

Server: server/app/goauto/aimatching/suggest.go (batch provider call),
server/app/goauto/shopeeproduct/{context_version,ai_suggest}.go (spec
context version + suggest-colors/suggest-sizes), wired into
handler.go/router.go/service.go.
Web: shopee-products AI 匹配 button on both tabs, low-confidence rows
left for manual pick, pending AI mappings get an explicit 确认 action.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkjpksKeizaRpYXfgD74Lf
2026-08-31 16:18:42 +08:00
QiuSW 09d9d2227a feat(goauto): continue orders from PDD product detail (#161) 2026-08-31 11:36:31 +08:00
QiuSW f0f5f5bd14 feat(goauto): manage authenticated agent releases (#143) 2026-08-29 21:34:32 +08:00
QiuSW 148ba96521 feat(goauto): add managed purchase rules (#127) 2026-08-29 21:20:55 +08:00
QiuSW 11c407d582 feat(#148): match purchase specs asynchronously 2026-08-29 11:51:32 +08:00
QiuSW c9e9539de0 fix(#147): move purchase matching outside transaction 2026-08-29 11:09:48 +08:00
QiuSW 9d23b44811 fix(upload): preserve multipart boundaries (#139) 2026-08-29 00:01:37 +08:00
QiuSW 9916ed5ccf feat(web): add stock purchase workflow (#136) 2026-08-28 21:46:17 +08:00
QiuSW 941b7b11f3 feat(#122): add SYB inner-code admin workflow 2026-08-28 10:24:22 +08:00
QiuSW a2cc9a144f feat(#75): 增加 SYB 每小时自动同步 2026-08-24 16:02:16 +08:00
QiuSW 3a8ec2055e fix(#74): 增加 SYB 店铺联想筛选 2026-08-24 15:33:01 +08:00
QiuSW 476ca64616 fix(#72): wait for API and retry SYB load 2026-08-24 11:02:47 +08:00
QiuSW 67d8f32eaf feat(#69): add Shopee PDD specification mapping 2026-08-23 23:23:42 +08:00
QiuSW 6181d97f39 feat(#62): add AI specification matching settings 2026-08-21 17:04:49 +08:00
QiuSW 01d852011a feat(#58): batch retry failed purchase tasks 2026-08-21 14:20:40 +08:00
QiuSW 7e4f404265 feat(#44): create purchase tasks from SYB products 2026-08-21 10:17:31 +08:00
QiuSW c0c88bb43f feat(#35): add purchase task admin UI 2026-08-20 23:54:17 +08:00
QiuSW badb78fa68 feat(#50): run SYB imports asynchronously 2026-08-20 15:24:50 +08:00
QiuSW 6c7a7298e3 feat(#49): complete SYB shop management 2026-08-20 11:54:11 +08:00
QiuSWandClaude Opus 5 7edb4e7b9f feat(#48): add SYB sync orchestration, import endpoint and page entry
Stage 2. Completes the path from the SYB API to the archive.

sybimport.Sync pages a date range day by day and folds each detail line
through the existing idempotent ApplyDetail. The paging loop is driven by
listTotal, never by the list response's own total, which live data
confirmed is the page's row count rather than the filtered total. A day
whose completeness cannot be proven — a short page, a total that drifted
while paging, a detail response missing an order — stops the run instead
of reporting a partial import as a whole one. Rows already written stay:
they are idempotent on (order_code, detail_id), so a re-run overwrites.

sybimport.Connect reuses the cached session and only discards it when SYB
explicitly says it is invalid. Caching the numeric user id alongside the
cookies is required, not incidental: session validation calls
/am/user/get?id=, and a wrong id comes back as a business error rather
than a logout, so cookies alone can never be revalidated.

The import endpoint is single-flight and detached from the request
context, so closing the tab cannot abandon a half-finished range. It
reads only; no SYB write endpoint is reachable from GoAuto.

The page swaps its "no import entry" warning for a date-range dialog that
warns when the span is wide — a single week held over 7000 orders — and
reports partial progress when a run fails midway.

Verified: go build, go vet, go test ./... all pass. The drift check was
mutation-tested. Not verified: the MySQL migration for syb_session, and
any browser walkthrough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 16:56:00 +08:00
QiuSWandClaude Opus 5 03a56565ef feat(#41): add syb product admin page
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 14:49:12 +08:00
QiuSWandClaude Opus 5 40d2862c1a feat(#40): add shopee product admin page
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 13:53:51 +08:00