feat(barcode): 多条码管理(迁移 + GTIN 校验 + 后台/公开 API + 后台 UI) #5

Merged
lixu merged 3 commits from devin/1781934649-multi-barcode into main 2026-06-20 15:53:43 +08:00
Owner

背景

现有商品档案每个商品只有单一 gtin 字段,且当前入库的样本都是国外(欧洲)条码。
实际业务中同一件商品常有多个合法 GS1 条码:单品码(EAN-13/UPC)+ 整箱码(ITF-14/GTIN-14)、
不同规格/包装各自的码、进口品「原产国码 + 国内总代理 69x 码」、促销/捆绑装单独码等。
因此把「一个商品 → 多个条码」纳入数据模型。

按确认的两点约定:

  1. 只收 GS1 全球唯一码;店内码/变量重量码/优惠券码(20–29、02x、UPC 2/4/5、EAN-8 0/2 等)一律不收录。
  2. 一码命中多个商品时由后台提示去重(不静默覆盖)。

本 PR 已完成(后端 + 迁移 + 校验)

迁移 migrations/0007_product_barcode

CREATE TABLE product_barcode(
  id, product_id FK, gtin, gtin_type(EAN8|UPC|EAN13|ITF14|GTIN14),
  pack_level(each|case|pallet), region, is_primary, source_id, created_at);
CREATE UNIQUE INDEX ... ON product_barcode(gtin);                 -- 一码一品(全局唯一)
CREATE UNIQUE INDEX ... ON product_barcode(product_id) WHERE is_primary; -- 每品至多一主码
-- 回填:把每个商品现有 product.gtin 提升为主码

product.gtin 保留为「主码」的反范式镜像,与 is_primary 行保持同步,公开 API/列表/旧调用方零改动。

api/internal/gtin:GS1 GTIN-8/12/13/14 规范化——长度、mod-10 校验位、拒收非全球唯一号段;附单测(含中国 690 前缀通过、各类受限码被拒)。

