Commit Graph
56 Commits
Author SHA1 Message Date
QiuSWandClaude Opus 5 5b3c8df3bc feat(server): 同步虾皮完整颜色尺码并一次性匹配 (#290)
图搜采集成功并自动关联后,拉取该虾皮商品的完整颜色尺码清单合并进档案,
再触发一次匹配。此后同一虾皮商品的其它颜色 SYB 订单无需再采集、再点匹配。

- 新增 shopeespec 叶子包,凭据只从 GOAUTO_ERPGO_BASE_URL/GOAUTO_ERPGO_APIKEY
  读取;错误文本不携带 URL,避免 apikey 流进日志。
- 写入档案前经 sybspec.StripAnnotations 剥离 【...】,与 SYB 明细同源,
  否则 confirmedMappings 查不到、映射全部落空。
- 标记记录同步时对着哪个 PDD 商品(spec_sync_pdd_product_id),不是布尔值,
  重新关联时自动失效。
- 已完整同步的商品再次图搜时返回 IMAGE_SEARCH_SPEC_SYNCED。
- 迁移只加列不回填:既有档案仍是从 SYB 明细增量累积的,不能假装已同步。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-16 10:33:53 +08:00
QiuSWandClaude Opus 5 174d92a1c9 fix(server): SYB 结构过滤合并为一条,要求同时包含 - 和 # (#286)
结构过滤原为两条 char 规则(- 和 #),RuleSet.Match 逐条 Contains、
命中即返回,是或关系。线上最近一次完整同步(syb_sync_run 541)里
- 命中 384、# 命中 148,合计 532 条被过滤;因 - 先判且命中即返回,
那 148 条是含 # 但不含 - 的。

改为一条,keyword 存必需字符集合 -#,匹配要求集合中每个字符都出现:

- 同时含 - 和 # → 过滤
- 只含其一 → 放行
- 都不含 → 放行

已向用户说明每次同步至少多放行 148 条,用户确认后实施。

`[必须]` migrations/migrate.go 的种子必须同步改成一条。该函数每次启动
都会 FirstOrCreate,若继续播种旧的两条,下次重启就会把迁移
1789500000000 合并掉的行重新建回来,过滤静默退回或关系。已加
TestFreshDatabaseSeedsOneStructuralRule 锁住。

关键词过滤不变,仍是子串匹配,并加测试防止字符集合语义误用到关键词上。

连带效果:真实样本 300斤牛奶絲圓領#A057 只含 #,不再被结构过滤命中后
落到关键词规则上仍被过滤——#269 把关键词称为 forward safety net,这是
它第一次真的接住东西。realworld 用例的统计由 char=5/keyword=0/kept=6
变为 char=3/keyword=1/kept=7。

app/goauto/sybimport 的 12 个失败先于本次存在(#285),未新增。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-15 17:15:40 +08:00
QiuSW ffa5fc0710 fix: add idempotent image snapshot column migration (#278) 2026-09-14 18:02:22 +08:00
QiuSW 35b69fc25a fix: narrow image search migration (#278) 2026-09-14 16:31:07 +08:00
QiuSW b4dc7fc9ae feat: track image search links and preserve manual overrides (#279) 2026-09-14 16:08:55 +08:00
QiuSW baa9408a36 feat: allow image search collection task source (#278) 2026-09-14 16:05:01 +08:00
QiuSW aed295c8c1 fix(syb): reduce parse status to success or failed (#272) 2026-09-12 11:36:43 +08:00
QiuSWandClaude Opus 5 503b3cbbc5 fix(goauto): create syb_product_filter on existing databases #269
Three defects that only a real MySQL run could show.

The table had no version-local migration. Adding the model to
migrations.Migrate covers databases built from scratch, but every existing
one already has the older versions recorded in sys_migration, so that list
never runs again there. cmd/migrate's post-migration table check caught it:
"迁移后仍缺少表:syb_product_filter". The new version file creates the table
and seeds the same eight rules with FirstOrCreate on the normalized key, so
running both paths against one database cannot duplicate them.

Keyword and NormalizedKeyword were typed text, which MySQL refuses to put in
a key specification without a prefix length (Error 1170), so the unique index
could not be built at all. They are sized 200 now, matching how SYBShop sizes
its own matching key. SQLite accepts text in an index, which is why the whole
test suite passed while the real migration failed.

The 只看启用 filter is removed rather than kept: hiding a disabled structural
rule would conceal the one state on this page that matters most, since a
disabled "#" means roughly 65% of 档口 rows start entering the system again.
Keyword search stays.

Verified against the local MySQL database: migration applies, eight rules
present with traditional Chinese preserved (印花大學T normalizes to 印花大學t),
menu row created under 采采管理.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 17:00:11 +08:00
QiuSWandClaude Opus 5 cad9f6d185 fix(goauto): add the missing menu migration for SYB product filters #269
Registering a module in access.GoAutoModules() does not make its page
reachable on an existing database. sys_menu rows are written by the
versioned migration that first created them, and that version is already
recorded in sys_migration everywhere — it never runs again. Startup only
reconciles the casbin API matrix, not menus.

So on a fresh database the page appeared and on every real one it did not:
the table, the API and the route were all present with no way to navigate
there. This adds a migration that upserts the entry under 采采管理 beside
「SYB 店铺」 and binds it to both 管理员 and 采购员.

Verified by deleting the row from an already-migrated database and
re-running migrate: 新执行 1 个,跳过 42 个, row restored with both roles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
2026-09-11 16:49:56 +08:00
QiuSW 57b0f1595f feat(client-api): add scoped editable client keys (#237) 2026-09-07 16:20:46 +08:00
QiuSW 29ba16e4f9 feat(syb): allow purchaser manual sync (#236) 2026-09-07 15:03:31 +08:00
QiuSW bf58ad0005 merge: integrate main and purchase fixes for release (#234) 2026-09-07 11:58:24 +08:00
QiuSW bd386cfae4 fix(syb): 修正规格颜色尺码反序 (#216) 2026-09-04 19:33:35 +08:00
QiuSW 1ac50064bd fix(syb): 回填历史店铺标准化键 (#213) 2026-09-04 10:43:36 +08:00
QiuSW 13164adde0 fix(migrate): 补齐设备身份恢复字段 (#201) 2026-09-03 11:12:07 +08:00
QiuSW 81c1a7ead2 fix: add scheduled job execution history (#199) 2026-09-02 23:12:48 +08:00
QiuSW aa2ecc6ef7 feat: add scheduled SYB spec AI parsing (#198) 2026-09-02 21:33:14 +08:00
QiuSW b12460825c fix: prevent AI matching write timeout (#197) 2026-09-02 17:35:12 +08:00
QiuSW d7924619b6 feat: 增加蝦皮规格定时与手动自动匹配 (#195) 2026-09-01 23:41:11 +08:00
QiuSW d90bf66cc2 fix(auth): reload purchaser policies after reconcile (#161) 2026-08-31 14:23:12 +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 7c18159d70 fix(#156): reconcile purchaser permissions at startup 2026-08-29 16:30:23 +08:00
QiuSW cc326ebc6c feat(#155): reuse failed collection tasks by attempt 2026-08-29 16:05:37 +08:00
QiuSW afa29f1d86 fix(#149): add purchase match work item migration 2026-08-29 14:06:00 +08:00
QiuSW 11c407d582 feat(#148): match purchase specs asynchronously 2026-08-29 11:51:32 +08:00
QiuSW f84ea5df3b feat(#142): group GoAuto admin navigation 2026-08-29 09:44:06 +08:00
QiuSW 6f611a04ef feat(goauto): collect cropped color images (#133) 2026-08-28 23:31:23 +08:00
QiuSW 72f54f76ac feat(goauto): add configurable module menus (#137) 2026-08-28 21:21:40 +08:00
QiuSW 679f4691a4 feat(goauto): add stock purchase task type (#135) 2026-08-28 20:53:51 +08:00
QiuSW 33d4bb17ad feat(#130): collect replacement products from Agent 2026-08-28 18:20:24 +08:00
QiuSW 92385c4a8d feat(#131): activate product replacements safely 2026-08-28 17:56:17 +08:00
QiuSW 696e0bca52 feat(#129): add PDD product replacement model 2026-08-28 17:33:15 +08:00
QiuSW 7b5574506a fix(#125): reject duplicate migration versions 2026-08-28 10:41:04 +08:00
QiuSW 5c6655f706 feat(#119 #120 #121): implement SYB inner-code workflow 2026-08-28 10:24:15 +08:00
QiuSW 28c3cb5138 feat: collect current PDD page from Agent (#101) 2026-08-27 10:53:48 +08:00
QiuSW b885a4ef66 feat(agent): implement #90 task history 2026-08-26 09:26:38 +08:00
QiuSW ae9a9fef6f fix: add purchaser role permissions (#85) 2026-08-25 14:25:22 +08:00
QiuSW a2cc9a144f feat(#75): 增加 SYB 每小时自动同步 2026-08-24 16:02:16 +08:00
QiuSW 166adc6fa8 feat(#70): import cmautobuy product catalog 2026-08-23 23:59:12 +08:00
QiuSW 9490bddf8f feat(#67): add Shopee order snapshots to purchase tasks 2026-08-22 10:41:05 +08:00
QiuSW 6181d97f39 feat(#62): add AI specification matching settings 2026-08-21 17:04:49 +08:00
QiuSW 65ae41fd6f feat(#34): implement purchase task state machine 2026-08-20 17:22:52 +08:00
QiuSW 978f42806a feat(#33): define purchase task data and API contract 2026-08-20 16:49:43 +08:00
QiuSW badb78fa68 feat(#50): run SYB imports asynchronously 2026-08-20 15:24:50 +08:00
QiuSWandClaude Opus 5 cda37b4a60 feat(#49): add syb_shop model, migration and name normalization
Stage B step 1. The shop list decides which of a SYB account's shops get
imported.

Matching is by name, so the normalized form is the real key: whitespace
trimmed, full-width ASCII folded, letters lowercased. " ABC店 ", "ABC店"
and "abc店" are one shop. DisplayName is only ever shown to people, and
the unique index sits on the normalized column so a caller that skips
normalization cannot create rows that look identical on screen.

Interior whitespace is deliberately not collapsed: two shops differing
only there are still two shops, and merging them silently would be worse
than the duplicate this prevents.

Soft delete uses the DeletedFlag sentinel rather than a nullable
deleted_at in the unique index, which is silently inert because unique
indexes treat every NULL as distinct. Mutation-tested: dropping the
sentinel from the index makes the recreate-after-delete case fail.

The version-local migration file is included this time — the model alone
would never reach an existing database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:18:32 +08:00
QiuSWandClaude Opus 5 e6f533b71e fix(#48): add the missing version-local migration for syb_session
Adding SYBSession to migrations.Migrate was not enough. Schema reaches an
existing database only through a version-local file; with the previous
version already recorded in sys_migration, Migrate never re-ran and the
table simply never appeared. The unit tests build a fresh database every
time, so they stayed green while the real database was missing a table —
which surfaced as "Error 1146: Table 'goauto.syb_session' doesn't exist"
on the first import attempt.

server/.gitignore was hiding these files. go-admin ignores version-local
because it is where generated local migrations land, but every GoAuto
migration belongs in version control; the existing ones had been forced
in with `git add -f`. Un-ignoring *.go there also recovers four migrations
that were never committed at all — 1786700000000 through 1786700300000,
covering the base schema, device registration, heartbeat and collection
execution. A fresh clone could not have built a working database.

Guard the class of mistake rather than just this instance:

  - migrations.VerifyTables checks every model's table after migrating and
    names what is missing along with the fix.
  - The migrate command runs it, so the failure lands at migrate time
    instead of at the first request that needs the table.
  - initDB no longer discards migrateModel's error. Upstream had
    `_ = migrateModel()` followed by an unconditional "初始化成功", so a
    failed migration reported success and the launcher believed it.

Also records the two-step rule in Common-Changes: a new model needs both
the model registration and a new version file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 10:10:11 +08:00
QiuSWandClaude Opus 5 bbc5fdba05 fix(#48): replace per-migration noise with a summary, force UTF-8 console
The startup output was dominated by two upstream behaviours. Migrate
printed a bare count for every already-applied migration — a lone "1"
with no version, no explanation — through the standard logger, hence
stderr, which the launcher's `2>&1 |` renders as a red PowerShell error
block that looks like a failed migration. And the version-existence check
echoed one SELECT count(*) per version. Both replaced: a Warn-level
session for the check, and one summary line on stdout.

Console encoding needed more than [Console]::OutputEncoding: PowerShell
5.1 decodes child-process output by the console code page, so chcp 65001
goes with it.

The regression test capture was wrong twice and mutation testing caught
both. It captured only stdout while the bug wrote to stderr, and it
compared lines to "1" when the logger prefixes a timestamp, so the
assertion could never fail. It now captures the standard logger too and
matches with a pattern that allows the prefix — verified by reinstating
the bug and watching the test fail.

Not verified: the PowerShell edits, which need a Windows run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:53:56 +08:00
QiuSWandClaude Opus 5 8001c03abd fix(#48): make configuration resolution visible at startup and in errors
Diagnosing "credential not configured" required reading the source: the
local config layer was a silent no-op when it found no file, and the
import error named only environment variables even though config.yaml is
now the normal place to configure this — sending operators to look in a
file that was never the problem.

Startup now logs which config.yaml was loaded, or that none was found and
where it looked, followed by whether SYB credentials resolved and from
which layer. Presence only, never values: server logs get pasted into
tickets.

The import error now names the file that was actually consulted and
distinguishes "no config.yaml found" from "found it, but it has no syb
credentials" — two problems with different fixes that previously produced
identical text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:37:58 +08:00
QiuSWandClaude Opus 5 c86e98d0cb feat(#48): make one config work for both development and deployment
The repo already layered file defaults under environment overrides, on
both the backend (settings.yml < GOAUTO_*) and the frontend
(.env.production < process.env). What was missing was a translator for
production: config.yaml only ever existed for the PowerShell launchers,
so a packaged binary read none of it and had no database credentials
either — SYB was inheriting an existing gap, not creating one.

The server now reads config.yaml itself, between settings.yml and the
environment. Lookup is GOAUTO_CONFIG, then ./config.yaml, then beside the
executable, so a packaged binary works wherever it is started. An absent
file is not an error: containers supply everything through the
environment. Scalars are read by YAML type and coerced, so an unquoted
all-digit password cannot take startup down over a quoting detail.

This removed the need for a Read-SybConfig in PowerShell: the launcher
just hands over the path it already knows, rather than reimplementing a
YAML parser.

The server also serves the built frontend when dist is present, which is
what .env.production's empty VUE_APP_BASE_API already assumes. The
history fallback is restricted to non-API GETs, and is not installed at
all without dist, so development 404s stay 404s.

Precedence is mutation-tested: applying the local file after the
environment instead of before makes the layering test fail.

Not verified: the PowerShell change and any Windows deployment — both
need a run on the Windows side.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:28:45 +08:00