公开只读 API(api/internal/store

  • GET /api/v1/products/barcode/{gtin}任一条码(主码或附码)都能命中同一商品。
  • 商品详情新增 barcodes 数组(含 type/pack_level/region/is_primary)。
  • 搜索 q 额外匹配任意条码。

后台 store 层(api/internal/adminstore

  • 商品详情含 barcodes
  • AddBarcode / DeleteBarcode(删主码自动改选新主码并同步 product.gtin)/ SetPrimaryBarcode
  • 条码命中其他商品 → 返回 ConflictError(带占用商品 id/名称),供后台「去重」提示。
  • 全程 audit_log 留痕(add_barcode / delete_barcode / set_primary_barcode)、来源 manual、重算质量分。

待办(按用户指示已暂停,未包含在本 PR)

  • 后台 handler 路由:POST/DELETE /api/products/{id}/barcodesPOST .../{barcodeID}/primary,及 409 去重响应。
  • 投稿/审核支持多条码(投稿时校验、审核通过去重写入)。
  • 前端:公开商品详情/投稿表单多条码 + 后台条码管理 UI。
  • 构建打包、部署到 goods.tangshasha.com、端到端验证。

验证

  • go build ./... 通过;go test ./internal/gtin/ 通过。
  • 暂未部署(待 UI/handler 完成并经你确认后再部署)。

更新:完成 API 接口 + 运营后台 UI + 公开站展示(本次提交)

此前分支仅含 store 层逻辑(迁移、GTIN 校验、AddBarcode/DeleteBarcode/SetPrimaryBarcode),缺少 HTTP 路由与前端,无法实际使用。现已补齐并端到端验证:

API(adminhandler)

  • POST /api/products/{id}/barcodes 新增条码
  • DELETE /api/products/{id}/barcodes/{barcodeID} 删除(主条码删除后自动改选其一为主)
  • POST /api/products/{id}/barcodes/{barcodeID}/primary 设为主条码(镜像到 product.gtin
  • 条码被其它商品占用 → 409(返回冲突商品 id/name 供运营去重);非法 GTIN → 400 invalid_gtin

运营后台(admin-frontend)

  • 商品详情页新增「条码」卡片:列出全部条码(主条码加星标)、按 类型/包装层级/地区 新增、设为主、删除;操作记入审计日志

公开站(public-frontend)

  • 商品详情新增「其他条码」展示;公开 API GET /products/barcode/{gtin} 已支持按任一条码命中(store 层)

验证:本地起 Postgres + 全量迁移 + admin/public 双服务,curl 跑通 新增/冲突409/非法400/设为主(镜像 product.gtin)/删除自动改选主,公开站按副条码命中;后台 UI 浏览器实测渲染与交互正常。Go build/vet/test 与前端 tsc 均通过。

## 背景 现有商品档案每个商品只有单一 `gtin` 字段,且当前入库的样本都是国外(欧洲)条码。 实际业务中同一件商品常有**多个合法 GS1 条码**:单品码(EAN-13/UPC)+ 整箱码(ITF-14/GTIN-14)、 不同规格/包装各自的码、进口品「原产国码 + 国内总代理 69x 码」、促销/捆绑装单独码等。 因此把「一个商品 → 多个条码」纳入数据模型。 按确认的两点约定: 1. **只收 GS1 全球唯一码**;店内码/变量重量码/优惠券码(20–29、02x、UPC 2/4/5、EAN-8 0/2 等)一律不收录。 2. **一码命中多个商品时由后台提示去重**(不静默覆盖)。 ## 本 PR 已完成(后端 + 迁移 + 校验) **迁移 `migrations/0007_product_barcode`** ```sql CREATE TABLE product_barcode( id, product_id FK, gtin, gtin_type(EAN8|UPC|EAN13|ITF14|GTIN14), pack_level(each|case|pallet), region, is_primary, source_id, created_at); CREATE UNIQUE INDEX ... ON product_barcode(gtin); -- 一码一品(全局唯一) CREATE UNIQUE INDEX ... ON product_barcode(product_id) WHERE is_primary; -- 每品至多一主码 -- 回填:把每个商品现有 product.gtin 提升为主码 ``` `product.gtin` 保留为「主码」的反范式镜像,与 `is_primary` 行保持同步,公开 API/列表/旧调用方零改动。 **`api/internal/gtin`**:GS1 GTIN-8/12/13/14 规范化——长度、mod-10 校验位、拒收非全球唯一号段;附单测(含中国 690 前缀通过、各类受限码被拒)。 **公开只读 API(`api/internal/store`)** - `GET /api/v1/products/barcode/{gtin}`:**任一条码**(主码或附码)都能命中同一商品。 - 商品详情新增 `barcodes` 数组(含 type/pack_level/region/is_primary)。 - 搜索 `q` 额外匹配任意条码。 **后台 store 层(`api/internal/adminstore`)** - 商品详情含 `barcodes`。 - `AddBarcode` / `DeleteBarcode`(删主码自动改选新主码并同步 `product.gtin`)/ `SetPrimaryBarcode`。 - 条码命中其他商品 → 返回 `ConflictError`(带占用商品 id/名称),供后台「去重」提示。 - 全程 `audit_log` 留痕(add_barcode / delete_barcode / set_primary_barcode)、来源 `manual`、重算质量分。 ## 待办(按用户指示已暂停,未包含在本 PR) - 后台 handler 路由:`POST/DELETE /api/products/{id}/barcodes`、`POST .../{barcodeID}/primary`,及 409 去重响应。 - 投稿/审核支持多条码(投稿时校验、审核通过去重写入)。 - 前端:公开商品详情/投稿表单多条码 + 后台条码管理 UI。 - 构建打包、部署到 goods.tangshasha.com、端到端验证。 ## 验证 - `go build ./...` 通过;`go test ./internal/gtin/` 通过。 - 暂未部署(待 UI/handler 完成并经你确认后再部署)。 --- ## 更新:完成 API 接口 + 运营后台 UI + 公开站展示(本次提交) 此前分支仅含 store 层逻辑(迁移、GTIN 校验、AddBarcode/DeleteBarcode/SetPrimaryBarcode),缺少 HTTP 路由与前端,无法实际使用。现已补齐并端到端验证: **API(adminhandler)** - `POST /api/products/{id}/barcodes` 新增条码 - `DELETE /api/products/{id}/barcodes/{barcodeID}` 删除(主条码删除后自动改选其一为主) - `POST /api/products/{id}/barcodes/{barcodeID}/primary` 设为主条码(镜像到 `product.gtin`) - 条码被其它商品占用 → `409`(返回冲突商品 id/name 供运营去重);非法 GTIN → `400 invalid_gtin` **运营后台(admin-frontend)** - 商品详情页新增「条码」卡片:列出全部条码(主条码加星标)、按 类型/包装层级/地区 新增、设为主、删除;操作记入审计日志 **公开站(public-frontend)** - 商品详情新增「其他条码」展示;公开 API `GET /products/barcode/{gtin}` 已支持按任一条码命中(store 层) **验证**:本地起 Postgres + 全量迁移 + admin/public 双服务,curl 跑通 新增/冲突409/非法400/设为主(镜像 product.gtin)/删除自动改选主,公开站按副条码命中;后台 UI 浏览器实测渲染与交互正常。Go `build/vet/test` 与前端 `tsc` 均通过。
lixu added 1 commit 2026-06-20 13:54:50 +08:00
feat(barcode): 多条码管理(后端+迁移+GTIN校验)WIP
CI / Go (api) (pull_request) Failing after 18s
CI / Python (ingestion) (pull_request) Successful in 7s
CI / Migrations (postgres) (pull_request) Failing after 22s
1d5f775d33
- 迁移 0007: 新增 product_barcode 表(一品多码),回填旧 product.gtin 为主码,
  全局唯一索引保证「一码一品」,每品至多一个主码
- internal/gtin: GS1 GTIN-8/12/13/14 校验(校验位 + 拒收店内码/变量重量码/优惠券码)
- 公开只读 API: 任一条码命中商品、详情返回 barcodes、搜索匹配条码
- adminstore: 商品详情含 barcodes;新增 AddBarcode/DeleteBarcode/SetPrimaryBarcode,
  一码命中其他商品返回 ConflictError 供后台去重

待办(按用户要求暂停): 后台 handler 路由、投稿/审核多条码、前后端 UI

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
lixu added 1 commit 2026-06-20 15:06:16 +08:00
feat(barcode): admin barcode CRUD endpoints + UI; show extra barcodes publicly
CI / Python (ingestion) (pull_request) Successful in 10s
CI / Go (api) (pull_request) Failing after 15s
CI / Migrations (postgres) (pull_request) Failing after 11m35s
98b5f1575d
Wire the multi-barcode store layer to HTTP and the operator console:

- adminhandler: add POST /products/{id}/barcodes, DELETE
  /products/{id}/barcodes/{barcodeID}, and POST .../primary. A barcode
  owned by another product returns 409 with the conflicting product
  (gtin/product_id/product_name); an invalid GTIN returns 400.
- admin-frontend: BarcodesCard on the product detail page lists all
  barcodes (primary starred), adds with type/pack-level/region, sets
  primary, and deletes; audit labels for the new actions.
- public-frontend: product detail surfaces non-primary barcodes so a
  case/region code resolves and is visible to consumers.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
lixu changed title from feat(barcode): 多条码管理(后端+迁移+GTIN校验,WIP) to feat(barcode): 多条码管理(迁移 + GTIN 校验 + 后台/公开 API + 后台 UI) 2026-06-20 15:06:45 +08:00
lixu added 1 commit 2026-06-20 15:33:13 +08:00
style(api): gofmt gtin_test.go
CI / Python (ingestion) (pull_request) Successful in 8s
CI / Migrations (postgres) (pull_request) Successful in 15s
CI / Go (api) (pull_request) Successful in 31s
5b9338175e
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
lixu merged commit e34e007f28 into main 2026-06-20 15:53:43 +08:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lixu/goods#5