diff --git a/CLAUDE.md b/CLAUDE.md index 831c9f7..8bb66e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -249,7 +249,7 @@ cd client && flutter test ### 响应式 / 移动端适配 - 断点判定统一用 `context.isMobile`(`client/lib/core/responsive/responsive.dart`,宽度 < 600 为窄屏/手机),**禁止**在各处散落 `MediaQuery.width < 600` 这类魔法数 -- **列表屏**:用 `DataTableCard` 时必须同时传 `mobileCards`(窄屏渲染卡片流),卡片复用 `MobileListCard` / `MobileCardField`(`widgets/mobile_list_card.dart`);宽屏仍走原表格,行为不变 +- **列表屏**:统一用 `DsTable`(`widgets/ds/ds_table.dart`,真相源表格:toolbar+列+pager);窄屏卡片流复用 `MobileListCard` / `MobileCardField`(`widgets/mobile_list_card.dart`)。旧 `DataTableCard` 已删除(2026-07-03),勿再引用 - **弹窗固定宽度**:一律用 `context.dialogWidth(X)`(≤ 屏宽 92%),**禁止**裸写 `width: <固定值>` 导致窄屏溢出 - **导航**:窄屏(手机)用 Drawer 抽屉(`app_shell.dart`),宽屏保持侧边栏;新增页面无需单独处理,挂在 shell 下即可 - 平台判断仍遵守上面「前端平台判断」规则(`kIsWeb` 先于 `dart:io`) diff --git a/docs/db-schema.html b/docs/db-schema.html new file mode 100644 index 0000000..55cdfdd --- /dev/null +++ b/docs/db-schema.html @@ -0,0 +1,684 @@ + + + + + +数据库 Schema — 酒库管理系统 + + + +
+ +
+

数据库 Schema · 酒库管理系统

+
MySQL 8.0+ · 多租户(所有业务表含 shop_id 隔离)· 启动时 GORM AutoMigrate
+
31 张表
365 个字段
来源:backend/schema/schema.sql + model AutoMigrate
+
+ PK 主键 + UNIQUE 唯一键 + INDEX 普通索引 + FULLTEXT 全文索引 + 租户 多租户隔离列 +
+

租户 · 用户 · 安全

多租户根表与登录/会话/审计。所有业务表的 shop_id 都指向 shops。

+

shops 门店(租户)

+ + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
nameVARCHAR(100)门店名称
codeVARCHAR(50)门店编号(唯一)
addressVARCHAR(255)NULL
phoneVARCHAR(30)NULL
manager_nameVARCHAR(50)NULL负责人
descriptionTEXTNULL店铺简介
logo_urlVARCHAR(500)门店 logo URL
business_licenseVARCHAR(500)NULL营业执照照片URL
shop_photosJSONNULL门店照片URL数组
custom_fieldsJSONNULL扩展字段
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
UNIQUE uk_code (`code`)
INDEX idx_deleted_at (`deleted_at`)
+
+
+

users 用户

+ + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
usernameVARCHAR(50)
password_hashVARCHAR(255)bcrypt
real_nameVARCHAR(50)NULL
phoneVARCHAR(30)NULL
roleENUM('admin','operator','readonly','superadmin')operator
is_activeTINYINT(1)1
last_login_atDATETIMENULL最近登录时间
custom_fieldsJSONNULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
UNIQUE uk_shop_username (`shop_id`, `username`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_deleted_at (`deleted_at`)
+
+
+

user_sessions 登录会话

+ + + + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
user_idBIGINT UNSIGNED
sidVARCHAR(64)嵌入 JWT 的会话标识
device_idVARCHAR(255)NULL
device_nameVARCHAR(255)NULL
platformVARCHAR(50)NULLwindows|macos|linux|android|ios|web
platform_classVARCHAR(20)NULLdesktop|mobile|web
ipVARCHAR(64)NULL
user_agentVARCHAR(512)NULL
refresh_jtiVARCHAR(64)NULL当前有效 refresh token 的 jti,用于轮换+重用检测
created_atDATETIMECURRENT_TIMESTAMP
last_seen_atDATETIMECURRENT_TIMESTAMP
revoked_atDATETIMENULL
revoked_reasonVARCHAR(30)NULLkicked|logout|admin|disabled|reuse|pwd_reset
revoked_byBIGINT UNSIGNEDNULL吊销操作人 user_id;系统/自助吊销为 NULL
refresh_exp_atDATETIMENULL
+
PRIMARY (`id`)
UNIQUE uk_session_sid (`sid`)
INDEX idx_session_shop_user (`shop_id`, `user_id`)
INDEX idx_session_class (`platform_class`)
INDEX idx_session_revoked (`revoked_at`)
+
+
+

login_attempts 登录尝试审计

+ + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_codeVARCHAR(64)NULL
usernameVARCHAR(50)NULL
ipVARCHAR(64)NULL
user_agentVARCHAR(512)NULL
successTINYINT(1)0
reasonVARCHAR(40)NULLinvalid_shop|invalid_user|bad_password|locked|inactive|platform_not_allowed
created_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
INDEX idx_attempt_user (`username`, `created_at`)
INDEX idx_attempt_ip (`ip`, `created_at`)
+

授权 · 兑换券

门店授权与时长兑换券码池(ed25519/HMAC 已退役,改时长兑换券模型)。

+

licenses 许可证

+ + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
license_keyVARCHAR(768)Ed25519 signed token
device_idVARCHAR(255)NULLdeprecated: use license_devices
typeENUM('trial','monthly','annual','lifetime')trial
tierVARCHAR(32)standard权益档位钩子(pro/max-like),当前默认 standard
expires_atDATETIMENULLNULL=永久
is_activeTINYINT(1)1
max_devicesINT3最大绑定设备数
featuresJSONNULL功能开关 {"finance":true}
activated_atDATETIMENULLdeprecated
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
UNIQUE uk_license_key (`license_key`)
INDEX idx_shop_id (`shop_id`)
+
+
+

license_devices 许可证设备绑定

+ + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
license_idBIGINT UNSIGNED
shop_id 租户BIGINT UNSIGNED
device_idVARCHAR(255)
device_nameVARCHAR(255)NULL
platformVARCHAR(50)NULLwindows|macos|android|ios|web
activated_atDATETIMECURRENT_TIMESTAMP
last_seen_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
UNIQUE uk_license_device (`license_id`, `device_id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_license_id (`license_id`)
+
+
+

license_codes 兑换券码池

+ + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
codeVARCHAR(32)归一化大写无连字符
typeENUM('trial','monthly','annual','lifetime')annual计费/时长标签
tierVARCHAR(32)standard档位钩子(pro/max-like),当前默认 standard
duration_daysINT0授予时长;0=永久(lifetime)
max_devicesINT0授予设备上限;0=不改变现值
statusENUM('unused','redeemed','void')unused
redeemed_shop_idBIGINT UNSIGNEDNULL被哪个门店兑换
redeemed_atDATETIMENULL
redeemed_device_idVARCHAR(255)NULL兑换设备(审计)
batchVARCHAR(64)NULL发放批次/活动名
noteVARCHAR(255)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
UNIQUE uk_code (`code`)
INDEX idx_status (`status`)
INDEX idx_redeemed_shop (`redeemed_shop_id`)
+

商品主数据

products 每行 = 一个特有产品/序列号(非 SKU),是商品信息的单一来源。基础数据字典为入库选文本服务。

+

products 商品

+
单一来源铁律:批次号 batch_no、生产日期 production_date、进价、图片、public_id 均以此表为准。每行 = 一个序列号(uk_product_code 同店唯一)。
+ + + + + + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
codeVARCHAR(50)NULL商品编号
barcodeVARCHAR(100)NULL条码
nameVARCHAR(200)商品名称
seriesVARCHAR(100)NULL系列
specVARCHAR(100)NULL规格
unitVARCHAR(20)NULL单位
category_idBIGINT UNSIGNEDNULL
brandVARCHAR(100)NULL品牌
purchase_priceDECIMAL(16,2)NULL参考进价
sale_priceDECIMAL(16,2)NULL参考售价
public_idCHAR(36)UUID,公开URL使用
min_stockINT0库存预警值
descriptionTEXTNULL商品描述
custom_fieldsJSONNULL动态扩展字段
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_category (`category_id`)
INDEX idx_deleted_at (`deleted_at`)
UNIQUE uk_product_code (`shop_id`, `code`)
UNIQUE uk_public_id (`public_id`)
FULLTEXT ft_name (`name`)
+
+
+

product_categories 商品分类

+ + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
nameVARCHAR(100)
parent_idBIGINT UNSIGNEDNULL父分类,支持二级
sort_orderINT0
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
+
+
+

product_images 商品图片

+ + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
product_idBIGINT UNSIGNED
shop_id 租户BIGINT UNSIGNED
urlVARCHAR(500)相对路径,如 /images/products/1/xxx.jpg
sort_orderINT0
created_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
INDEX idx_product_id (`product_id`)
INDEX idx_shop_id (`shop_id`)
+
+
+

product_name_options 商品名称选项

+ + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
codeVARCHAR(50)NULL选项编号
nameVARCHAR(200)名称
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_deleted_at (`deleted_at`)
+
+
+

product_series_options 商品系列选项

+ + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
codeVARCHAR(50)NULL选项编号
nameVARCHAR(200)系列名称
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_deleted_at (`deleted_at`)
+
+
+

product_spec_options 商品规格选项

+ + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
codeVARCHAR(50)NULL选项编号
nameVARCHAR(200)规格名称
quantityINT0单品数量
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_deleted_at (`deleted_at`)
+
+
+

product_origin_options 商品产地选项(基础数据·AutoMigrate)

+ + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED租户隔离
codeVARCHAR(50)选项编号
nameVARCHAR(200)名称
remarkVARCHAR(500)
created_at / updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL软删除
+ +
+
+

product_shelf_life_options 商品保质期选项(如「24个月」「无限期」·AutoMigrate)

+ + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED租户隔离
codeVARCHAR(50)选项编号
nameVARCHAR(200)名称
remarkVARCHAR(500)
created_at / updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL软删除
+ +
+
+

product_storage_options 商品储存条件选项(如「阴凉避光」·AutoMigrate)

+ + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED租户隔离
codeVARCHAR(50)选项编号
nameVARCHAR(200)名称
remarkVARCHAR(500)
created_at / updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL软删除
+ +
+
+

product_description_docs 商品介绍文档(富文本·AutoMigrate)

+ + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED租户隔离
titleVARCHAR(200)标题
contentTEXT正文
remarkVARCHAR(500)
created_at / updated_at / deleted_atDATETIME
+ +

仓库 · 往来单位

+

warehouses 仓库

+ + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
nameVARCHAR(100)
locationVARCHAR(200)NULL
is_defaultTINYINT(1)0
custom_fieldsJSONNULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
+
+
+

partners 往来单位

+ + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
codeVARCHAR(50)NULL
nameVARCHAR(200)
typeSET('supplier','customer')supplier可同时是供应商和客户
contactVARCHAR(50)NULL联系人
phoneVARCHAR(30)NULL
addressVARCHAR(255)NULL
bank_accountVARCHAR(100)NULL银行账号
statusENUM('enabled','disabled')enabled正常/禁用
custom_fieldsJSONNULL
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_deleted_at (`deleted_at`)
+

出入库单据

单据明细含「快照列」(product_code/name/series/spec/batch_no/production_date):导入/审核时拷贝,作历史保真+显示兜底。

+

stock_in_orders 入库单

+ + + + + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
order_noVARCHAR(50)入库单号
typeVARCHAR(30)purchase入库类型: purchase采购/return退货/other其他
warehouse_idBIGINT UNSIGNED
partner_idBIGINT UNSIGNEDNULL供应商
operator_idBIGINT UNSIGNED经办人
creator_idBIGINT UNSIGNEDNULL制单人
reviewer_idBIGINT UNSIGNEDNULL审核人
statusENUM('draft','pending','approved','rejected')draft
order_dateDATE
total_amountDECIMAL(16,2)0
reviewed_atDATETIMENULL
custom_fieldsJSONNULL
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
UNIQUE uk_order_no (`shop_id`, `order_no`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_warehouse (`warehouse_id`)
INDEX idx_order_date (`order_date`)
INDEX idx_status (`status`)
+
+
+

stock_in_items 入库单明细

+
快照列:product_code/product_name/series/spec/batch_no/production_date 在导入/录入时拷贝,product_id 可为 0(历史导入占位)。库存页的批次/生产日期实际从这里取(见 inventories 说明)。
+ + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
order_idBIGINT UNSIGNED
shop_id 租户BIGINT UNSIGNED
product_idBIGINT UNSIGNED
product_codeVARCHAR(50)NULL商品编号(源流水号,历史导入)
product_nameVARCHAR(255)NULL商品名称(历史导入快照)
seriesVARCHAR(100)NULL系列(历史导入快照)
specVARCHAR(100)NULL规格(历史导入快照)
quantityDECIMAL(12,3)数量
unit_priceDECIMAL(16,2)0单价
total_priceDECIMAL(16,2)0
batch_noVARCHAR(50)NULL批次号
production_dateDATENULL生产日期
expire_dateDATENULL有效期
custom_fieldsJSONNULL
remarkVARCHAR(255)NULL
+
PRIMARY (`id`)
INDEX idx_order_id (`order_id`)
INDEX idx_shop_product (`shop_id`, `product_id`)
+
+
+

stock_out_orders 出库单

+ + + + + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
order_noVARCHAR(50)
typeVARCHAR(30)salesale销售/return退货/other其他
warehouse_idBIGINT UNSIGNED
partner_idBIGINT UNSIGNEDNULL客户
operator_idBIGINT UNSIGNED
creator_idBIGINT UNSIGNEDNULL制单人
reviewer_idBIGINT UNSIGNEDNULL
statusENUM('draft','pending','approved','rejected')draft
order_dateDATE
total_amountDECIMAL(16,2)0
reviewed_atDATETIMENULL
custom_fieldsJSONNULL
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
deleted_atDATETIMENULL
+
PRIMARY (`id`)
UNIQUE uk_order_no (`shop_id`, `order_no`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_warehouse (`warehouse_id`)
INDEX idx_order_date (`order_date`)
INDEX idx_status (`status`)
+
+
+

stock_out_items 出库单明细

+
当前问题相关:出库行快照 batch_no/production_date 在 App 建单时常为空(选货按 product 聚合、未带批次)。打印回退到 products 主记录——若该 product 也没填,就显示空白,但库存页仍能显示(取自入库明细)。unit_price=成本、sale_price=售价。
+ + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
order_idBIGINT UNSIGNED
shop_id 租户BIGINT UNSIGNED
product_idBIGINT UNSIGNED
product_codeVARCHAR(50)NULL商品编号(源流水号,历史导入)
product_nameVARCHAR(255)NULL商品名称(历史导入快照)
seriesVARCHAR(100)NULL系列(历史导入快照)
specVARCHAR(100)NULL规格(历史导入快照)
quantityDECIMAL(12,3)
unit_priceDECIMAL(16,2)0成本单价(入库成本快照)
sale_priceDECIMAL(16,2)0售价(出库实际销售单价,应收按售价×数量)
total_priceDECIMAL(16,2)0成本小计(unit_price×quantity)
batch_noVARCHAR(50)NULL批次号
production_dateDATENULL生产日期
custom_fieldsJSONNULL
remarkVARCHAR(255)NULL
+
PRIMARY (`id`)
INDEX idx_order_id (`order_id`)
INDEX idx_shop_product (`shop_id`, `product_id`)
INDEX idx_so_items_shop_code (`shop_id`, `product_code`)
+

库存 · 流水 · 盘点

库存为批次模式:每条 inventories 记录 = 一个入库批次,经 stock_in_item_id 关联其入库明细。

+

inventories 库存批次记录

+
批次模式 + 显示口径:每行 = 一个入库批次。列表查询的批次/生产日期取 COALESCE(DATE(sii.production_date), DATE(inv.production_date))COALESCE(NULLIF(sii.batch_no,''), inv.batch_no)——优先关联入库明细 sii不读 products。本表 batch_no/production_date 为快照兜底。
+ + + + + + + + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC主键
shop_id 租户BIGINT UNSIGNED所属门店(多租户隔离,所有查询必带)
warehouse_idBIGINT UNSIGNEDNULL所在仓库;NULL 表示历史导入未指定仓
product_idBIGINT UNSIGNEDNULL指向 products 的特有产品/序列号;历史导入占位单据可为 NULL/0
stock_in_item_idBIGINT UNSIGNEDNULL来源入库明细行。批次/生产日期显示优先从此关联取(sii),是本表的「真值上游」
inventory_check_idBIGINT UNSIGNEDNULL若由盘点调整产生,指向盘点单 inventory_checks
quantityDECIMAL(12,3)0当前批次结存数量(出入库审核时在事务内增减)
product_codeVARCHAR(50)NULL商品编号快照(拷贝自入库时),product 缺失时兜底显示
product_nameVARCHAR(200)NULL商品名称快照,兜底显示
seriesVARCHAR(100)NULL系列快照(语义=型号),兜底显示
specVARCHAR(100)NULL规格快照(语义=版本),兜底显示
unitVARCHAR(20)NULL单位快照
warehouse_nameVARCHAR(100)NULL仓库名快照,warehouse 关联缺失时兜底
unit_priceDECIMAL(16,2)NULL入库成本单价快照
production_dateDATENULL生产日期快照兜底(显示口径见上方:优先取 stock_in_item)
batch_noVARCHAR(50)NULL批次号快照兜底(显示口径见上方:优先取 stock_in_item)
supplier_nameVARCHAR(200)NULL供应商名快照(入库来源)
remarkVARCHAR(500)NULL备注
created_atDATETIMECURRENT_TIMESTAMP创建时间(FIFO 排序依据 idx_fifo 用到)
updated_atDATETIMECURRENT_TIMESTAMP更新时间
deleted_atDATETIMENULL软删除标记,NULL=有效
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_fifo (`shop_id`, `warehouse_id`, `product_id`, `created_at`)
INDEX idx_shop_wh_product (`shop_id`, `warehouse_id`, `product_id`)
INDEX idx_stock_in_item (`stock_in_item_id`)
INDEX idx_inventory_check (`inventory_check_id`)
INDEX idx_deleted_at (`deleted_at`)
+
+
+

inventory_logs 库存流水

+ + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
warehouse_idBIGINT UNSIGNED
product_idBIGINT UNSIGNED
directionENUM('in','out')
quantityDECIMAL(12,3)
qty_beforeDECIMAL(12,3)变动前数量
qty_afterDECIMAL(12,3)变动后数量
ref_typeVARCHAR(30)stock_in/stock_out/check
ref_idBIGINT UNSIGNED关联单据ID
operator_idBIGINT UNSIGNEDNULL
created_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
INDEX idx_shop_product (`shop_id`, `product_id`)
INDEX idx_ref (`ref_type`, `ref_id`)
+
+
+

inventory_checks 库存盘点单

+ + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
check_noVARCHAR(50)
warehouse_idBIGINT UNSIGNED
operator_idBIGINT UNSIGNED
statusENUM('draft','completed')draft
check_dateDATE
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
updated_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
UNIQUE uk_check_no (`shop_id`, `check_no`)
INDEX idx_shop_id (`shop_id`)
+
+
+

inventory_check_items 盘点明细

+ + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
check_idBIGINT UNSIGNED
shop_id 租户BIGINT UNSIGNED
product_idBIGINT UNSIGNED
system_qtyDECIMAL(12,3)系统数量
actual_qtyDECIMAL(12,3)实际盘点数量
diff_qtyDECIMAL(12,3)计算列差异
remarkVARCHAR(255)NULL
+
PRIMARY (`id`)
INDEX idx_check_id (`check_id`)
+

财务

+

finance_records 财务流水

+ + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
partner_idBIGINT UNSIGNEDNULL
typeENUM('receivable','payable','receipt','payment')应收/应付/收款/付款
amountDECIMAL(16,2)
balanceDECIMAL(16,2)操作后余额
statusENUM('open','closed')open结清状态(payable/receivable 有效)
ref_typeVARCHAR(30)NULL关联单据类型
ref_idBIGINT UNSIGNEDNULL
operator_idBIGINT UNSIGNED
record_dateDATE
custom_fieldsJSONNULL
remarkVARCHAR(500)NULL
created_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_partner (`partner_id`)
INDEX idx_record_date (`record_date`)
+

配置 · 运维 · 反馈

+

number_rules 单号规则

+ + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
shop_id 租户BIGINT UNSIGNED
typeVARCHAR(30)stock_in/stock_out/check
prefixVARCHAR(20)前缀
current_noINT0当前序号
date_formatVARCHAR(20)YYYYMMDD日期格式
updated_atDATETIMECURRENT_TIMESTAMP
+
PRIMARY (`id`)
UNIQUE uk_shop_type (`shop_id`, `type`)
+
+
+

feedbacks 用户意见反馈

+ + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
created_atDATETIMECURRENT_TIMESTAMP
shop_id 租户BIGINT UNSIGNED
shop_codeVARCHAR(50)NULL
user_idBIGINT UNSIGNEDNULL
usernameVARCHAR(50)NULL
roleVARCHAR(20)NULL
typeVARCHAR(20)bug / suggestion
contentTEXT
imagesJSON
app_versionVARCHAR(30)NULL
platformVARCHAR(20)NULL
client_ipVARCHAR(60)NULL
statusVARCHAR(20)new
+
PRIMARY (`id`)
INDEX idx_shop_id (`shop_id`)
INDEX idx_user_id (`user_id`)
INDEX idx_type (`type`)
INDEX idx_status (`status`)
+
+
+

error_reports 客户端异常上报(AutoMigrate,无 shop 软删除)

+ + + + + + + + + + + + + + + + + +
列名类型可空默认说明
id PKBIGINT UNSIGNEDAUTO_INC
error_typeVARCHAR(30)flutter_error / zone_error / caught_exception
app_versionVARCHAR(30)
platformVARCHAR(20)ios/android/web/windows/macos
usernameVARCHAR(50)
shop_id 租户BIGINT UNSIGNED
shop_noVARCHAR(50)
roleVARCHAR(20)
product_infoVARCHAR(200)设备/系统信息
client_ipVARCHAR(60)
error_msgTEXT错误信息
stack_traceTEXT堆栈
occurred_atDATETIME发生时间
created_atDATETIME上报时间
+ +
+

生成时间:2026-06-26 · 字段以 schema.sql 为准;产地/保质期/储存/介绍文档/异常上报等表由 GORM AutoMigrate 建,列信息取自 backend/internal/model/

+
+
+ + \ No newline at end of file diff --git a/docs/review/2026-07-03-precommit-review.md b/docs/review/2026-07-03-precommit-review.md new file mode 100644 index 0000000..b0e1138 --- /dev/null +++ b/docs/review/2026-07-03-precommit-review.md @@ -0,0 +1,121 @@ +# 代码审查报告 — 提交前复审(auth 双屏重建 + DS 原子扩展 + 备案 https 回切) + +**审查时间**:2026-07-03(第二轮,接 `docs/review/2026-07-03-uncommitted-review.md`) +**变更范围**:git diff HEAD 210 个文件(+11354 / −10986)+ 约 90 个未跟踪文件(auth_shared / ds_toast / ds_menu / users_screen / golden 基线等) +**已验证事实**(调用方提供):`flutter analyze` 0 error、251 测试全过、`check_ds_code` 0 违规、`go build/vet/test` 全过 + +## 总体评价 + +本轮新增改动质量高,无阻塞提交的问题。登录/注册双屏照原型重建后,dispose 路径(Timer/OverlayEntry/FocusNode/Controller)总体齐全;上一轮 6 条 should-fix 中 4 条已修复(https 回切一并解决 SHOULD-001/002,manual.yml 兼容变量解决 003,DsButton 置灰解决 004),SEC-001/002/003 修复无回归且补了跨租户回归测试。剩余问题集中在登录屏两处小竞态/生命周期缝隙和几条运维/文档收尾项。 + +--- + +## 必须修复(blocking) + +无。 + +--- + +## 建议改进(should-fix,non-blocking) + +### [SHOULD-101] 登录中途导航离开 → catch 分支 setState 打在已 dispose 的 State 上 +**文件**:`client/lib/screens/auth/login_screen.dart:306-311` +**问题**:`_login()` 里 `await AuthRepository.login(...)` 期间,「注册新门店 →」「忘记密码?」链接**未随 `_loading` 禁用**,用户可在等待网络时点走(`context.go('/register')`)。此后: +- 失败路径:`on AuthException catch` / `catch` 里的 `setState(() => _errorMessage = ...)` 无 `mounted` 守卫(对照 finally 是有的),在已 dispose 的 State 上抛 FlutterError,经全局捕获还会打进错误上报; +- 成功路径:`await ref.read(authStateProvider.notifier).login(user)` 在 dispose 后使用 `ref` 同样会抛,且被泛型 catch 捕到后再次走无守卫的 setState。 + +**建议**: +```dart +} on AuthException catch (e) { + if (!mounted) return; + setState(() => _errorMessage = e.message); +} +``` +两个 catch 都补 `mounted` 守卫;或更简单——`_loading` 时把两个 `_Link` 的 onTap 置空。 + +### [SHOULD-102] 历史下拉:失焦关闭 Timer 在重新聚焦时未取消,会误关刚打开的下拉 +**文件**:`client/lib/screens/auth/login_screen.dart:81-95, 97-111` +**问题**:失焦分支安排 `_closeShopTimer = Timer(150ms, _closeShop)`;但 `hasFocus` 分支只调 `_closeShop()` 不取消该 Timer。序列「聚焦 → 点外部失焦(Timer 起跑)→ 150ms 内再点回字段(下拉重新打开)→ 旧 Timer 到点」会把刚打开的下拉关掉,且箭头图标状态与实际不符。username 侧同构。 +**建议**:`hasFocus` 分支先 `_closeShopTimer?.cancel()` / `_closeUsernameTimer?.cancel()` 再开下拉。 + +### [SHOULD-103] SEC-004 残留:盘点建单仍未校验 warehouse_id / product_id 归属 +**文件**:`backend/internal/handler/inventory.go:240-271`(`CreateCheck`) +**问题**:本轮 `finance.Create` 已补 `ensureShopRefOpt`(finance.go:96-100),stock_in/out 的 Create/Update 也都补了;但审计报告 SEC-004 点名的另一处 `CreateCheck` 依旧直存请求里的 `req.WarehouseID` 与 `Items[i].ProductID`,塞他店 id 会留下跨店脏引用。读取侧已按 shop_id 收口(Preload 带条件、system_qty 查询带 shop_id),**无信息泄露**,但脏数据会让该盘点行 system_qty 恒为 0、`Items.Product` 恒为 nil。 +**建议**:对齐 stock_in.Create 的写法: +```go +if err := ensureShopRef(h.db, "warehouses", req.WarehouseID, shopID); err != nil { ... } +for i := range req.Items { + if err := ensureShopRef(h.db, "products", req.Items[i].ProductID, shopID); err != nil { ... } +} +``` + +### [SHOULD-104] 证书续期路径依赖营销站 webroot,8 月底到期前需确认打通 +**文件**:`deploy/nginx-jiu-ali.conf:107-112`(80 → 443 无条件 301)、注释第 8-9 行 +**问题**:证书 2026-08-28 到期(不足两个月)。80 端口整站 `return 301`,certbot HTTP-01 会跟随跳转到 443,challenge 最终落在 `location /` 的 `root /opt/jiu/marketing`。两个脆弱点:① certbot `--webroot` 必须恰好指向 `/opt/jiu/marketing`;② `deploy-site.sh` 若用 `rsync --delete` 同步营销站,续期窗口内可能删掉 challenge 文件。 +**建议**:在 80 的 server 块里、`return 301` 之前加一个独立 challenge 位置,让续期与营销站部署解耦: +```nginx +location ^~ /.well-known/acme-challenge/ { root /opt/jiu/acme; } +``` +并在 baize 台账登记续期命令用同一 webroot。 + +### [SHOULD-105] 项目 CLAUDE.md 仍强制使用本轮已删除的 DataTableCard +**文件**:`/Users/wangjia/code/jiu/CLAUDE.md`(「表格列头筛选」「响应式 / 移动端适配」两节) +**问题**:`client/lib/widgets/data_table_card.dart` 本轮已删(连同 form_dialog / page_scaffold / search_chip / select_product_dialog / tab_state_provider,经全量 grep 确认**零残留引用**),但项目规则仍写「用 `DataTableCard` 时必须同时传 `mobileCards`」「可筛选列使用 `FilterableColumnHeader`」。规则与代码脱节,后续 agent 会照死文档施工。(`ColDef` 仍存活于 `multi_select_dropdown.dart`,该行不受影响。) +**建议**:随本次提交把这两节改写为 DS 重建后的真实套路(ds_atoms / MobileListCard 现状)。 + +### [SHOULD-106] 测试失败截图混在未跟踪文件里,勿随本次提交入库 +**文件**:`client/test/failures/tmp_date_menu_{isolatedDiff,maskedDiff,masterImage,testImage}.png` +**问题**:golden 失败产物;`client/.gitignore` 只忽略 `test/golden/failures/`,没盖住 `test/failures/`,`git add -A` 会把 4 张调试图带进仓库(违背「截图放 tmp 不落 repo」约定)。 +**建议**:`client/.gitignore` 补一行 `test/failures/`,并删除这 4 个文件(用 git clean)。 + +### [SHOULD-107](上轮 SHOULD-005 收尾)结清后 financePartnerRowsProvider 仍陈旧 +**文件**:`client/lib/screens/stock_in/stock_in_list_screen.dart:1117-1119`(stock_out 同构) +**问题**:本轮已补 `reload()` 刷新列表(好),但 `financePartnerRowsProvider` 是**非 autoDispose** 的 FutureProvider(finance_provider.dart:113),结清后切到财务屏仍显示结清前的应收应付,直到财务屏自己的刷新路径触发。 +**建议**:closeByRef 成功后追加 `ref.invalidate(financePartnerRowsProvider)`(一行)。 + +--- + +## 细节(nit) + +### [NIT-101] 登录历史下拉定宽 340,窄屏溢出字段甚至屏幕 +**文件**:`client/lib/screens/auth/login_screen.dart:202` +`context.dialogWidth(340)` 只按屏宽 92% 封顶。360dp 手机上字段实际宽约 248(440 卡 − 24 外距 − 32 内距 ×2),下拉 331 宽从字段左缘起排,右侧超出屏幕约 27px 被裁。建议宽度取锚点字段实宽(LayoutBuilder 或缓存 RenderBox size)。 + +### [NIT-102] `replaceFirst('AuthException: ', '')` 是死代码 +**文件**:`client/lib/screens/auth/register_screen.dart:136` +`AuthException.toString()` 直接返回 message(auth_repository.dart:17),该前缀永不出现。无害,删掉即可。 + +### [NIT-103] `_resetPassword` 的 TextEditingController 仍未 dispose(上轮 NIT-003 未修) +**文件**:`client/lib/screens/settings/users_screen.dart:465` +另:该对话框用裸 Material `TextField` + `InputDecoration(hintText)`,与 DS 的 `DsInput` 不一致(check_ds_code 未拦,属风格缝隙)。 + +### [NIT-104] 授权锁定判定靠英文文案子串匹配 +**文件**:`client/lib/screens/auth/login_screen.dart:60-64` +`m.contains('license') && (lock|expire)` 与后端 `service.ErrLicenseLocked` 固定文案强耦合,后端一旦本地化/改文案即静默失效退回普通错误条。注释已写明来源,可接受;更稳做法是后端返回错误 code。 + +### [NIT-105] 注册页「确认密码」与「登录密码」共用一个 `_obscure` +**文件**:`client/lib/screens/auth/register_screen.dart:280-297` +点密码框的眼睛会同时明文显示确认框。行为可辩护(对照确认更方便),确认是有意为之即可。 + +### [NIT-106] 登录/注册页脚年份硬编码「© 2026」 +**文件**:`client/lib/screens/auth/login_screen.dart:562` +跨年需手改。原型如此则属 1:1 还原,提请知悉。 + +--- + +## 值得肯定的地方 + +- **上轮反馈闭环干净**:https 回切一举解决 SHOULD-001(mixed content)与 SHOULD-002——5 个 `compile-*.sh` 的 `BASE_URL` 全部回到 `https://jiu.51yanmei.com`,Android `usesCleartextTraffic`、iOS/macOS `NSAllowsArbitraryLoads` 明文豁免**全部撤除**(工作树与 HEAD 一致,经 PlistBuddy/grep 逐一确认);`manual.yml` 补 `EC2_* = ALI_*` 兼容变量解决旧 tag 回滚炸 unbound variable(SHOULD-003);`DsButton` disabled 态 `Opacity(0.55)`(SHOULD-004)✅ +- **SEC 修复无回归且加了防回归测试**:`ownership.go` 写入侧 `ensureShopRef` + 读取侧全部 `Preload("X", "shop_id = ?", shopID)` 双防线,`TestStockInHandler_CrossTenantRefs` 覆盖「跨店外键 400 + 存量脏引用不带出 PII」两条路径;`main.go` 生产模式空/默认 JWT secret 直接拒启(SEC-002);三个运维 workflow 的 DB 密码改从容器内 `$MYSQL_ROOT_PASSWORD` 取、不再走命令行(SEC-003),`DB_PASSWORD` secret 引用清零 ✅ +- **登录屏生命周期总体严谨**:`dispose()` 中 Timer cancel、OverlayEntry remove、3 controller + 2 FocusNode 全 dispose;`ds_toast` 单例 `_entry.mounted` 守卫 + `_entry == entry` 比对避免关错新 toast;「被顶下线漏提示」的 initState 首帧补读修法正确且注释讲清了根因 ✅ +- **`fillStockOutItemSnapshots` 顺带完成了出库明细 product 归属校验**(`shop_id = ? AND id IN ?` + 缺行报错),比逐行 ensureShopRef 还省往返 ✅ +- **死文件清理彻底**:6 个删除文件在 lib/ 与 test/ 均无残留引用(analyze 0 error 交叉印证)✅ +- **注册契约核对无误**:`AuthRepository.register` 六字段与后端 `service.RegisterInput`(auth.go:424-432)逐一对应,密码 min=6 与前端校验一致,与原型差异(无编号/兑换券字段、地址必填)在文件头注释里如实登记 ✅ + +--- + +## 结论 + +- [x] 0 处 blocking,可以提交 +- [ ] should-fix 7 处:SHOULD-101/102(登录屏两行守卫,建议顺手修掉再提交)、SHOULD-106(.gitignore + 删 4 张失败截图,**建议提交前处理**以免脏图入库)、SHOULD-105(CLAUDE.md 同步)、SHOULD-103/104/107 可随后续迭代 +- [ ] nit 6 处,不阻塞 diff --git a/docs/review/2026-07-03-uncommitted-review.md b/docs/review/2026-07-03-uncommitted-review.md new file mode 100644 index 0000000..7a739d0 --- /dev/null +++ b/docs/review/2026-07-03-uncommitted-review.md @@ -0,0 +1,96 @@ +# 代码审查报告 — 未提交变更集(出入库 rolling30 + DS 重构 + CI ali 单轨化) + +**审查时间**:2026-07-03 +**变更文件**:200 个文件,+10283 行,−9665 行(含 staged 删除 `client/lib/widgets/data_table_card.dart`) +**验证结果**:`go build ./...` / `go vet ./...` / `go test ./...` 全部通过;`flutter analyze` 0 error(3 warning 均在 test/,96 info) + +## 总体评价 + +变更集质量整体很高。后端 rolling30 窗口边界数学正确、窗口相接不重叠、有测试覆盖,新增查询多租户 `shop_id` 条件齐全;客户端大规模 DS 重构后,我逐一核对了全部 40+ 处确认弹窗的「按钮标签 ↔ onPressed ↔ pop 值」语义,未发现批量正则转换导致的错位;CI ali 单轨化的 `DEPLOY_*` 变量在 workflows / lib-forgejo.sh / deploy-*.sh / backup-db.sh 之间自洽。无阻塞合并的问题;「裸 IP 明文 HTTP」是用户拍板的备案过渡方案,但有两条具体的破坏路径需要确认(见 SHOULD-001/002)。 + +--- + +## 必须修复(blocking) + +无。 + +--- + +## 建议改进(should-fix,non-blocking 但建议尽快处理/确认) + +### [SHOULD-001] Web 版 mixed content:https 域名入口下 API 全被浏览器拦截 +**文件**:`scripts/ci/compile-client-web.sh:20-21`(同模式见 `compile-android.sh:44`、`compile-ios.sh:106`、`compile-macos.sh:33`、`compile-windows.sh:43`、`scripts/local_test.sh:33`) +**问题**:Web 构建的 `BASE_URL` 硬编码为 `http://182.92.213.171:8443`。`deploy-client.yml:183-184` 注释说明 EC2 仍保留 `jiu.51yanmei.com` 反代桥接到 ali——若任何用户仍从 `https://jiu.51yanmei.com/app` 打开 Web 版,页面是 https 源、XHR 目标是 http,浏览器会以 active mixed content 直接拦截所有 API 请求,登录静默失败(native 桌面/移动端无此策略,不受影响)。 +**建议**:三选一——① Web 构建 `BASE_URL` 留空走同源相对路径(`AppConfig` 里 Web 分支取 `Uri.base`),天然兼容 http/https 两个入口;② 确认 EC2 桥已把 `/app` 路径 301 到 `http://182.92.213.171:8443/app`;③ 明确弃用域名 Web 入口并在 baize 台账登记。 + +### [SHOULD-002] 明文 HTTP 承载登录口令与 JWT(过渡期已知风险,建议登记回切项) +**文件**:`scripts/ci/compile-*.sh`(同上)、`client/android/app/src/main/AndroidManifest.xml:11`、`client/ios/Runner/Info.plist:11`、`client/macos/Runner/Info.plist:37` +**问题**:备案过渡期所有客户端流量(含 `/auth/login` 的明文密码、后续每个请求的 JWT)走公网明文 HTTP;三端又同时开了全局明文豁免(Android `usesCleartextTraffic="true"`、iOS/macOS `NSAllowsArbitraryLoads=true`)。注释里已写「备案后应移除」,但散落在 6 个 compile 脚本 + 3 个平台清单共 9 处。另注意:iOS 带 `NSAllowsArbitraryLoads=true` 提交 App Store/TestFlight 可能被要求填写豁免理由。 +**建议**:用 todo skill 登记一条「备案通过后回切 https」任务,列全 9 处文件清单,避免遗漏;iOS 可考虑收窄为 `NSExceptionDomains` 只豁免该 IP。 + +### [SHOULD-003] manual.yml 回滚到割接前的旧 tag 会失败 +**文件**:`.gitea/workflows/manual.yml:22-24, 31-35` +**问题**:manual 回滚 `checkout ref: inputs.version` 后执行**旧 tag 里的** deploy 脚本。割接前的 tag(deploy 脚本尚未支持 `DEPLOY_*` 回退写法、直接引用 `${EC2_HOST}`)在 `set -u` 下会因 unbound variable 直接失败——而回滚恰恰最可能选旧版本。 +**建议**:要么在 manual.yml env 里同时兼容传一份 `EC2_* = ALI_*`(让旧脚本也指向 ali),要么在 workflow 注释里写明「最早可回滚 tag」的下限。 + +### [SHOULD-004] DsButton 无 disabled 视觉态,加载中按钮看起来仍可点 +**文件**:`client/lib/widgets/ds/ds_atoms.dart:47`(`InkWell(onTap: onPressed)`) +**问题**:`onPressed: null` 时(如 `about_screen.dart:758` 提交反馈、`stock_in_form_screen.dart` 暂存/提交的 `_submitting ? null : ...`)按钮仅失去点击响应,颜色/边框与可用态完全一致,用户无从感知「提交中」。旧 Material 按钮自带 disabled 置灰,本次批量替换后此反馈丢失。 +**建议**:在 `DsButton.build` 里对 `onPressed == null` 做 `Opacity(0.5)` 或 fg/bg 换 `t.muted`/`t.bg`(对齐原型 `.btn:disabled` 若有定义;没有则先在原型 atoms.css 登记再实现,遵守设计系统单一真源)。 + +### [SHOULD-005] 结清成功后不刷新列表/财务数据 +**文件**:`client/lib/screens/stock_in/stock_in_list_screen.dart:1101-1135`、`client/lib/screens/stock_out/stock_out_list_screen.dart`(同构 `_confirmSettle`) +**问题**:`closeByRef` 成功后只弹 snackbar,既不 `reload()` 列表也不 `invalidate` 财务相关 provider。若详情抽屉/行内展示了应收应付状态徽章,会显示过期状态;财务屏的 `financePartnerRowsProvider` 非 autoDispose 时也会陈旧(`finance_screen.dart:105` 有手动 invalidate,但只在财务屏自己的刷新路径里)。 +**建议**:结清成功后补 `ref.read(stockXxListProvider.notifier).reload()` 并 `ref.invalidate(financePartnerRowsProvider)`。 + +### [SHOULD-006] finance Trend 循环发 2N 条 SQL(months=24 时 48 条) +**文件**:`backend/internal/handler/finance.go:198-215` +**问题**:每个月对 `stock_in_orders`/`stock_out_orders` 各发一条 SUM 查询,`months` 上限 24 → 单请求最多 48 次往返。数据量小时无碍,但属于典型可合并聚合。 +**建议**:各表一条 `GROUP BY strftime/DATE_FORMAT(order_date, '%Y-%m')`(或按窗口一条 SQL 取回后内存分桶),把 2N 降到 2。注意保持 MySQL/SQLite 双方言兼容(现有代码用日期串比较正是为此,分桶在 Go 内存里做最稳)。 + +--- + +## 细节(nit) + +### [NIT-001] 出库列表头部计数回退值口径混用 +**文件**:`client/lib/screens/stock_out/stock_out_list_screen.dart:466` +`'近30天 ${...summary30...monthCount ?? total}'`——summary 未加载时回退到 `total`(当前筛选下的分页总数,非 30 天口径),文案与数值短暂不符。入库侧 `stock_in_list_screen.dart:426` 同构,建议未加载时显示 `—`。 + +### [NIT-002] DsSelect 按对象同一性判等 +**文件**:`client/lib/screens/inventory/inventory_check_screen.dart:227` +`o.$1 == value` 依赖实例同一性(`Warehouse` 未重写 `==`)。仓库列表 provider 因换店/网络恢复重建后,`_selectedWarehouse` 指向旧实例,选中标签会回落成「请选择仓库」。建议 `DsSelect` 用 `warehouse.id` 做 value,或给模型加 `==`/`hashCode`。 + +### [NIT-003] `_resetPassword` 的 TextEditingController 未 dispose +**文件**:`client/lib/screens/settings/users_screen.dart:461` +对话框关闭后 `ctrl` 泄漏(对照 `stock_in_list_screen.dart:1084` 的 `_confirmCost` 是有 dispose 的)。低危但建议补齐。 + +### [NIT-004] finance Summary 的 GROUP BY 依赖 MySQL 函数依赖推导 +**文件**:`backend/internal/handler/finance.go:233-247` +`SELECT COALESCE(p.name,'')` 但 `GROUP BY f.partner_id, f.type`。MySQL 5.7+ 的 `ONLY_FULL_GROUP_BY` 能识别 `p.id = f.partner_id` 的函数依赖所以现在能跑,但把 `p.name` 显式加进 GROUP BY 更防御(不同版本/方言行为差异)。 + +### [NIT-005] deploy 脚本头注释仍写 EC2 为默认目标 +**文件**:`scripts/ci/deploy-client.sh:5`、`deploy-site.sh:4`、`deploy-server.sh:21`(`DEPLOY_TARGET:-ec2` 默认值) +割接后 EC2 轨已从 workflows 移除,但脚本注释与 `DEPLOY_TARGET` 默认值仍是 ec2。行为不受影响(workflow 显式传 ali),属文档漂移;下次动这些脚本时顺手更正即可。 + +### [NIT-006] 确认进价对话框:全部留空点「确认」无任何反馈 +**文件**:`client/lib/screens/stock_in/stock_in_list_screen.dart:1087` +`ok == true` 但 `items` 为空时静默 return,用户不知道为什么没生效。建议 snackbar 提示「未填写任何进价」。 + +--- + +## 值得肯定的地方 + +- **rolling30 窗口边界正确**:`summaryBounds`(`backend/internal/handler/stock_in.go:170-180`)当前窗 `[今-29d, 明)` 与对照窗 `[今-59d, 今-29d)` 各 30 天、首尾相接不重叠;默认分支与原 `monthBounds` 行为完全等价;`stock_in_test.go:167-179` 补了 -40 天单据落对照窗的边界测试 ✅ +- **多租户无缺口**:本次触及的全部查询(stock_in/out Summary 的 agg 与 pending 计数、finance Trend 的 `sum`、finance Summary 的 Raw SQL、inventory summary 月初快照)均带 `shop_id = ?`;`shopID` 一律取自 `middleware.GetShopID(c)`,无一处从请求参数读 ✅ +- **批量转换零错位**:逐一核对 40+ 处 `DsButton` 弹窗动作——取消一律 pop(false/null/void)、确认语义键一律 pop(true),`_detailActionGroups` 中标签与 `_confirmDelete/Submit/Approve/Reject/Withdraw/Return/Settle` 的映射全部对应正确 ✅ +- **除零防御到位**:`_momDelta`(inventory_list_screen.dart:474)、`StockSummary.countDeltaPct/amountDeltaPct`(stock_summary.dart:27-32)、`DsBarChart` 的 `max<=0→1`(ds_bar_chart.dart:41)均有保护 ✅ +- **口径拆分清晰**:`stockXxSummaryProvider`(自然月,财务屏用)与 `stockXxSummary30Provider`(rolling30,列表 KPI 用)并存,避免财务屏被列表口径变更波及,注释写明了用户拍板缘由 ✅ +- **CI ali 单轨化自洽**:`DEPLOY_SSH_KEY/HOST/USER` 在 `lib-forgejo.sh:114-115`、三个 deploy 脚本、`backup-db.sh:20` 的 `${DEPLOY_*:-$EC2_*}` 回退写法一致;backup/manual/deploy-client/site/server 五个 workflow 均已切 `ALI_*` secrets ✅ +- **reload 保留旧数据**(inventory_provider.dart:105 `copyWithPrevious`)消除筛选切换白屏;`WheelDatePanel` 起止倒置时自动交换(wheel_date_picker.dart:285-287);stock_out 表单空明细在提交与渲染两处都有守卫 ✅ + +--- + +## 结论 + +- [x] 0 处 blocking 问题,可以合并 +- [ ] 建议改进 6 处(SHOULD-001/002 建议在下次发版前确认/登记回切项;003-006 可随后续迭代处理),nit 6 处不阻塞 diff --git a/docs/review/inventory-qty-packsize-dirty.csv b/docs/review/inventory-qty-packsize-dirty.csv new file mode 100644 index 0000000..6a7bc8c --- /dev/null +++ b/docs/review/inventory-qty-packsize-dirty.csv @@ -0,0 +1,821 @@ +inventory_id,shop_id,warehouse_id,product_id,shang_pin_bian_ma,name,series,spec,current_qty,pack_size,dirty_pack_eq_qty,suggest_qty +1,1,1,1,NULL,NULL,NULL,NULL,108.000,,N, +2,1,1,2,NULL,NULL,NULL,NULL,54.000,,N, +3,1,1,3,NULL,NULL,NULL,NULL,36.000,,N, +4,1,1,4,NULL,NULL,NULL,NULL,36.000,,N, +5,1,1,5,NULL,NULL,NULL,NULL,24.000,,N, +7,1,2,8,NULL,NULL,NULL,NULL,24.000,,N, +1141,7,NULL,1166,ZXZ027280,茅台春夏秋冬动物2025,普通/53度,500ML*24/套,24.000,24,Y,1 +2276,6,NULL,2301,ZXZ027280,茅台春夏秋冬动物2025,普通/53度,500ML*24/套,24.000,24,Y,1 +3482,1,NULL,3507,ZXZ027280,茅台春夏秋冬动物2025,普通/53度,500ML*24/套,24.000,24,Y,1 +4760,8,10,4785,ZXZ027280,茅台春夏秋冬动物2025,普通/53度,500ML*24/套,24.000,24,Y,1 +4759,8,10,4784,ZXZ024282,杜康·河南,普通/53度,500ml*20/件,20.000,20,Y,1 +1140,7,NULL,1165,ZXZ024282,杜康·河南,普通/53度,500ml*20/件,20.000,20,Y,1 +2275,6,NULL,2300,ZXZ024282,杜康·河南,普通/53度,500ml*20/件,20.000,20,Y,1 +3481,1,NULL,3506,ZXZ024282,杜康·河南,普通/53度,500ml*20/件,20.000,20,Y,1 +6,1,2,7,NULL,NULL,NULL,NULL,18.000,,N, +9412,8,10,4830,P1297,茅台公斤大件2005原件,普通/53度,1.00L*12/件,12.000,12,Y,1 +1126,7,NULL,1151,ZXZ010092,茅台大件2002,普通/53度,500ml*12/件,12.000,12,Y,1 +4743,8,10,4768,ZXZ010092,茅台大件2002,普通/53度,500ml*12/件,12.000,12,Y,1 +2261,6,NULL,2286,ZXZ010092,茅台大件2002,普通/53度,500ml*12/件,12.000,12,Y,1 +3465,1,NULL,3490,ZXZ010092,茅台大件2002,普通/53度,500ml*12/件,12.000,12,Y,1 +2262,6,NULL,2287,ZXZ010155,汾酒2000,普通/53度,500ml*12/件,12.000,12,Y,1 +3466,1,NULL,3491,ZXZ010155,汾酒2000,普通/53度,500ml*12/件,12.000,12,Y,1 +1127,7,NULL,1152,ZXZ010155,汾酒2000,普通/53度,500ml*12/件,12.000,12,Y,1 +4744,8,10,4769,ZXZ010155,汾酒2000,普通/53度,500ml*12/件,12.000,12,Y,1 +4745,8,10,4770,ZXZ011928,茅台远望楼2010原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +2263,6,NULL,2288,ZXZ011928,茅台远望楼2010原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +3467,1,NULL,3492,ZXZ011928,茅台远望楼2010原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +1128,7,NULL,1153,ZXZ011928,茅台远望楼2010原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +2153,6,NULL,2178,ZXZ014241,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +3338,1,NULL,3363,ZXZ014241,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +2264,6,NULL,2289,ZXZ014660,茅台博鳌亚洲论坛2012大件,普通/53度,500ml*12/件,12.000,12,Y,1 +3468,1,NULL,3493,ZXZ014660,茅台博鳌亚洲论坛2012大件,普通/53度,500ml*12/件,12.000,12,Y,1 +4746,8,10,4771,ZXZ014660,茅台博鳌亚洲论坛2012大件,普通/53度,500ml*12/件,12.000,12,Y,1 +1129,7,NULL,1154,ZXZ014660,茅台博鳌亚洲论坛2012大件,普通/53度,500ml*12/件,12.000,12,Y,1 +1130,7,NULL,1155,ZXZ014666,茅台五星2007大件,普通/53度,500ml*12/件,12.000,12,Y,1 +2265,6,NULL,2290,ZXZ014666,茅台五星2007大件,普通/53度,500ml*12/件,12.000,12,Y,1 +3469,1,NULL,3494,ZXZ014666,茅台五星2007大件,普通/53度,500ml*12/件,12.000,12,Y,1 +4747,8,10,4772,ZXZ014666,茅台五星2007大件,普通/53度,500ml*12/件,12.000,12,Y,1 +4748,8,10,4773,ZXZ021857,茅台白皮飞天1998原开,普通/53度,500ml*12/件,12.000,12,Y,1 +1131,7,NULL,1156,ZXZ021857,茅台白皮飞天1998原开,普通/53度,500ml*12/件,12.000,12,Y,1 +2266,6,NULL,2291,ZXZ021857,茅台白皮飞天1998原开,普通/53度,500ml*12/件,12.000,12,Y,1 +3470,1,NULL,3495,ZXZ021857,茅台白皮飞天1998原开,普通/53度,500ml*12/件,12.000,12,Y,1 +2267,6,NULL,2292,ZXZ022311,茅台大件2008,普通/53度,500ml*12/件,12.000,12,Y,1 +3471,1,NULL,3496,ZXZ022311,茅台大件2008,普通/53度,500ml*12/件,12.000,12,Y,1 +4749,8,10,4774,ZXZ022311,茅台大件2008,普通/53度,500ml*12/件,12.000,12,Y,1 +1132,7,NULL,1157,ZXZ022311,茅台大件2008,普通/53度,500ml*12/件,12.000,12,Y,1 +1133,7,NULL,1158,ZXZ022498,茅台白皮1999原开,普通/53度,500ml*12/件,12.000,12,Y,1 +2268,6,NULL,2293,ZXZ022498,茅台白皮1999原开,普通/53度,500ml*12/件,12.000,12,Y,1 +3472,1,NULL,3497,ZXZ022498,茅台白皮1999原开,普通/53度,500ml*12/件,12.000,12,Y,1 +4750,8,10,4775,ZXZ022498,茅台白皮1999原开,普通/53度,500ml*12/件,12.000,12,Y,1 +4751,8,10,4776,ZXZ022499,茅台铁盖1993原开,普通/53度,200ml*12/件,12.000,12,Y,1 +1134,7,NULL,1159,ZXZ022499,茅台铁盖1993原开,普通/53度,200ml*12/件,12.000,12,Y,1 +2269,6,NULL,2294,ZXZ022499,茅台铁盖1993原开,普通/53度,200ml*12/件,12.000,12,Y,1 +3473,1,NULL,3498,ZXZ022499,茅台铁盖1993原开,普通/53度,200ml*12/件,12.000,12,Y,1 +6899,7,NULL,1159,ZXZ022500,茅台铁盖1993原开,普通/53度,200ml*12/件,12.000,12,Y,1 +9347,8,10,7269,ZXZ022500,茅台铁盖1993原开,普通/53度,200ml*12/件,12.000,12,Y,1 +4752,8,10,4777,ZXZ026149,茅台大件2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +2270,6,NULL,2295,ZXZ026149,茅台大件2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +3474,1,NULL,3499,ZXZ026149,茅台大件2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +1135,7,NULL,1160,ZXZ026149,茅台大件2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +1136,7,NULL,1161,ZXZ026326,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +4753,8,10,4778,ZXZ026326,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +2271,6,NULL,2296,ZXZ026326,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +3475,1,NULL,3500,ZXZ026326,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +2272,6,NULL,2297,ZXZ026602,茅台小白条2023原件,普通/53度,500ml*5/件,12.000,5,N, +1137,7,NULL,1162,ZXZ026602,茅台小白条2023原件,普通/53度,500ml*5/件,12.000,5,N, +1138,7,NULL,1163,ZXZ026827,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +2273,6,NULL,2298,ZXZ026827,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +3476,1,NULL,3501,ZXZ026827,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +9348,8,10,7270,ZXZ026827,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +9349,8,10,7271,ZXZ026828,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +6900,7,NULL,1163,ZXZ026828,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +6901,7,NULL,1163,ZXZ026829,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +9350,8,10,7272,ZXZ026829,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +9351,8,10,7273,ZXZ026830,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +6902,7,NULL,1163,ZXZ026830,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +6903,7,NULL,1163,ZXZ026831,贵州茅台酒2011,普通/53度,500ml*12/件,12.000,12,Y,1 +9352,8,10,7274,ZXZ026831,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +9353,8,10,7275,ZXZ026832,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +2274,6,NULL,2299,ZXZ026832,茅台2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +3477,1,NULL,3502,ZXZ026832,茅台2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +1139,7,NULL,1164,ZXZ026832,茅台2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +9354,8,10,7276,ZXZ026833,茅台大件2011原开,普通/53度,500ml*12/件,12.000,12,Y,1 +6904,7,NULL,1164,ZXZ026833,茅台2011原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +6905,7,NULL,1043,ZXZ026936,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +6906,7,NULL,1043,ZXZ026937,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +9355,8,10,7277,ZXZ026937,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +6907,7,NULL,1043,ZXZ026938,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +9356,8,10,7278,ZXZ028054,茅台2009大件,普通/53度,500ml*12/件,12.000,12,Y,1 +3478,1,NULL,3503,ZXZ028055,茅台公斤大件2009,普通/53度,1.00L*12/件,12.000,12,Y,1 +4754,8,10,4779,ZXZ028055,茅台公斤大件2009,普通/53度,1.00L*12/件,12.000,12,Y,1 +4755,8,10,4780,ZXZ028574,茅台大件2011,普通/53度,500ml*12/件,12.000,12,Y,1 +3479,1,NULL,3504,ZXZ028574,茅台大件2011,普通/53度,500ml*12/件,12.000,12,Y,1 +3480,1,NULL,3505,ZXZ028684,茅台大件2006原开,普通/53度,500ml*12/件,12.000,12,Y,1 +4756,8,10,4781,ZXZ028684,茅台大件2006原开,普通/53度,500ml*12/件,12.000,12,Y,1 +4757,8,10,4782,ZXZ029443,铁盖郎酒1996原箱,普通/53度,500ml*12/件,12.000,12,Y,1 +4758,8,10,4783,ZXZ029459,茅台和平鸽2017大件原开,普通/53度,500ml*12/件,12.000,12,Y,1 +4741,8,10,4766,ZXZ002041,茅台1998白皮,普通/53度,500ml*11/件,11.000,11,Y,1 +1124,7,NULL,1149,ZXZ002041,茅台1998白皮,普通/53度,500ml*11/件,11.000,11,Y,1 +2259,6,NULL,2284,ZXZ002041,茅台1998白皮,普通/53度,500ml*11/件,11.000,11,Y,1 +3463,1,NULL,3488,ZXZ002041,茅台1998白皮,普通/53度,500ml*11/件,11.000,11,Y,1 +2260,6,NULL,2285,ZXZ017445,茅台特供陈酿大件2013,普通/53度,500ml*11/件,11.000,11,Y,1 +3464,1,NULL,3489,ZXZ017445,茅台特供陈酿大件2013,普通/53度,500ml*11/件,11.000,11,Y,1 +4742,8,10,4767,ZXZ017445,茅台特供陈酿大件2013,普通/53度,500ml*11/件,11.000,11,Y,1 +1125,7,NULL,1150,ZXZ017445,茅台特供陈酿大件2013,普通/53度,500ml*11/件,11.000,11,Y,1 +1123,7,NULL,1148,ZXZ025203,酒鬼1999年,普通/54度,540ml*10/件,10.000,10,Y,1 +2258,6,NULL,2283,ZXZ025203,酒鬼1999年,普通/54度,540ml*10/件,10.000,10,Y,1 +3462,1,NULL,3487,ZXZ026602,茅台小白条2023原件,普通/53度,500ml*5/件,10.000,5,N, +4740,8,10,4765,ZXZ026602,茅台小白条2023原件,普通/53度,500ml*5/件,10.000,5,N, +4739,8,10,4764,ZXZ002042,茅台白皮1999,普通/53度,500ml*8/件,8.000,8,Y,1 +1122,7,NULL,1147,ZXZ002042,茅台白皮1999,普通/53度,500ml*8/件,8.000,8,Y,1 +2257,6,NULL,2282,ZXZ002042,茅台白皮1999,普通/53度,500ml*8/件,8.000,8,Y,1 +3461,1,NULL,3486,ZXZ002042,茅台白皮1999,普通/53度,500ml*8/件,8.000,8,Y,1 +9405,8,10,4816,P1283,茅台彩釉珍品2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3360,1,NULL,3385,ZXZ000997,茅台酱色2018,普通/53度,500ml*6/件,6.000,6,Y,1 +4632,8,10,4657,ZXZ000997,茅台酱色2018,普通/53度,500ml*6/件,6.000,6,Y,1 +1031,7,NULL,1056,ZXZ000997,茅台酱色2018,普通/53度,500ml*6/件,6.000,6,Y,1 +2166,6,NULL,2191,ZXZ000997,茅台酱色2018,普通/53度,500ml*6/件,6.000,6,Y,1 +1032,7,NULL,1057,ZXZ002635,茅台三十年原开2005,普通/53度,375ml*6/件,6.000,6,Y,1 +2167,6,NULL,2192,ZXZ002635,茅台三十年原开2005,普通/53度,375ml*6/件,6.000,6,Y,1 +3361,1,NULL,3386,ZXZ002635,茅台三十年原开2005,普通/53度,375ml*6/件,6.000,6,Y,1 +4633,8,10,4658,ZXZ002635,茅台三十年原开2005,普通/53度,375ml*6/件,6.000,6,Y,1 +3362,1,NULL,3387,ZXZ015001,茅台2013小件,普通/53度,500ml*6/件,6.000,6,Y,1 +4634,8,10,4659,ZXZ015001,茅台2013小件,普通/53度,500ml*6/件,6.000,6,Y,1 +1033,7,NULL,1058,ZXZ015001,茅台2013小件,普通/53度,500ml*6/件,6.000,6,Y,1 +2168,6,NULL,2193,ZXZ015001,茅台2013小件,普通/53度,500ml*6/件,6.000,6,Y,1 +6788,7,NULL,1058,ZXZ016627,茅台2013小件,普通/53度,500ml*6/件,6.000,6,Y,1 +9225,8,10,7147,ZXZ016627,茅台2013小件,普通/53度,500ml*6/件,6.000,6,Y,1 +1034,7,NULL,1059,ZXZ017131,茅台十五年2019,普通/53度,500ml*6/件,6.000,6,Y,1 +2169,6,NULL,2194,ZXZ017131,茅台十五年2019,普通/53度,500ml*6/件,6.000,6,Y,1 +3363,1,NULL,3388,ZXZ017131,茅台十五年2019,普通/53度,500ml*6/件,6.000,6,Y,1 +4635,8,10,4660,ZXZ017131,茅台十五年2019,普通/53度,500ml*6/件,6.000,6,Y,1 +3364,1,NULL,3389,ZXZ017428,五粮液2020,普通/39度,500ml*6/件,6.000,6,Y,1 +4636,8,10,4661,ZXZ017428,五粮液2020,普通/39度,500ml*6/件,6.000,6,Y,1 +1035,7,NULL,1060,ZXZ017428,五粮液2020,普通/39度,500ml*6/件,6.000,6,Y,1 +2170,6,NULL,2195,ZXZ017428,五粮液2020,普通/39度,500ml*6/件,6.000,6,Y,1 +1036,7,NULL,1061,ZXZ017436,茅台同码2021,普通/53度,500ml*6/件,6.000,6,Y,1 +2171,6,NULL,2196,ZXZ017436,茅台同码2021,普通/53度,500ml*6/件,6.000,6,Y,1 +3365,1,NULL,3390,ZXZ017436,茅台同码2021,普通/53度,500ml*6/件,6.000,6,Y,1 +4637,8,10,4662,ZXZ017436,茅台同码2021,普通/53度,500ml*6/件,6.000,6,Y,1 +3366,1,NULL,3391,ZXZ017973,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +4638,8,10,4663,ZXZ017973,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +1037,7,NULL,1062,ZXZ017973,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +2172,6,NULL,2197,ZXZ017973,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +1038,7,NULL,1063,ZXZ018113,茅台十五年2007,普通/53度,500ml*6/件,6.000,6,Y,1 +2173,6,NULL,2198,ZXZ018113,茅台十五年2007,普通/53度,500ml*6/件,6.000,6,Y,1 +3367,1,NULL,3392,ZXZ018113,茅台十五年2007,普通/53度,500ml*6/件,6.000,6,Y,1 +4639,8,10,4664,ZXZ018113,茅台十五年2007,普通/53度,500ml*6/件,6.000,6,Y,1 +3368,1,NULL,3393,ZXZ019821,茅台十五年2005,普通/53度,500ml*6/件,6.000,6,Y,1 +4640,8,10,4665,ZXZ019821,茅台十五年2005,普通/53度,500ml*6/件,6.000,6,Y,1 +1039,7,NULL,1064,ZXZ019821,茅台十五年2005,普通/53度,500ml*6/件,6.000,6,Y,1 +2174,6,NULL,2199,ZXZ019821,茅台十五年2005,普通/53度,500ml*6/件,6.000,6,Y,1 +1040,7,NULL,1065,ZXZ021176,茅台鼠年原箱2020,普通/53度,500ml*6/件,6.000,6,Y,1 +2175,6,NULL,2200,ZXZ021176,茅台鼠年原箱2020,普通/53度,500ml*6/件,6.000,6,Y,1 +1041,7,NULL,1066,ZXZ021178,五粮液1618/2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2176,6,NULL,2201,ZXZ021178,五粮液1618/2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3369,1,NULL,3394,ZXZ021178,五粮液1618/2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4641,8,10,4666,ZXZ021178,五粮液1618/2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6789,7,NULL,1062,ZXZ021454,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +9226,8,10,7148,ZXZ021454,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +3370,1,NULL,3395,ZXZ021456,茅台小件2018,普通/53度,500ml*6/件,6.000,6,Y,1 +4642,8,10,4667,ZXZ021456,茅台小件2018,普通/53度,500ml*6/件,6.000,6,Y,1 +1042,7,NULL,1067,ZXZ021456,茅台小件2018,普通/53度,500ml*6/件,6.000,6,Y,1 +2177,6,NULL,2202,ZXZ021456,茅台小件2018,普通/53度,500ml*6/件,6.000,6,Y,1 +1043,7,NULL,1068,ZXZ022977,五粮液2005原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2178,6,NULL,2203,ZXZ022977,五粮液2005原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3371,1,NULL,3396,ZXZ022977,五粮液2005原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4643,8,10,4668,ZXZ022977,五粮液2005原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3372,1,NULL,3397,ZXZ022979,五粮夜2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4644,8,10,4669,ZXZ022979,五粮夜2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1044,7,NULL,1069,ZXZ022979,五粮夜2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2179,6,NULL,2204,ZXZ022979,五粮夜2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1045,7,NULL,1070,ZXZ023067,钓鱼台雅品2017原开,普通/53度,500ml*6/件,6.000,6,Y,1 +2180,6,NULL,2205,ZXZ023067,钓鱼台雅品2017原开,普通/53度,500ml*6/件,6.000,6,Y,1 +3373,1,NULL,3398,ZXZ023067,钓鱼台雅品2017原开,普通/53度,500ml*6/件,6.000,6,Y,1 +4645,8,10,4670,ZXZ023067,钓鱼台雅品2017原开,普通/53度,500ml*6/件,6.000,6,Y,1 +3374,1,NULL,3399,ZXZ023299,青花汾酒三十年2016原开,普通/48度,500ml*6/件,6.000,6,Y,1 +4646,8,10,4671,ZXZ023299,青花汾酒三十年2016原开,普通/48度,500ml*6/件,6.000,6,Y,1 +1046,7,NULL,1071,ZXZ023299,青花汾酒三十年2016原开,普通/48度,500ml*6/件,6.000,6,Y,1 +2181,6,NULL,2206,ZXZ023299,青花汾酒三十年2016原开,普通/48度,500ml*6/件,6.000,6,Y,1 +1047,7,NULL,1072,ZXZ023392,汾酒青花20年2024原箱,普通/42度,475ML*6/件,6.000,6,Y,1 +2182,6,NULL,2207,ZXZ023392,汾酒青花20年2024原箱,普通/42度,475ML*6/件,6.000,6,Y,1 +3375,1,NULL,3400,ZXZ023392,汾酒青花20年2024原箱,普通/42度,475ML*6/件,6.000,6,Y,1 +4647,8,10,4672,ZXZ023392,汾酒青花20年2024原箱,普通/42度,475ML*6/件,6.000,6,Y,1 +3376,1,NULL,3401,ZXZ023941,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4648,8,10,4673,ZXZ023941,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1048,7,NULL,1073,ZXZ023941,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2183,6,NULL,2208,ZXZ023941,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1049,7,NULL,1074,ZXZ024375,茅台十五年2017,普通/53度,500ml*6/件,6.000,6,Y,1 +2184,6,NULL,2209,ZXZ024375,茅台十五年2017,普通/53度,500ml*6/件,6.000,6,Y,1 +3377,1,NULL,3402,ZXZ024375,茅台十五年2017,普通/53度,500ml*6/件,6.000,6,Y,1 +3378,1,NULL,3403,ZXZ024715,茅台定制尊享2019原件,普通/53度,500ml*6/件,6.000,6,Y,1 +4649,8,10,4674,ZXZ024715,茅台定制尊享2019原件,普通/53度,500ml*6/件,6.000,6,Y,1 +1050,7,NULL,1075,ZXZ024715,茅台定制尊享2019原件,普通/53度,500ml*6/件,6.000,6,Y,1 +2185,6,NULL,2210,ZXZ024715,茅台定制尊享2019原件,普通/53度,500ml*6/件,6.000,6,Y,1 +1051,7,NULL,1076,ZXZ024864,青花汾二十2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2186,6,NULL,2211,ZXZ024864,青花汾二十2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3379,1,NULL,3404,ZXZ024864,青花汾二十2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4650,8,10,4675,ZXZ024864,青花汾二十2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3380,1,NULL,3405,ZXZ025086,五粮液2018原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +4651,8,10,4676,ZXZ025086,五粮液2018原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +1052,7,NULL,1077,ZXZ025086,五粮液2018原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +2187,6,NULL,2212,ZXZ025086,五粮液2018原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +1053,7,NULL,1078,ZXZ025087,五粮液2018原开,普通/39度,500ml*6/件,6.000,6,Y,1 +2188,6,NULL,2213,ZXZ025087,五粮液2018原开,普通/39度,500ml*6/件,6.000,6,Y,1 +3381,1,NULL,3406,ZXZ025087,五粮液2018原开,普通/39度,500ml*6/件,6.000,6,Y,1 +4652,8,10,4677,ZXZ025087,五粮液2018原开,普通/39度,500ml*6/件,6.000,6,Y,1 +1054,7,NULL,1079,ZXZ025166,泸州老窖特曲,普通/52度,500ml*6/件,6.000,6,Y,1 +2189,6,NULL,2214,ZXZ025166,泸州老窖特曲,普通/52度,500ml*6/件,6.000,6,Y,1 +1055,7,NULL,1080,ZXZ025308,剑南春2014原件,普通/52度,500ml*6/件,6.000,6,Y,1 +2190,6,NULL,2215,ZXZ025308,剑南春2014原件,普通/52度,500ml*6/件,6.000,6,Y,1 +3382,1,NULL,3407,ZXZ025308,剑南春2014原件,普通/52度,500ml*6/件,6.000,6,Y,1 +4653,8,10,4678,ZXZ025308,剑南春2014原件,普通/52度,500ml*6/件,6.000,6,Y,1 +1056,7,NULL,1081,ZXZ025310,五粮液45度,普通/45度,500ml*6/件,6.000,6,Y,1 +2191,6,NULL,2216,ZXZ025310,五粮液45度,普通/45度,500ml*6/件,6.000,6,Y,1 +1057,7,NULL,1082,ZXZ025319,复兴版青花2023原开,普通/48度,500ml*6/件,6.000,6,Y,1 +2192,6,NULL,2217,ZXZ025319,复兴版青花2023原开,普通/48度,500ml*6/件,6.000,6,Y,1 +3383,1,NULL,3408,ZXZ025319,复兴版青花2023原开,普通/48度,500ml*6/件,6.000,6,Y,1 +4654,8,10,4679,ZXZ025319,复兴版青花2023原开,普通/48度,500ml*6/件,6.000,6,Y,1 +9227,8,10,7149,ZXZ025627,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +6790,7,NULL,1073,ZXZ025627,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3384,1,NULL,3409,ZXZ026123,茅台公斤原件2024,普通/53度,1.00L*6/件,6.000,6,Y,1 +4655,8,10,4680,ZXZ026123,茅台公斤原件2024,普通/53度,1.00L*6/件,6.000,6,Y,1 +1058,7,NULL,1083,ZXZ026123,茅台公斤原件2024,普通/53度,1.00L*6/件,6.000,6,Y,1 +2193,6,NULL,2218,ZXZ026123,茅台公斤原件2024,普通/53度,1.00L*6/件,6.000,6,Y,1 +1059,7,NULL,1084,ZXZ026196,茅台小件2019,普通/53度,500ml*6/件,6.000,6,Y,1 +2194,6,NULL,2219,ZXZ026196,茅台小件2019,普通/53度,500ml*6/件,6.000,6,Y,1 +3385,1,NULL,3410,ZXZ026196,茅台小件2019,普通/53度,500ml*6/件,6.000,6,Y,1 +4656,8,10,4681,ZXZ026196,茅台小件2019,普通/53度,500ml*6/件,6.000,6,Y,1 +1060,7,NULL,1085,ZXZ026355,茅台小件2022,普通/53度,500ml*6/件,6.000,6,Y,1 +2195,6,NULL,2220,ZXZ026355,茅台小件2022,普通/53度,500ml*6/件,6.000,6,Y,1 +1061,7,NULL,1086,ZXZ026363,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2196,6,NULL,2221,ZXZ026363,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2162,6,NULL,2187,ZXZ026475,茅台小件2024,普通/53度,500ml*6/件,6.000,6,Y,1 +6791,7,NULL,1085,ZXZ026477,茅台小件2022,普通/53度,500ml*6/件,6.000,6,Y,1 +1062,7,NULL,1087,ZXZ026608,茅台2022年原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2197,6,NULL,2222,ZXZ026608,茅台2022年原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3386,1,NULL,3411,ZXZ026608,茅台2022年原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3343,1,NULL,3368,ZXZ026646,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +2158,6,NULL,2183,ZXZ026646,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +1063,7,NULL,1088,ZXZ026648,茅台精品2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2198,6,NULL,2223,ZXZ026648,茅台精品2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3387,1,NULL,3412,ZXZ026648,茅台精品2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3388,1,NULL,3413,ZXZ026650,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +4657,8,10,4682,ZXZ026650,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +1064,7,NULL,1089,ZXZ026650,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +2199,6,NULL,2224,ZXZ026650,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +6792,7,NULL,1089,ZXZ026651,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +9228,8,10,7150,ZXZ026651,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +9229,8,10,7151,ZXZ026653,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +6793,7,NULL,1089,ZXZ026653,茅台小可爱生日礼盒,普通/53度,100ML*6,6.000,6,Y,1 +6794,7,NULL,1086,ZXZ026675,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3389,1,NULL,3414,ZXZ026675,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4658,8,10,4683,ZXZ026675,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3390,1,NULL,3415,ZXZ026704,五粮液2019原开,普通/39度,500ml*6/件,6.000,6,Y,1 +4659,8,10,4684,ZXZ026704,五粮液2019原开,普通/39度,500ml*6/件,6.000,6,Y,1 +1065,7,NULL,1090,ZXZ026704,五粮液2019原开,普通/39度,500ml*6/件,6.000,6,Y,1 +2200,6,NULL,2225,ZXZ026704,五粮液2019原开,普通/39度,500ml*6/件,6.000,6,Y,1 +6795,7,NULL,1052,ZXZ026801,茅台小件2024,普通/53度,500ml*6/件,6.000,6,Y,1 +3391,1,NULL,3416,ZXZ026826,五粮液2021原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4660,8,10,4685,ZXZ026826,五粮液2021原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +1066,7,NULL,1091,ZXZ026826,五粮液2021原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2201,6,NULL,2226,ZXZ026826,五粮液2021原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2159,6,NULL,2184,ZXZ026841,茅台小件2026,普通/53度,500ml*6/件,6.000,6,Y,1 +3344,1,NULL,3369,ZXZ026841,茅台小件2026,普通/53度,500ml*6/件,6.000,6,Y,1 +3353,1,NULL,3378,ZXZ026856,五粮液原箱2025,普通/52度,500ml*6/件,6.000,6,Y,1 +2163,6,NULL,2188,ZXZ026856,五粮液原箱2025,普通/52度,500ml*6/件,6.000,6,Y,1 +1067,7,NULL,1092,ZXZ026858,五粮液2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2202,6,NULL,2227,ZXZ026858,五粮液2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3392,1,NULL,3417,ZXZ026858,五粮液2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4661,8,10,4686,ZXZ026858,五粮液2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3393,1,NULL,3418,ZXZ026878,茅台小件2020,普通/53度,500ml*6/件,6.000,6,Y,1 +4662,8,10,4687,ZXZ026878,茅台小件2020,普通/53度,500ml*6/件,6.000,6,Y,1 +1068,7,NULL,1093,ZXZ026878,茅台小件2020,普通/53度,500ml*6/件,6.000,6,Y,1 +2203,6,NULL,2228,ZXZ026878,茅台小件2020,普通/53度,500ml*6/件,6.000,6,Y,1 +6796,7,NULL,1093,ZXZ026879,茅台小件2020,普通/53度,500ml*6/件,6.000,6,Y,1 +9230,8,10,7152,ZXZ026879,茅台小件2020,普通/53度,500ml*6/件,6.000,6,Y,1 +9231,8,10,7153,ZXZ026903,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6797,7,NULL,1086,ZXZ026903,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6798,7,NULL,1048,ZXZ026926,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +9232,8,10,7154,ZXZ026926,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4663,8,10,4688,ZXZ026989,五粮液2026原件,普通/52度,500ml*6/件,6.000,6,Y,1 +1069,7,NULL,1094,ZXZ026989,五粮液2026原件,普通/52度,500ml*6/件,6.000,6,Y,1 +2204,6,NULL,2229,ZXZ026989,五粮液2026原件,普通/52度,500ml*6/件,6.000,6,Y,1 +3394,1,NULL,3419,ZXZ026989,五粮液2026原件,普通/52度,500ml*6/件,6.000,6,Y,1 +6799,7,NULL,1053,ZXZ026990,五粮液原箱2025,普通/52度,500ml*6/件,6.000,6,Y,1 +9233,8,10,7155,ZXZ026990,五粮液原箱2025,普通/52度,500ml*6/件,6.000,6,Y,1 +4664,8,10,4689,ZXZ026991,五粮液2025,普通/52度,500ml*6/件,6.000,6,Y,1 +3395,1,NULL,3420,ZXZ026991,五粮液2025,普通/52度,500ml*6/件,6.000,6,Y,1 +1070,7,NULL,1095,ZXZ026991,五粮液2025,普通/52度,500ml*6/件,6.000,6,Y,1 +2205,6,NULL,2230,ZXZ026991,五粮液2025,普通/52度,500ml*6/件,6.000,6,Y,1 +6800,7,NULL,1066,ZXZ027008,五粮液1618/2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9234,8,10,7156,ZXZ027008,五粮液1618/2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4665,8,10,4690,ZXZ027009,五粮液1618/2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +1071,7,NULL,1096,ZXZ027009,五粮液1618/2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2206,6,NULL,2231,ZXZ027009,五粮液1618/2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3396,1,NULL,3421,ZXZ027009,五粮液1618/2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6801,7,NULL,934,ZXZ027061,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6802,7,NULL,934,ZXZ027062,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9235,8,10,7157,ZXZ027062,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9236,8,10,7158,ZXZ027064,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6803,7,NULL,934,ZXZ027064,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6804,7,NULL,934,ZXZ027066,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9237,8,10,7159,ZXZ027066,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9238,8,10,7160,ZXZ027067,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6805,7,NULL,934,ZXZ027067,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6806,7,NULL,934,ZXZ027068,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9239,8,10,7161,ZXZ027068,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6807,7,NULL,934,ZXZ027069,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +3109,1,NULL,3134,ZXZ027069,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6808,7,NULL,934,ZXZ027070,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9240,8,10,7162,ZXZ027070,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9241,8,10,7163,ZXZ027071,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6809,7,NULL,934,ZXZ027071,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6810,7,NULL,934,ZXZ027072,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9242,8,10,7164,ZXZ027072,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9243,8,10,7165,ZXZ027073,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6811,7,NULL,934,ZXZ027073,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6812,7,NULL,934,ZXZ027074,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9244,8,10,7166,ZXZ027074,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9245,8,10,7167,ZXZ027075,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6813,7,NULL,934,ZXZ027075,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6814,7,NULL,934,ZXZ027076,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9246,8,10,7168,ZXZ027076,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9247,8,10,7169,ZXZ027077,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6815,7,NULL,934,ZXZ027077,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6816,7,NULL,934,ZXZ027078,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9248,8,10,7170,ZXZ027078,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9249,8,10,7171,ZXZ027079,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6817,7,NULL,934,ZXZ027079,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +2044,6,NULL,2069,ZXZ027080,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,6.000,6,Y,1 +1072,7,NULL,1097,ZXZ027081,茅台十五年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2207,6,NULL,2232,ZXZ027081,茅台十五年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3397,1,NULL,3422,ZXZ027081,茅台十五年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4666,8,10,4691,ZXZ027081,茅台十五年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2161,6,NULL,2186,ZXZ027084,茅台1935/2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6818,7,NULL,1051,ZXZ027085,茅台1935/2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2164,6,NULL,2189,ZXZ027086,贵州君品习酒2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6819,7,NULL,1054,ZXZ027087,贵州君品习酒2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9250,8,10,7172,ZXZ027087,贵州君品习酒2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +6820,7,NULL,1054,ZXZ027088,贵州君品习酒2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +3345,1,NULL,3370,ZXZ027088,贵州君品习酒2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9251,8,10,7173,ZXZ027089,贵州习酒1988原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +1073,7,NULL,1098,ZXZ027089,贵州习酒1988原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +2208,6,NULL,2233,ZXZ027089,贵州习酒1988原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +3354,1,NULL,3379,ZXZ027090,贵州习酒1988原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +6821,7,NULL,1098,ZXZ027090,贵州习酒1988原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4667,8,10,4692,ZXZ027169,奔富bin8红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1074,7,NULL,1099,ZXZ027169,奔富bin8红酒,14%vol,750ml*6/件,6.000,6,Y,1 +2209,6,NULL,2234,ZXZ027169,奔富bin8红酒,14%vol,750ml*6/件,6.000,6,Y,1 +3398,1,NULL,3423,ZXZ027169,奔富bin8红酒,14%vol,750ml*6/件,6.000,6,Y,1 +3399,1,NULL,3424,ZXZ027181,奔富28红酒,14%vol,750ml*6/件,6.000,6,Y,1 +4668,8,10,4693,ZXZ027181,奔富28红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1075,7,NULL,1100,ZXZ027181,奔富28红酒,14%vol,750ml*6/件,6.000,6,Y,1 +2210,6,NULL,2235,ZXZ027181,奔富28红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1076,7,NULL,1101,ZXZ027193,奔富128红酒,14%vol,750ml*6/件,6.000,6,Y,1 +2211,6,NULL,2236,ZXZ027193,奔富128红酒,14%vol,750ml*6/件,6.000,6,Y,1 +3400,1,NULL,3425,ZXZ027193,奔富128红酒,14%vol,750ml*6/件,6.000,6,Y,1 +4669,8,10,4694,ZXZ027193,奔富128红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1077,7,NULL,1102,ZXZ027205,奔富389红酒,14%vol,750ml*6/件,6.000,6,Y,1 +2212,6,NULL,2237,ZXZ027205,奔富389红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1078,7,NULL,1103,ZXZ027217,奔富407红酒,14%vol,750ml*6/件,6.000,6,Y,1 +2213,6,NULL,2238,ZXZ027217,奔富407红酒,14%vol,750ml*6/件,6.000,6,Y,1 +4670,8,10,4695,ZXZ027217,奔富407红酒,14%vol,750ml*6/件,6.000,6,Y,1 +3401,1,NULL,3426,ZXZ027217,奔富407红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1079,7,NULL,1104,ZXZ027229,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2214,6,NULL,2239,ZXZ027229,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4671,8,10,4696,ZXZ027229,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6822,7,NULL,1104,ZXZ027230,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3402,1,NULL,3427,ZXZ027231,汾酒青花20年2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9252,8,10,7174,ZXZ027231,汾酒青花20年2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1080,7,NULL,1105,ZXZ027231,汾酒青花20年2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2215,6,NULL,2240,ZXZ027231,汾酒青花20年2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6823,7,NULL,1105,ZXZ027232,汾酒青花20年2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6824,7,NULL,1104,ZXZ027233,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9253,8,10,7175,ZXZ027233,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9254,8,10,7176,ZXZ027234,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6825,7,NULL,1104,ZXZ027234,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6826,7,NULL,1104,ZXZ027235,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9255,8,10,7177,ZXZ027235,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9256,8,10,7178,ZXZ027236,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6827,7,NULL,1104,ZXZ027236,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1081,7,NULL,1106,ZXZ027237,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +2216,6,NULL,2241,ZXZ027237,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +3352,1,NULL,3377,ZXZ027237,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +6828,7,NULL,1106,ZXZ027239,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +9257,8,10,7179,ZXZ027239,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +1082,7,NULL,1107,ZXZ027250,老白汾酒原开2025,普通/53度,475ML*6/件,6.000,6,Y,1 +2217,6,NULL,2242,ZXZ027250,老白汾酒原开2025,普通/53度,475ML*6/件,6.000,6,Y,1 +3346,1,NULL,3371,ZXZ027251,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6829,7,NULL,1104,ZXZ027251,汾酒青花20年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4672,8,10,4697,ZXZ027252,青花郎原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +1083,7,NULL,1108,ZXZ027252,青花郎原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +2218,6,NULL,2243,ZXZ027252,青花郎原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +3403,1,NULL,3428,ZXZ027252,青花郎原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +6830,7,NULL,1108,ZXZ027253,青花郎原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +9258,8,10,7180,ZXZ027253,青花郎原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4673,8,10,4698,ZXZ027255,红花郎红十2025原开,普通/53度,500ml*6/件,6.000,6,Y,1 +3404,1,NULL,3429,ZXZ027255,红花郎红十2025原开,普通/53度,500ml*6/件,6.000,6,Y,1 +1084,7,NULL,1109,ZXZ027255,红花郎红十2025原开,普通/53度,500ml*6/件,6.000,6,Y,1 +2219,6,NULL,2244,ZXZ027255,红花郎红十2025原开,普通/53度,500ml*6/件,6.000,6,Y,1 +1085,7,NULL,1110,ZXZ027256,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +2220,6,NULL,2245,ZXZ027256,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +4674,8,10,4699,ZXZ027256,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +3405,1,NULL,3430,ZXZ027256,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +9259,8,10,7181,ZXZ027257,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +6831,7,NULL,1110,ZXZ027257,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +6832,7,NULL,1110,ZXZ027258,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +9260,8,10,7182,ZXZ027258,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +9261,8,10,7183,ZXZ027259,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +6833,7,NULL,1110,ZXZ027259,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +6834,7,NULL,1110,ZXZ027260,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +9262,8,10,7184,ZXZ027260,玛安蒂云峰珍藏梅洛红葡萄酒2024,14%vol,750ml*6/件,6.000,6,Y,1 +3406,1,NULL,3431,ZXZ027267,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +1086,7,NULL,1111,ZXZ027267,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +2221,6,NULL,2246,ZXZ027267,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +4675,8,10,4700,ZXZ027267,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +6835,7,NULL,1111,ZXZ027268,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +9263,8,10,7185,ZXZ027268,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +9264,8,10,7186,ZXZ027269,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +6836,7,NULL,1111,ZXZ027269,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +6837,7,NULL,1111,ZXZ027270,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +9265,8,10,7187,ZXZ027270,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +9266,8,10,7188,ZXZ027271,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +6838,7,NULL,1111,ZXZ027271,玛安蒂丹峰佳美娜红葡萄酒2021,14%vol,750ml*6/件,6.000,6,Y,1 +4676,8,10,4701,ZXZ027272,玛安蒂C-15赤霞珠红葡萄酒2022,14%vol,750ml*6/件,6.000,6,Y,1 +3407,1,NULL,3432,ZXZ027272,玛安蒂C-15赤霞珠红葡萄酒2022,14%vol,750ml*6/件,6.000,6,Y,1 +1087,7,NULL,1112,ZXZ027272,玛安蒂C-15赤霞珠红葡萄酒2022,14%vol,750ml*6/件,6.000,6,Y,1 +2222,6,NULL,2247,ZXZ027272,玛安蒂C-15赤霞珠红葡萄酒2022,14%vol,750ml*6/件,6.000,6,Y,1 +6839,7,NULL,1112,ZXZ027273,玛安蒂C-15赤霞珠红葡萄酒2022,14%vol,750ml*6/件,6.000,6,Y,1 +9267,8,10,7189,ZXZ027273,玛安蒂C-15赤霞珠红葡萄酒2022,14%vol,750ml*6/件,6.000,6,Y,1 +4677,8,10,4702,ZXZ027281,奔富bin6红酒,14%vol,750ml*6/件,6.000,6,Y,1 +1088,7,NULL,1113,ZXZ027281,奔富bin6红酒,14%vol,750ml*6/件,6.000,6,Y,1 +2223,6,NULL,2248,ZXZ027281,奔富bin6红酒,14%vol,750ml*6/件,6.000,6,Y,1 +3408,1,NULL,3433,ZXZ027281,奔富bin6红酒,14%vol,750ml*6/件,6.000,6,Y,1 +3409,1,NULL,3434,ZXZ027338,国窖2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +1089,7,NULL,1114,ZXZ027338,国窖2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2224,6,NULL,2249,ZXZ027338,国窖2022原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6840,7,NULL,1053,ZXZ027339,五粮液原箱2025,普通/52度,500ml*6/件,6.000,6,Y,1 +9268,8,10,7190,ZXZ027339,五粮液原箱2025,普通/52度,500ml*6/件,6.000,6,Y,1 +6841,7,NULL,1086,ZXZ027352,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1090,7,NULL,1115,ZXZ027359,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +2225,6,NULL,2250,ZXZ027359,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +1091,7,NULL,1116,ZXZ027361,茅台三十年2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +2226,6,NULL,2251,ZXZ027361,茅台三十年2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +1092,7,NULL,1117,ZXZ027367,汾酒青花三十复兴版2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2227,6,NULL,2252,ZXZ027367,汾酒青花三十复兴版2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4678,8,10,4703,ZXZ027367,汾酒青花三十复兴版2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3410,1,NULL,3435,ZXZ027367,汾酒青花三十复兴版2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1093,7,NULL,1118,ZXZ027424,茅台精品2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2228,6,NULL,2253,ZXZ027424,茅台精品2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1094,7,NULL,1119,ZXZ027437,汾酒青花三十2025复兴版原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +2229,6,NULL,2254,ZXZ027437,汾酒青花三十2025复兴版原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +3411,1,NULL,3436,ZXZ027437,汾酒青花三十2025复兴版原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +4679,8,10,4704,ZXZ027437,汾酒青花三十2025复兴版原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +4680,8,10,4705,ZXZ027438,汾酒青花26原箱2025,普通/42度,500ml*6/件,6.000,6,Y,1 +1095,7,NULL,1120,ZXZ027438,汾酒青花26原箱2025,普通/42度,500ml*6/件,6.000,6,Y,1 +2230,6,NULL,2255,ZXZ027438,汾酒青花26原箱2025,普通/42度,500ml*6/件,6.000,6,Y,1 +3412,1,NULL,3437,ZXZ027438,汾酒青花26原箱2025,普通/42度,500ml*6/件,6.000,6,Y,1 +6842,7,NULL,1120,ZXZ027439,汾酒青花26原箱2025,普通/42度,500ml*6/件,6.000,6,Y,1 +9269,8,10,7191,ZXZ027439,汾酒青花26原箱2025,普通/42度,500ml*6/件,6.000,6,Y,1 +3358,1,NULL,3383,ZXZ027440,汾酒青花26原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +1096,7,NULL,1121,ZXZ027440,汾酒青花26原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +2231,6,NULL,2256,ZXZ027440,汾酒青花26原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +9270,8,10,7192,ZXZ027441,汾酒青花26原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +6843,7,NULL,1121,ZXZ027441,汾酒青花26原箱2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4681,8,10,4706,ZXZ027462,汾酒青花三十复兴版2025原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +1097,7,NULL,1122,ZXZ027462,汾酒青花三十复兴版2025原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +2232,6,NULL,2257,ZXZ027462,汾酒青花三十复兴版2025原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +3413,1,NULL,3438,ZXZ027462,汾酒青花三十复兴版2025原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +6844,7,NULL,1122,ZXZ027463,汾酒青花三十复兴版2025原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +9271,8,10,7193,ZXZ027463,汾酒青花三十复兴版2025原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +4682,8,10,4707,ZXZ027474,茅台2016原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3414,1,NULL,3439,ZXZ027474,茅台2016原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1098,7,NULL,1123,ZXZ027474,茅台2016原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2233,6,NULL,2258,ZXZ027474,茅台2016原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1099,7,NULL,1124,ZXZ027526,五粮液2023原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2234,6,NULL,2259,ZXZ027526,五粮液2023原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4683,8,10,4708,ZXZ027526,五粮液2023原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3415,1,NULL,3440,ZXZ027526,五粮液2023原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3416,1,NULL,3441,ZXZ027527,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +1100,7,NULL,1125,ZXZ027527,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2235,6,NULL,2260,ZXZ027527,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4684,8,10,4709,ZXZ027527,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6845,7,NULL,1091,ZXZ027528,五粮液2021原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9272,8,10,7194,ZXZ027528,五粮液2021原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9273,8,10,7195,ZXZ027529,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6846,7,NULL,1125,ZXZ027529,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4685,8,10,4710,ZXZ027530,五粮液2019原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3417,1,NULL,3442,ZXZ027530,五粮液2019原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +1101,7,NULL,1126,ZXZ027530,五粮液2019原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2236,6,NULL,2261,ZXZ027530,五粮液2019原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +1102,7,NULL,1127,ZXZ027531,五粮液2018原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +2237,6,NULL,2262,ZXZ027531,五粮液2018原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4686,8,10,4711,ZXZ027531,五粮液2018原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3418,1,NULL,3443,ZXZ027531,五粮液2018原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9274,8,10,7196,ZXZ027532,五粮液2019原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6847,7,NULL,1126,ZXZ027532,五粮液2019原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +6848,7,NULL,1127,ZXZ027533,五粮液2018原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9275,8,10,7197,ZXZ027533,五粮液2018原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3419,1,NULL,3444,ZXZ027534,五粮液2019原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1103,7,NULL,1128,ZXZ027534,五粮液2019原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2238,6,NULL,2263,ZXZ027534,五粮液2019原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4687,8,10,4712,ZXZ027534,五粮液2019原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4688,8,10,4713,ZXZ027535,五粮液2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3420,1,NULL,3445,ZXZ027535,五粮液2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1104,7,NULL,1129,ZXZ027535,五粮液2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2239,6,NULL,2264,ZXZ027535,五粮液2010原开,普通/52度,500ml*6/件,6.000,6,Y,1 +6849,7,NULL,1073,ZXZ027536,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +9276,8,10,7198,ZXZ027536,五粮液2017原开,普通/52度,500ml*6/件,6.000,6,Y,1 +6850,7,NULL,1128,ZXZ027537,五粮液2019原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4689,8,10,4714,ZXZ027538,五粮液2021原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +1105,7,NULL,1130,ZXZ027538,五粮液2021原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +2240,6,NULL,2265,ZXZ027538,五粮液2021原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +3421,1,NULL,3446,ZXZ027538,五粮液2021原箱,普通/39度,500ml*6/件,6.000,6,Y,1 +6851,7,NULL,1125,ZXZ027568,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9277,8,10,7199,ZXZ027568,五粮液2020原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +4690,8,10,4715,ZXZ027582,五粮液2014原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3422,1,NULL,3447,ZXZ027582,五粮液2014原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1106,7,NULL,1131,ZXZ027582,五粮液2014原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2241,6,NULL,2266,ZXZ027582,五粮液2014原开,普通/52度,500ml*6/件,6.000,6,Y,1 +1107,7,NULL,1132,ZXZ027583,五粮液2016原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2242,6,NULL,2267,ZXZ027583,五粮液2016原开,普通/52度,500ml*6/件,6.000,6,Y,1 +4691,8,10,4716,ZXZ027583,五粮液2016原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3423,1,NULL,3448,ZXZ027583,五粮液2016原开,普通/52度,500ml*6/件,6.000,6,Y,1 +9278,8,10,7200,ZXZ027599,茅台小件2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6852,7,NULL,1049,ZXZ027599,茅台小件2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6853,7,NULL,1092,ZXZ027626,五粮液2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9279,8,10,7201,ZXZ027626,五粮液2024原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +3424,1,NULL,3449,ZXZ027627,茅台精品原箱2024,普通/53度,500ml*6/件,6.000,6,Y,1 +1108,7,NULL,1133,ZXZ027627,茅台精品原箱2024,普通/53度,500ml*6/件,6.000,6,Y,1 +2243,6,NULL,2268,ZXZ027627,茅台精品原箱2024,普通/53度,500ml*6/件,6.000,6,Y,1 +4692,8,10,4717,ZXZ027627,茅台精品原箱2024,普通/53度,500ml*6/件,6.000,6,Y,1 +3425,1,NULL,3450,ZXZ027644,2024年汾酒青花25,普通/42度,475ML*6/件,6.000,6,Y,1 +1109,7,NULL,1134,ZXZ027644,2024年汾酒青花25,普通/42度,475ML*6/件,6.000,6,Y,1 +2244,6,NULL,2269,ZXZ027644,2024年汾酒青花25,普通/42度,475ML*6/件,6.000,6,Y,1 +1110,7,NULL,1135,ZXZ027645,茅台2019原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2245,6,NULL,2270,ZXZ027645,茅台2019原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4693,8,10,4718,ZXZ027645,茅台2019原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3426,1,NULL,3451,ZXZ027645,茅台2019原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6854,7,NULL,1133,ZXZ027646,茅台精品原箱2024,普通/53度,500ml*6/件,6.000,6,Y,1 +4694,8,10,4719,ZXZ027675,汾酒巴拿马黑坛20年2025原件,普通/42度,475ML*6/件,6.000,6,Y,1 +1111,7,NULL,1136,ZXZ027675,汾酒巴拿马黑坛20年2025原件,普通/42度,475ML*6/件,6.000,6,Y,1 +2246,6,NULL,2271,ZXZ027675,汾酒巴拿马黑坛20年2025原件,普通/42度,475ML*6/件,6.000,6,Y,1 +3427,1,NULL,3452,ZXZ027675,汾酒巴拿马黑坛20年2025原件,普通/42度,475ML*6/件,6.000,6,Y,1 +6855,7,NULL,1136,ZXZ027676,汾酒巴拿马黑坛20年2025原件,普通/42度,475ML*6/件,6.000,6,Y,1 +9280,8,10,7202,ZXZ027676,汾酒巴拿马黑坛20年2025原件,普通/42度,475ML*6/件,6.000,6,Y,1 +4695,8,10,4720,ZXZ027739,茅台2010原件,普通/43度,500ml*6/件,6.000,6,Y,1 +3428,1,NULL,3453,ZXZ027739,茅台2010原件,普通/43度,500ml*6/件,6.000,6,Y,1 +1112,7,NULL,1137,ZXZ027739,茅台2010原件,普通/43度,500ml*6/件,6.000,6,Y,1 +2247,6,NULL,2272,ZXZ027739,茅台2010原件,普通/43度,500ml*6/件,6.000,6,Y,1 +1113,7,NULL,1138,ZXZ027740,五粮液2025原开,普通/52度,500ml*6/件,6.000,6,Y,1 +2248,6,NULL,2273,ZXZ027740,五粮液2025原开,普通/52度,500ml*6/件,6.000,6,Y,1 +3429,1,NULL,3454,ZXZ027740,五粮液2025原开,普通/52度,500ml*6/件,6.000,6,Y,1 +6856,7,NULL,1086,ZXZ027754,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4696,8,10,4721,ZXZ027789,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6857,7,NULL,1115,ZXZ027789,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +3430,1,NULL,3455,ZXZ027789,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6858,7,NULL,1115,ZXZ027790,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6859,7,NULL,1115,ZXZ027791,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6860,7,NULL,1115,ZXZ027792,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +9281,8,10,7203,ZXZ027792,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +9282,8,10,7204,ZXZ027793,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6861,7,NULL,1115,ZXZ027793,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6862,7,NULL,1115,ZXZ027794,茅台十五年原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +4697,8,10,4722,ZXZ027795,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3431,1,NULL,3456,ZXZ027795,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1114,7,NULL,1139,ZXZ027795,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2249,6,NULL,2274,ZXZ027795,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6863,7,NULL,1139,ZXZ027796,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6864,7,NULL,1139,ZXZ027797,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6865,7,NULL,1139,ZXZ027798,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6866,7,NULL,1139,ZXZ027799,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9283,8,10,7205,ZXZ027799,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6867,7,NULL,1139,ZXZ027800,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9284,8,10,7206,ZXZ027812,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +1115,7,NULL,1140,ZXZ027812,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +2250,6,NULL,2275,ZXZ027812,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +9285,8,10,7207,ZXZ027813,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +6868,7,NULL,1140,ZXZ027813,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +6869,7,NULL,1140,ZXZ027814,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +3355,1,NULL,3380,ZXZ027814,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +6870,7,NULL,1140,ZXZ027815,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +9286,8,10,7208,ZXZ027815,国窖2025原件,普通/52度,500ml*6/件,6.000,6,Y,1 +2251,6,NULL,2276,ZXZ027816,钓鱼台双羽酒,普通/53度,500ml*6/件,6.000,6,Y,1 +1116,7,NULL,1141,ZXZ027816,钓鱼台双羽酒,普通/53度,500ml*6/件,6.000,6,Y,1 +1117,7,NULL,1142,ZXZ027853,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2252,6,NULL,2277,ZXZ027853,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +2253,6,NULL,2278,ZXZ027860,茅台公斤2026原件,普通/53度,1.00L*6/件,6.000,6,Y,1 +1118,7,NULL,1143,ZXZ027860,茅台公斤2026原件,普通/53度,1.00L*6/件,6.000,6,Y,1 +6871,7,NULL,1139,ZXZ027900,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6872,7,NULL,1139,ZXZ027901,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6873,7,NULL,1139,ZXZ027902,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6874,7,NULL,1139,ZXZ027903,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6875,7,NULL,1139,ZXZ027904,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1119,7,NULL,1144,ZXZ027915,2011年鸟巢原开,普通/53度,500ml*6/件,6.000,6,Y,1 +2254,6,NULL,2279,ZXZ027915,2011年鸟巢原开,普通/53度,500ml*6/件,6.000,6,Y,1 +6876,7,NULL,1139,ZXZ027923,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6877,7,NULL,1139,ZXZ027924,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6878,7,NULL,1142,ZXZ027927,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6879,7,NULL,1139,ZXZ027928,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6880,7,NULL,1142,ZXZ027929,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6881,7,NULL,1142,ZXZ027930,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4698,8,10,4723,ZXZ027946,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +2255,6,NULL,2280,ZXZ027946,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +3432,1,NULL,3457,ZXZ027946,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +1120,7,NULL,1145,ZXZ027946,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +6882,7,NULL,1145,ZXZ027947,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +9287,8,10,7209,ZXZ027947,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +9288,8,10,7210,ZXZ027948,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +6883,7,NULL,1145,ZXZ027948,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +6884,7,NULL,1145,ZXZ027949,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +9289,8,10,7211,ZXZ027949,茅台生日礼盒2024,普通/53度,100ML*6,6.000,6,Y,1 +9290,8,10,7212,ZXZ027950,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6885,7,NULL,1139,ZXZ027950,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6886,7,NULL,1139,ZXZ027951,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9291,8,10,7213,ZXZ027951,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6887,7,NULL,1139,ZXZ027952,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6888,7,NULL,1139,ZXZ027953,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6889,7,NULL,1139,ZXZ027954,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6890,7,NULL,1139,ZXZ027955,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6891,7,NULL,1139,ZXZ027956,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6892,7,NULL,1139,ZXZ027957,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6893,7,NULL,1139,ZXZ027958,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6894,7,NULL,1139,ZXZ027959,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6895,7,NULL,1139,ZXZ027960,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +6896,7,NULL,1139,ZXZ027961,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +1121,7,NULL,1146,ZXZ027962,茅台精品原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +2256,6,NULL,2281,ZXZ027962,茅台精品原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6897,7,NULL,1146,ZXZ027963,茅台精品原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +6898,7,NULL,1146,ZXZ027964,茅台精品原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +9292,8,10,7214,ZXZ028050,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9293,8,10,7215,ZXZ028105,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4699,8,10,4724,ZXZ028138,茅台2024原箱,普通/43度,500ml*6/件,6.000,6,Y,1 +3433,1,NULL,3458,ZXZ028138,茅台2024原箱,普通/43度,500ml*6/件,6.000,6,Y,1 +3434,1,NULL,3459,ZXZ028276,汾二十五2025原箱,普通/42度,475ML*6/件,6.000,6,Y,1 +4700,8,10,4725,ZXZ028276,汾二十五2025原箱,普通/42度,475ML*6/件,6.000,6,Y,1 +9294,8,10,7216,ZXZ028371,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3435,1,NULL,3460,ZXZ028415,茅台香溢五洲2021原开,普通/53度,500ml*6/件,6.000,6,Y,1 +3436,1,NULL,3461,ZXZ028419,茅台蛇年2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +4701,8,10,4726,ZXZ028419,茅台蛇年2025原件,普通/53度,500ml*6/件,6.000,6,Y,1 +4702,8,10,4727,ZXZ028484,茅台兔年原箱2022,普通/53度,500ml*6/件,6.000,6,Y,1 +3437,1,NULL,3462,ZXZ028484,茅台兔年原箱2022,普通/53度,500ml*6/件,6.000,6,Y,1 +9295,8,10,7217,ZXZ028513,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9296,8,10,7218,ZXZ028514,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9297,8,10,7219,ZXZ028515,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9298,8,10,7220,ZXZ028518,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9299,8,10,7221,ZXZ028520,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3438,1,NULL,3463,ZXZ028522,茅台精品2023原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9300,8,10,7222,ZXZ028529,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +3439,1,NULL,3464,ZXZ028573,五粮液2012/1618,普通/52度,500ml*6/件,6.000,6,Y,1 +4703,8,10,4728,ZXZ028573,五粮液2012/1618,普通/52度,500ml*6/件,6.000,6,Y,1 +4704,8,10,4729,ZXZ028631,茅台龙年小件2024,普通/53度,500ml*6/件,6.000,6,Y,1 +3440,1,NULL,3465,ZXZ028631,茅台龙年小件2024,普通/53度,500ml*6/件,6.000,6,Y,1 +3441,1,NULL,3466,ZXZ028633,茅台精品小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4705,8,10,4730,ZXZ028633,茅台精品小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4706,8,10,4731,ZXZ028635,茅台精品原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +3442,1,NULL,3467,ZXZ028635,茅台精品原件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +3443,1,NULL,3468,ZXZ028636,茅台精品原箱2026,普通/53度,500ml*6/件,6.000,6,Y,1 +9301,8,10,7223,ZXZ028639,茅台小件2026,普通/53度,500ml*6/件,6.000,6,Y,1 +3444,1,NULL,3469,ZXZ028672,茅台2023年原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4707,8,10,4732,ZXZ028672,茅台2023年原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9302,8,10,7224,ZXZ028673,茅台2023年原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9303,8,10,7225,ZXZ028679,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4708,8,10,4733,ZXZ028693,茅台精品2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3445,1,NULL,3470,ZXZ028693,茅台精品2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9304,8,10,7226,ZXZ028700,茅台2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3446,1,NULL,3471,ZXZ028701,茅台十五年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4709,8,10,4734,ZXZ028702,茅台十五年2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4710,8,10,4735,ZXZ028754,茅台十五年2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +3447,1,NULL,3472,ZXZ028754,茅台十五年2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +3448,1,NULL,3473,ZXZ028755,茅台十五年2023原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4711,8,10,4736,ZXZ028755,茅台十五年2023原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9305,8,10,7227,ZXZ028757,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3449,1,NULL,3474,ZXZ028758,茅台2022年原箱,普通/53度,500ml/单品,6.000,,N, +3450,1,NULL,3475,ZXZ028771,茅台2013原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4712,8,10,4737,ZXZ028771,茅台2013原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4713,8,10,4738,ZXZ028910,汾酒青花20年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3451,1,NULL,3476,ZXZ028910,汾酒青花20年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3452,1,NULL,3477,ZXZ028911,汾酒青花20年2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4714,8,10,4739,ZXZ028911,汾酒青花20年2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4715,8,10,4740,ZXZ028933,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3453,1,NULL,3478,ZXZ028933,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +3454,1,NULL,3479,ZXZ028934,茅台小件2024,普通/53度,500ml*6/件,6.000,6,Y,1 +4716,8,10,4741,ZXZ028934,茅台小件2024,普通/53度,500ml*6/件,6.000,6,Y,1 +9306,8,10,7228,ZXZ028997,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +9307,8,10,7229,ZXZ028998,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +9308,8,10,7230,ZXZ028999,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +4717,8,10,4742,ZXZ029003,钓鱼台V20/2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +3455,1,NULL,3480,ZXZ029003,钓鱼台V20/2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +3456,1,NULL,3481,ZXZ029004,钓鱼台V20/2022原开,普通/53度,500ml*6/件,6.000,6,Y,1 +4718,8,10,4743,ZXZ029004,钓鱼台V20/2022原开,普通/53度,500ml*6/件,6.000,6,Y,1 +4719,8,10,4744,ZXZ029024,老白汾酒2025年原件,普通/53度,475ML*6/件,6.000,6,Y,1 +3457,1,NULL,3482,ZXZ029024,老白汾酒2025年原件,普通/53度,475ML*6/件,6.000,6,Y,1 +9309,8,10,7231,ZXZ029025,汾酒青花20年2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9310,8,10,7232,ZXZ029026,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +9311,8,10,7233,ZXZ029027,汾酒青花20年2026原箱,普通/42度,500ml*6/件,6.000,6,Y,1 +3458,1,NULL,3483,ZXZ029028,老白汾酒2025年原件,普通/53度,500ml/单品,6.000,,N, +3459,1,NULL,3484,ZXZ029029,老白汾酒2024年原件,普通/53度,500ml/单品,6.000,,N, +4720,8,10,4745,ZXZ029029,老白汾酒2024年原件,普通/53度,500ml/单品,6.000,,N, +3460,1,NULL,3485,ZXZ029034,茅台五十年2026原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9312,8,10,7234,ZXZ029051,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9313,8,10,7235,ZXZ029056,茅台精品2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9314,8,10,7236,ZXZ029058,茅台精品2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4721,8,10,4746,ZXZ029122,茅台人民大会堂特供陈酿2006小件,普通/53度,500ml*6/件,6.000,6,Y,1 +4722,8,10,4747,ZXZ029144,茅台十五年2021,普通/53度,500ml*6/件,6.000,6,Y,1 +9315,8,10,7237,ZXZ029145,茅台十五年2021,普通/53度,500ml*6/件,6.000,6,Y,1 +9316,8,10,7238,ZXZ029146,茅台十五年2021,普通/53度,500ml*6/件,6.000,6,Y,1 +9317,8,10,7239,ZXZ029147,茅台十五年2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9318,8,10,7240,ZXZ029148,茅台十五年2021原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4723,8,10,4748,ZXZ029184,茅台2015原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4724,8,10,4749,ZXZ029185,茅台2018原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4725,8,10,4750,ZXZ029186,茅台五星原件2015,普通/53度,500ml*6/件,6.000,6,Y,1 +4726,8,10,4751,ZXZ029187,茅台精品原件2018,普通/53度,500ml*6/件,6.000,6,Y,1 +4727,8,10,4752,ZXZ029188,茅台2015小件,普通/53度,500ml*6/件,6.000,6,Y,1 +9319,8,10,7241,ZXZ029190,茅台小件2014,普通/53度,500ml*6/件,6.000,6,Y,1 +4728,8,10,4753,ZXZ029191,茅台小件2022,普通/53度,500ml*6/件,6.000,6,Y,1 +4729,8,10,4754,ZXZ029336,茅台精品2022原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9320,8,10,7242,ZXZ029339,茅台小件2025,普通/53度,500ml*6/件,6.000,6,Y,1 +9321,8,10,7243,ZXZ029347,茅台精品2022原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4730,8,10,4755,ZXZ029394,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9322,8,10,7244,ZXZ029397,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9323,8,10,7245,ZXZ029408,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9324,8,10,7246,ZXZ029411,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9325,8,10,7247,ZXZ029415,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9326,8,10,7248,ZXZ029416,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9327,8,10,7249,ZXZ029419,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9328,8,10,7250,ZXZ029420,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9329,8,10,7251,ZXZ029421,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9330,8,10,7252,ZXZ029422,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9331,8,10,7253,ZXZ029423,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9332,8,10,7254,ZXZ029424,茅台2020原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4731,8,10,4756,ZXZ029444,汉酱2022原箱,普通/51度,500ml*6/件,6.000,6,Y,1 +4732,8,10,4757,ZXZ029451,茅台2026原箱,普通/53度,1.00L*6/件,6.000,6,Y,1 +4733,8,10,4758,ZXZ029452,茅台公斤原件2025,普通/53度,1.00L*6/件,6.000,6,Y,1 +9333,8,10,7255,ZXZ029453,茅台公斤原件2025,普通/53度,1.00L*6/件,6.000,6,Y,1 +4734,8,10,4759,ZXZ029454,茅台十五年2024原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9334,8,10,7256,ZXZ029461,茅台十五年2019,普通/53度,500ml*6/件,6.000,6,Y,1 +9335,8,10,7257,ZXZ029468,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9336,8,10,7258,ZXZ029477,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9337,8,10,7259,ZXZ029478,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9338,8,10,7260,ZXZ029493,茅台十五年2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9339,8,10,7261,ZXZ029494,茅台十五年2022原件,普通/53度,500ml*6/件,6.000,6,Y,1 +9340,8,10,7262,ZXZ029542,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9341,8,10,7263,ZXZ029543,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9342,8,10,7264,ZXZ029548,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4735,8,10,4760,ZXZ029595,茅台1935/2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9343,8,10,7265,ZXZ029596,茅台2025原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4736,8,10,4761,ZXZ029597,茅台2022年原箱,普通/53度,880ml*6/件,6.000,6,Y,1 +4737,8,10,4762,ZXZ029598,茅台2023原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4738,8,10,4763,ZXZ029619,五粮液2025原箱,普通/52度,500ml*6/件,6.000,6,Y,1 +9344,8,10,7266,ZXZ029669,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9345,8,10,7267,ZXZ029715,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +9346,8,10,7268,ZXZ029716,茅台2026原箱,普通/53度,500ml*6/件,6.000,6,Y,1 +4627,8,10,4652,ZXZ027440,汾酒青花26原箱2025,普通/53度,500ml*6/件,5.000,6,N, +4628,8,10,4653,ZXZ027644,2024年汾酒青花25,普通/42度,475ML*6/件,5.000,6,N, +3359,1,NULL,3384,ZXZ027807,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +4629,8,10,4654,ZXZ027807,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +1030,7,NULL,1055,ZXZ027807,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +2165,6,NULL,2190,ZXZ027807,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +6785,7,NULL,1055,ZXZ027808,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +9222,8,10,7144,ZXZ027808,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +6786,7,NULL,1055,ZXZ027809,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +9223,8,10,7145,ZXZ027809,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +6787,7,NULL,1055,ZXZ027810,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +9224,8,10,7146,ZXZ027810,贵州茅台酒2025,普通/53度,200mL*5/件,5.000,5,Y,1 +4630,8,10,4655,ZXZ029583,小白条2020,普通/53度,50ML*5,5.000,5,Y,1 +4631,8,10,4656,ZXZ029584,茅台小金条2020,普通/53度,50ML*5,5.000,5,Y,1 +1027,7,NULL,1052,ZXZ026475,茅台小件2024,普通/53度,500ml*6/件,4.000,6,N, +1028,7,NULL,1053,ZXZ026856,五粮液原箱2025,普通/52度,500ml*6/件,4.000,6,N, +9219,8,10,7141,ZXZ027061,钓鱼台景泰蓝酒咖啡金2021原件,普通/53度,500ml*6/件,4.000,6,N, +1029,7,NULL,1054,ZXZ027086,贵州君品习酒2025原件,普通/53度,500ml*6/件,4.000,6,N, +4621,8,10,4646,ZXZ027090,贵州习酒1988原箱2025,普通/53度,500ml*6/件,4.000,6,N, +4622,8,10,4647,ZXZ027814,国窖2025原件,普通/52度,500ml*6/件,4.000,6,N, +3356,1,NULL,3381,ZXZ028420,茅台2025原箱,普通/53度,500ml*4/件,4.000,4,Y,1 +4623,8,10,4648,ZXZ028420,茅台2025原箱,普通/53度,500ml*4/件,4.000,4,Y,1 +9220,8,10,7142,ZXZ028421,茅台2025原箱,普通/53度,500ml*4/件,4.000,4,Y,1 +9221,8,10,7143,ZXZ028423,茅台2025原箱,普通/53度,500ml*4/件,4.000,4,Y,1 +3357,1,NULL,3382,ZXZ028912,梦之蓝M3 2019原件,40.8%,500ml*4/件,4.000,4,Y,1 +4624,8,10,4649,ZXZ028912,梦之蓝M3 2019原件,40.8%,500ml*4/件,4.000,4,Y,1 +4625,8,10,4650,ZXZ029028,老白汾酒2025年原件,普通/53度,500ml/单品,4.000,,N, +4626,8,10,4651,ZXZ029647,茅台蛇年2025原件,普通/53度,375ml*2/盒,4.000,2,N, +4619,8,10,4644,ZXZ026856,五粮液原箱2025,普通/52度,500ml*6/件,3.000,6,N, +3111,1,NULL,3136,ZXZ027084,茅台1935/2025原箱,普通/53度,500ml*6/件,3.000,6,N, +1026,7,NULL,1051,ZXZ027084,茅台1935/2025原箱,普通/53度,500ml*6/件,3.000,6,N, +9218,8,10,7140,ZXZ027085,茅台1935/2025原箱,普通/53度,500ml*6/件,3.000,6,N, +4620,8,10,4645,ZXZ027232,汾酒青花20年2026原箱,普通/53度,500ml*6/件,3.000,6,N, +4608,8,10,4633,ZXZ014241,茅台2009大件,普通/53度,500ml*12/件,2.000,12,N, +1018,7,NULL,1043,ZXZ014241,茅台2009大件,普通/53度,500ml*12/件,2.000,12,N, +1019,7,NULL,1044,ZXZ020036,刘洋河礼盒2001,普通/45度,500ml*2/盒,2.000,2,Y,1 +2154,6,NULL,2179,ZXZ020036,刘洋河礼盒2001,普通/45度,500ml*2/盒,2.000,2,Y,1 +3339,1,NULL,3364,ZXZ020036,刘洋河礼盒2001,普通/45度,500ml*2/盒,2.000,2,Y,1 +4609,8,10,4634,ZXZ020036,刘洋河礼盒2001,普通/45度,500ml*2/盒,2.000,2,Y,1 +3340,1,NULL,3365,ZXZ022472,郎酒1988,普通/53度,50ml*2/盒,2.000,2,Y,1 +4610,8,10,4635,ZXZ022472,郎酒1988,普通/53度,50ml*2/盒,2.000,2,Y,1 +1020,7,NULL,1045,ZXZ022472,郎酒1988,普通/53度,50ml*2/盒,2.000,2,Y,1 +2155,6,NULL,2180,ZXZ022472,郎酒1988,普通/53度,50ml*2/盒,2.000,2,Y,1 +1021,7,NULL,1046,ZXZ026141,五粮液礼盒2022,普通/52度,500ml*2/盒,2.000,2,Y,1 +2156,6,NULL,2181,ZXZ026141,五粮液礼盒2022,普通/52度,500ml*2/盒,2.000,2,Y,1 +3341,1,NULL,3366,ZXZ026141,五粮液礼盒2022,普通/52度,500ml*2/盒,2.000,2,Y,1 +4611,8,10,4636,ZXZ026141,五粮液礼盒2022,普通/52度,500ml*2/盒,2.000,2,Y,1 +3342,1,NULL,3367,ZXZ026512,茅台虎年礼盒375ml,普通/53度,375ml*2/盒,2.000,2,Y,1 +4612,8,10,4637,ZXZ026512,茅台虎年礼盒375ml,普通/53度,375ml*2/盒,2.000,2,Y,1 +1022,7,NULL,1047,ZXZ026512,茅台虎年礼盒375ml,普通/53度,375ml*2/盒,2.000,2,Y,1 +2157,6,NULL,2182,ZXZ026512,茅台虎年礼盒375ml,普通/53度,375ml*2/盒,2.000,2,Y,1 +1023,7,NULL,1048,ZXZ026646,茅台小件2025,普通/53度,500ml*6/件,2.000,6,N, +1024,7,NULL,1049,ZXZ026841,茅台小件2026,普通/53度,500ml*6/件,2.000,6,N, +4613,8,10,4638,ZXZ026841,茅台小件2026,普通/53度,500ml*6/件,2.000,6,N, +4614,8,10,4639,ZXZ027088,贵州君品习酒2025原件,普通/53度,500ml*6/件,2.000,6,N, +1025,7,NULL,1050,ZXZ027377,茅台马年礼盒2026,普通/53度,500ml*2/盒,2.000,2,Y,1 +2160,6,NULL,2185,ZXZ027377,茅台马年礼盒2026,普通/53度,500ml*2/盒,2.000,2,Y,1 +3347,1,NULL,3372,ZXZ028245,国窖2022年,普通/43度,100ml*2/盒,2.000,2,Y,1 +4615,8,10,4640,ZXZ028245,国窖2022年,普通/43度,100ml*2/盒,2.000,2,Y,1 +3348,1,NULL,3373,ZXZ028561,贵州茅台酒2020,普通/53度,50ml*2/盒,2.000,2,Y,1 +4616,8,10,4641,ZXZ028561,贵州茅台酒2020,普通/53度,50ml*2/盒,2.000,2,Y,1 +3349,1,NULL,3374,ZXZ028675,茅台三斤原件2024,普通/53度,1.50L*2/件,2.000,2,Y,1 +3350,1,NULL,3375,ZXZ028929,红花郎.红十2024,普通/53度,500ml*2/盒,2.000,2,Y,1 +3351,1,NULL,3376,ZXZ028930,红花郎.红十2025,普通/53度,500ml*2/盒,2.000,2,Y,1 +4617,8,10,4642,ZXZ028930,红花郎.红十2025,普通/53度,500ml*2/盒,2.000,2,Y,1 +4618,8,10,4643,ZXZ029676,小国窖礼盒,普通/53度,50ml*2/盒,2.000,2,Y,1 diff --git a/docs/review/inventory-qty-packsize-report.md b/docs/review/inventory-qty-packsize-report.md new file mode 100644 index 0000000..673f79a --- /dev/null +++ b/docs/review/inventory-qty-packsize-report.md @@ -0,0 +1,93 @@ +# 库存「装箱数被当成数量」脏数据排查报告 + +> 排查日期:2026-06-22 · 只读核查,**未修改任何生产数据** +> 明细清单:`docs/review/inventory-qty-packsize-dirty.csv`(820 行) + +## 一、起因 + +用户反馈「商品编号 P1297 有 12 个库存,正常只有一个」,并认为「库存都是 1,quantity 字段可以删」。排查后两个判断都需要修正。 + +## 二、P1297 真相 + +P1297 = product `4830`(shop 8),**只有 1 条库存行**(inventory id 9412),不是 12 条: + +| 字段 | 值 | +|---|---| +| 商品名称 | 茅台公斤大件2005原件 | +| 规格 | `1.00L*12/件`(一件装 12 瓶) | +| 单位 | (空) | +| 当前数量 | **12.000** | +| 来源 | 入库明细 32353 / 入库单 7976,**录入数量本身就是 12** | + +即 inventory 忠实快照了入库单的 12,不是代码 bug。问题在于**导入时把规格里的「装箱数 12」当成了库存数量**,用户预期是「1 件」。 + +## 三、系统性结论:装箱数泄漏 + +这不是个例。全库 **9412 条活跃库存中,820 条 quantity > 1**,其中 **784 条的数量恰好等于规格里的装箱数**(`*N/件` → 数量 N): + +- qty=12 的行:82 条里 80 条规格含 `*12` +- qty=6 的行:641 条里绝大多数规格含 `*6` +- 判定规则:规格 `\*(\d+)` 捕获的装箱数 == 当前数量 → 标记 `dirty_pack_eq_qty=Y` + +### 按门店分布(疑似脏 dirty=Y) + +| shop_id | 脏行数 | 备注 | +|---|---|---| +| 8 | 279 | 主力门店 | +| 7 | 239 | | +| 1 | 142 | 疑似 seed/demo(商品信息多为 NULL) | +| 6 | 124 | | +| **合计** | **784** | | + +### 另有 36 行 quantity>1 但装箱数≠数量(`dirty=N`) + +需**人工复核**,不能机械归 1。典型: +- shop 1 的 seed 数据:数量 108/54/36/24…,商品编码/规格全 NULL(demo,可忽略)。 +- `ZXZ026602 茅台小白条2023原件` 规格 `500ml*5/件`,数量却是 12 / 10 —— 装箱数 5,数量非整箱倍数,真实库存存疑。 + +## 四、quantity 字段能否删除 + +**不能删**(用户原设想不成立): + +1. 数据层面:并非「都是 1」,真有 820 行多单位(其中 784 是脏数据,但删字段前必须先治脏,且仍有真实多单位场景)。 +2. 代码层面:`quantity` 被 FIFO 扣减、出库库存校验、库存流水 `inventory_logs`、财务对账、公开商品在库量、盘点全链路依赖,删列等于重构整个库存系统。 + +本轮已按决策**仅在前端隐藏库存数量列**(库存列表表格 + 手机卡片),DB 字段与后端逻辑保留;缺货/预警/流水不动。 + +## 五、建议(待决策,本轮不执行) + +1. **784 行脏数据按「件」归正为 1**:把「规格装箱数 == 数量」的行,数量统一改为 1(视作 1 件);同步修对应入库明细 `stock_in_items.quantity`,保持单据与库存一致。执行前**先备份**,并对照 CSV 的 `suggest_qty` 列逐店确认。 +2. **36 行 dirty=N 单独人工核对**:shop 1 demo 可忽略;`*5/件` 却数量 12/10 等异常逐条问业务。 +3. **根治导入器**:库存/入库导入时,遇到 `规格含 *N` 且「数量列恰好等于 N」的情况给出告警或按件折算,避免再次把装箱数写进数量。 + +> 是否执行第 1 项数据归正,请确认后另行单独处理(需备份 + 逐店复核 + 影响行数预览)。 + +--- + +## 六、全量数据质量审计(2026-06-22 补充) + +对全部活跃库存(9412 行 / 7282 distinct product)逐类核查: + +| 类别 | 行数 | 说明 | +|---|---|---| +| 数量 ≤ 0 | 0 | 干净 | +| product_id=0(无主数据) | 0 | 干净 | +| 孤儿(product_id 指向不存在 product) | 0 | 干净 | +| 快照商品编码为空 | 24 | 轻微,无法定位编号 | +| **快照编码 ≠ 所指 product.code(错指)** | **2130** | **重大,全在 shop 7** | +| 装箱数=数量(脏数量) | 784 | 见上文,跨 shop 8/7/6/1 | + +### 重大:shop 7 的 product_id 错指(与 shop 8 旧损坏同款) + +`shop 7` 有 **2130 条库存行错指**,涉及 **514 个被多编码共享的 product**(每个 product 名下挂多个不同商品编号)。典型:product `234` 名下有 **130 条库存行、130 个不同商品编码**——即 130 个真实商品编号的库存全部指向了同一个 product。这正是上次给 shop 8 修过的「按名称合并导致 product_id 搞混」根因。 + +各店对比: + +| shop | 活跃库存行 | 错指行 | 状态 | +|---|---|---|---| +| 1 | 1213 | 0 | 干净 | +| 6 | 1134 | 0 | 干净 | +| 7 | 3286 | **2130** | ❌ 未修复 | +| 8 | 3779 | 0 | 已修复(本次之前) | + +**建议**:对 shop 7 跑既有的 `backend/cmd/fix-inventory-products` 工具(按库存快照编码重建 per-code product),流程同 shop 8——**先备份** → dry-run 预览 → 执行 → 复核「错指=0、distinct product 数=活跃行去重数」。需你确认后单独执行。 diff --git a/docs/review/lint-report.md b/docs/review/lint-report.md new file mode 100644 index 0000000..fac208c --- /dev/null +++ b/docs/review/lint-report.md @@ -0,0 +1,33 @@ +# Lint 检查报告 — 2026-07-03 + +## Go 后端 + +### ✅ 全部通过 +- `gofmt -l .`:无格式问题 +- `go vet ./...`:无问题 + +## Flutter 前端 + +### ✅ 已自动修复 +- `dart format lib test`:70 个文件格式化 +- `dart fix --apply --code=unnecessary_import`:移除 10 处多余 import(涉及 kpi_card / label_preview_dialog / wheel_date_picker / product_detail_screen 及 5 个 test 文件) +- `client/pubspec.yaml`:修正 `flutter_web_plugins: any` → `sdk: flutter`(原写法导致 `flutter pub get` 直接失败,属本轮未提交改动引入的构建阻断问题;`pubspec.lock` 随之更新) + +### ⚠️ 需要手动修复(warning,本轮改动文件) +- `client/test/product_repository_test.dart:142` / `:161`:`unnecessary_type_check`——类型判断结果恒为 true,断言无效,建议改为有意义的断言或删除 + +### ⚠️ 需要手动修复(warning,存量代码,非本轮改动) +- `client/lib/core/platform/single_instance.dart:7`:`unused_element`——私有声明 `_server` 未被引用 + +### ℹ️ info 级别(82 处,不阻断 DoD,择机清理) +- `use_build_context_synchronously` ×46:主要集中在 `stock_in_list_screen.dart` / `stock_out_list_screen.dart` 的 async 回调中跨 await 使用 context(mounted 检查对象不匹配) +- `prefer_const_constructors` ×19 + `prefer_const_literals_to_create_immutables` ×1 +- `deprecated_member_use` ×7:`withOpacity` → `withValues()`(inventory_check_screen / label_preview_dialog / multi_select_dropdown / order_print_preview_dialog) +- `curly_braces_in_flow_control_structures` ×7 +- `no_wildcard_variable_uses` ×2:`app_shell.dart:581/586` 引用了通配符变量 `_` + +### 重点关注目录 +- `client/lib/screens/auth/`、`client/lib/widgets/ds/`:**零问题**,干净 + +## 结论 +0 error;3 处 warning 需手动处理(2 处在本轮改动的测试文件、1 处存量);pubspec 构建阻断问题已修复。整体可提交。 diff --git a/docs/review/stockorder-empty-fields-stats.html b/docs/review/stockorder-empty-fields-stats.html new file mode 100644 index 0000000..7e7668f --- /dev/null +++ b/docs/review/stockorder-empty-fields-stats.html @@ -0,0 +1,143 @@ + + + + + +出入库明细空字段 × 库存可补 数据统计 — 酒库管理系统 + + + +

出入库明细 空字段 × 库存可补 · 数据统计

+
数据源:生产库每日备份 jiu_db_20260626_020210.sql.gz(2026-06-26 02:02)· 单租户(鼎晟酒行)
+
+
出库明细 26,693
+
入库明细 28,443
+
库存批次 13,120
+
商品 9,525
+
+ +
+ 口径说明:「库存能找到值」= 该字段在库存表的显示值非空,即 COALESCE(NULLIF(sii.batch_no,''), inv.batch_no)(用户在库存页看到的值)。出库明细按 product_id 匹配库存;入库明细按 stock_in_item_id 直连。
+ 「打印必空白」= 明细行快照为空 该行 product 主记录也为空(两者都空,打印才会留白)。 +
+ +

一、核心结论

+
+
    +
  • 出库单打印空白、且能从库存补回的,只有约 18~20 行(集中在 13 张单,全部是 2026-06-20 ~ 06-24 的近期 App 新建单(含截图 CK20260623000007)。这正是「出库单显示对齐库存口径」能解决的部分。
  • +
  • 出库明细批次号为空共 5,528 行,但其中 99.7% 在库存里也查不到值(商品已售罄无在库批次 / 源数据本就没录批次)——属源数据缺失,非显示 bug,无法从库存补
  • +
  • 入库单批次/生产日期可补数 = 0(库存、product 主记录都补不出)。因为入库是数据录入源头,源头空则下游全空,无上游可回填——与出库不同,不是显示 bug,"显示对齐库存口径"对入库无意义。空批次跨 2023-04~2026-06 整段历史。
  • +
  • 根因复述:批次/生产日期的值真实落在 inventories自身的快照列上(不是 stock_in_item、也不是 product 主记录);出库打印却回退读 product 主记录,对不上。详见 db-schema
  • +
+
+ +

二、出库明细 stock_out_items

+

product_id 全部有值(无占位行)。

+ +

① 宽口径:明细字段为空 ÷ 其中库存能找到值

+
+ + + + + + + + +
字段明细为空占比其中库存有值
batch_no 批次号5,55720.8%46
production_date 生产日期660.25%48
product_code 商品编号620.23%48
series 系列630.24%48
spec 规格630.24%48
+ +

② 打印口径:明细空 且 product 主记录也空(= 打印必空白)÷ 库存可补

+
+ + + + + +
字段打印空白行库存可补涉及出库单
batch_no 批次号5,528181,207
production_date 生产日期372022
+ +

三、入库明细 stock_in_items

+

与出库完全同口径:库存按 product_id 匹配;并额外列出能否从 product 主记录补值。

+
+ + + + + + + + +
字段明细为空库存可补product 主记录可补
batch_no 批次号6,87600
production_date 生产日期1600
product_code 商品编号251025
series 系列271226
spec 规格271225
+
+ 入库单批次号/生产日期:可补数 = 0(库存、product 主记录都补不出)。这是结构性的——入库是数据录入的源头:批次若在入库时就空着,由它建的 product 也空、由它生成的库存批次同样空,没有任何上游能反过来填。出库之所以能补,是因为出库引用的是「别的、已录过批次的库存批次」。
+ 入库空批次跨度 2023-04 ~ 2026-06(整段历史都有,非 App 期专属),属源头从未录入,不是显示 bug,与出库的「显示对齐库存口径」无关。商品编号/系列/规格少量空行(25~27)可从 product 主记录补,但属商品身份字段、与本次批次问题无关。
+ +

四、受影响(打印空白但库存可补)出库单清单

+

20 个明细行,13 张单,全部为近期 App 新建单。这些单据打印时批次/生产日期留白,但库存里有值。

+
+ + + + + + + + + + + + + + + + + + + + + + + +
出库单号日期product_id库存批次库存生产日期
CK202606240000212026-06-24477920156746002009-04-16
CK202606230000012026-06-2394482023-003/AI0134532025-02-27
CK202606230000012026-06-23443012012-01-01
CK202606230000012026-06-2394492023-003/AI0134412025-02-27
CK202606230000012026-06-2394862006-01/030082006-12-01
CK202606230000032026-06-2346172026-001/YC0254002026-03-19
CK202606230000042026-06-2394772025-023/AA0073332025-12-18
CK202606230000042026-06-2394782025-023/AA0071912025-12-18
CK202606230000042026-06-2347222025-127/4410661454892026-03-14
CK20260623000007 截图2026-06-2343822022-001/AH0001082023-12-28
CK202606230000092026-06-234247(批次空)2024-08-21
CK202606230000102026-06-2347172022-014/6646676000532024-01-31
CK202606230000102026-06-2395032023-004/6541052509042024-07-11
CK202606230000132026-06-2340262022-091/AA0198352023-07-25
CK202606230000142026-06-2394992022-006/AI0084382024-05-11
CK202606230000142026-06-2395002022-005/AH0036302024-03-28
CK202606230000152026-06-234645(批次空)2026-01-19
CK202606230000162026-06-2347322022-030/7996894088202023-02-14
CK202606230000172026-06-2347572025-102/0066792405922026-02-13
CK202606200000012026-06-203510RK20260613000004-012026-06-09
+ +

五、对修复方案的指向

+
+ 「出库单显示对齐库存口径」(已选方案)足以覆盖真实问题:对近期 App 出库单(上表 13 张)打印时按 product → inventory 取批次/生产日期即可即时补全,无需动数据、无需重发。
+ 历史海量空批次(5,500+ 行)不在修复范围:它们在 product / 库存 / 入库明细中均无值,属源数据从未录入,任何「显示口径」都补不出来。
+ 入库单方向收益≈0:入库明细与库存几乎无可用关联,且入库明细空批次同样源数据缺失。 +
+ +

统计方法:将备份导入临时库 jiu_analyze,按上述口径 SQL 聚合,完成后删除临时库。生成时间 2026-06-26。

+ + diff --git a/docs/runbooks/ec2-to-ali-migration.html b/docs/runbooks/ec2-to-ali-migration.html new file mode 100644 index 0000000..b920ce3 --- /dev/null +++ b/docs/runbooks/ec2-to-ali-migration.html @@ -0,0 +1,218 @@ + + + + + +EC2 → 阿里云 迁移计划(定稿)— 酒库管理系统 + + + +

EC2 → 阿里云 迁移计划 定稿

+
酒库管理系统(jiu)· 把 jiu 的服务 / 数据 / 文件 / DB / 域名解析 / CI 接入从 AWS EC2 整体切到阿里云 ECS。共享机上的 pangolin / marzban / blog 原地不动。核心策略:DB 主从实时复制 + ICP 备案期间双轨部署,最后几秒提升从库为主 + 切 DNS
+
+ AWS EC2 · ap-southeast-1(新加坡)· Amazon Linux 2023 · 18.136.60.128 + 目标 阿里云 ECS · 大陆地域(需备案) + 切流停机 仅几秒(提升从库) + 定稿日 2026-06-23 +
+ +

已拍定的关键决策

+ + + + + + + +
定论
接入架构阿里独立 nginx 直连 443,不复刻 pangolin(pangolin 后续单独迁走)
证书certbot DNS-01 + Cloudflare API 预签(与机器 IP/端口/解析解耦,切流前即可备好)
数据库切换MySQL 主从复制(EC2 主 → 阿里从,实时同步),切流时提升从库为主,写冻结仅几秒
CI/CD双轨:编译/测试/发布不变,Deploy 拆成 EC2 + Ali 两步并存,迁移期每次发版同步两边
域名注册商先从 Cloudflare 转入阿里万网(备案前置),转移不影响解析,全程仍指 EC2,备案过后再切 A 记录
+ +
+ ⚠ 头号前置 — 域名注册商转移 + ICP 备案: + 51yanmei.com 现注册在 Cloudflare(海外),不在工信部名单 → 大陆备案前必须把注册商转入阿里万网(转出流程 5–7 天,期间解析不中断)。 + 备案本身 1–3 周。这两件事决定整个时间表,且 EC2 在此期间照常对外服务。 + 注意:注册商转移 ≠ 改解析——转入阿里万网后 A 记录照样指 EC2,线上零影响。 +
+ + +

一、迁移盘点(搬什么 / 不搬什么)

+

EC2 是多服务共享机,本次只迁 jiu。

+ +

① 要迁移的 jiu 资产 MOVE

+ + + + + + + + + + +
类别内容位置 / 体量迁移方式
后端服务systemd jiu.servicejiu-server(+ jiu-gencode/opt/jiu/backend · 64MCI 双轨重新部署
数据库MySQL 8.0 容器 jiu_mysql / 库 jiu_db35.3 MB主从复制实时同步(非一次性 dump)
配置production.env(含密钥)、version.yaml/opt/jiu/configscp 拷贝;域名不变 → 内容照搬
上传文件商品图片/opt/jiu/images · 2Mrsync不在 DB 复制范围,切流前再补一次)
前端 / 官网 / 安装包Flutter web、Eleventy 站、apk/zip/exe/opt/jiu/{web,marketing,downloads}CI 双轨下发 / rsync
反代配置nginx jiu.confpangolin-edge 容器内改造为独立 nginx(listen 443)
TLS 证书jiu.51yanmei.comEC2 letsencrypt阿里机 DNS-01 预签
域名解析jiu.51yanmei.com A 记录Cloudflare → 18.136.60.128 · 未代理备案后切到阿里 IP(唯一一条 DNS 改动)
+ +

② 留在 EC2 / 不动 KEEP

+

pangolin-edge / pangolin-singbox(共享反代,后续单独迁)、marzban、blog 全家桶(blog/umami/postgres)、blog/vpn 证书、blog/pay/vpn 解析。

+ +

③ NAS(群晖 192.168.3.200)不搬,仅改配置 KEEP

+
+ Forgejo(git.51yanmei.com:2222)+ act_runner 在内网群晖git.51yanmei.com 解析的是内网 IP,与 EC2 无关。迁移只需:① 加 ALI_* Secrets;② 改 deploy-server.sh 的 nginx reload 命令。 +
+ +

④ 顺手清理 DROP

+

/opt/jiu/web-old/opt/jiu/web_old —— 历史残留,不迁。

+ + +

二、数据库主从复制(方案核心)

+
EC2 主库已就绪、零重启:log_bin=ON · binlog_format=ROW · server_id=1 · gtid=OFF · binlog 保留 30 天。→ 用经典 binlog 位点复制,不碰主库、不停现网;隧道断几天也能靠 30 天 binlog 自动追上。
+

网络通路:SSH 隧道(不暴露 DB 公网)

+
# 阿里机用 autossh 保活隧道,从库连本机 13306 → EC2 的 jiu_mysql:3306
+autossh -M 0 -fN -L 13306:127.0.0.1:3306 ec2   # systemd 托管,断线自动重连
+

搭建(EC2 纯新增,不影响现网)

+
# 1) EC2 主库建复制账号
+CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY '<pass>';
+GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
+
+# 2) 带位点快照 → 导入阿里从库
+docker exec jiu_mysql sh -c 'mysqldump -uroot -p$MYSQL_ROOT_PASSWORD \
+  --single-transaction --master-data=2 jiu_db' | gzip > snap.sql.gz
+
+# 3) 阿里从库(容器 command 加 --server-id=2 --read-only)挂主库开追
+CHANGE REPLICATION SOURCE TO SOURCE_HOST='127.0.0.1', SOURCE_PORT=13306,
+  SOURCE_USER='repl', SOURCE_PASSWORD='<pass>',
+  SOURCE_LOG_FILE='<dump里的file>', SOURCE_LOG_POS=<dump里的pos>;
+START REPLICA;   # SHOW REPLICA STATUS\G → Seconds_Behind_Source=0 即追平
+ + +

三、迁移步骤(分阶段)

+ +
0

P0 · 域名注册商转移 + 提交备案(并行,EC2 照常服务)

+ + +
1

P1 · 阿里机基础搭建(无停机)

+ + +
2

P2 · 起从库实时同步(见第二节)

+ + +
3

P3 · nginx + 证书 + 后端(仍未切流,--resolve 自测)

+ + +
4

P4 · CI/CD 双轨

+ + + + + + +
对象改动
Secrets新增 ALI_HOST / ALI_SSH_KEY / ALI_USER(EC2_* 保留不动)
deploy-server/site/client.ymlDeploy 拆两步:「Deploy → EC2」在前,「Deploy → Ali」在后,均 if: always()(一边失败不挡发版)
deploy-server.sh参数化目标 host/key + nginx reload 方式:EC2 走 docker exec pangolin-edge,Ali 走独立 nginx -s reload(配置落 /etc/nginx/conf.d/jiu.conf
deploy/nginx-jiu.conflisten 127.0.0.1:8445 ssllisten 443 ssl
+
Schema 变更版本的顺序:带新列/索引的发版,靠「EC2 先部署 → DDL 经复制流到达阿里副本 → Ali 再部署即 no-op」化解。workflow 里 EC2 步骤排在 Ali 之前,天然满足。
+ +
5

P5 · 【备案通过后】切流:提升从库为主 + 切 DNS(写冻结仅几秒)

+
0. 切前两边各全备一份(提升后 EC2 库即过时,留底兜底)
+1. 提前把 jiu.51yanmei.com TTL 降到 60–120s
+2. 最后一次 rsync EC2:/opt/jiu/images → Ali   # 文件不走复制
+3. EC2: systemctl stop jiu                    # 停写
+4. Ali: 确认 Seconds_Behind_Source=0(追平最后一笔)
+5. Ali: STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only=OFF;  # 转独立主库
+6. Ali: systemctl restart jiu                 # AutoMigrate 跑在可写库,no-op
+7. Cloudflare: A 记录 18.136.60.128 → <ALI_IP>
+ +
6

P6 · 公网验证

+ + +
7

P7 · 观察后拆 EC2

+ + + +

四、风险与回滚

+ + + + + + + +
风险等级对策
注册商转移 + 备案周期长EC2 全程照常服务;阿里机后台先搭好、双轨同步,备案不阻塞日常发版
图片文件不在 DB 复制范围P5 切流前最后 rsync 一次 images/;切流后新图直接落阿里
提升从库即 DB 不可逆P5 第 0 步两边全备;低 TTL,出问题 A 记录改回 EC2 + 拿备份恢复
autossh 隧道断 → 复制暂停binlog 留 30 天,重连自动追上;挂延迟告警
schema 变更版本在副本上 AutoMigrate 失败双轨 EC2 先于 Ali,DDL 先复制到位
+ + +

五、剩余小决策

+ + + + +
#决策点推荐
DNS 托管:留 Cloudflare / 搬阿里云 DNS留 Cloudflare(记录已在此,改动最小)
P5 切流时段夜间低峰(写冻结仅几秒,影响极小)
+ +
+
文档:/Users/wangjia/code/jiu/docs/runbooks/ec2-to-ali-migration.html · 已登记 docs/index.html
+ + diff --git a/docs/security/2026-07-03-audit.md b/docs/security/2026-07-03-audit.md new file mode 100644 index 0000000..021b1b8 --- /dev/null +++ b/docs/security/2026-07-03-audit.md @@ -0,0 +1,102 @@ +# 安全审计报告 — 2026-07-03 + +**审计范围**:后端 `backend/`(router / 全部 handler / middleware / service / config / model)与部署链路(`.gitea/workflows/*`、`scripts/ci/*`、`deploy/*`)。重点核查本会话新增的 `summaryBounds`/`rolling30`(出入库 KPI)、`finance.Trend`、库存/出入库搜索的 `EXISTS/IN` 子查询与 LIKE 拼接、`?window` 参数处理、新增路由鉴权、CI secrets 使用、`backup-db.sh` 密码处理、公开端点暴露面。 + +**发现问题**:Critical 1,High 2,Medium 3,Low 4 + +--- + +## Critical — 必须立即修复 + +### SEC-001: 关联对象(Partner / Warehouse)跨租户信息泄露 +**文件**: +- `backend/internal/model/stock.go:26-27,80-81`、`backend/internal/model/finance.go:20`(关联定义只按 `foreignKey`,无 shop 作用域) +- `backend/internal/handler/stock_in.go:208-212`(`Get` Preload Partner/Warehouse) +- `backend/internal/handler/stock_out.go:142-144`(`Get` Preload Partner/Warehouse) +- `backend/internal/handler/finance.go:70`(`ListRecords` Preload Partner)、`finance.go:232-245`(`Summary` `LEFT JOIN partners p ON p.id = f.partner_id`,无 `p.shop_id` 约束) +- 写入侧不校验归属:`stock_in.go:220-268`(Create 直接透传 `req.PartnerID`/`req.WarehouseID`)、`stock_out.go:190-239`、`finance.go:81-127`(Create 透传 `partner_id`) + +**描述**:下单/建财务记录接口接收请求体里的 `partner_id`、`warehouse_id`,**不校验这些 ID 是否属于当前门店**。读取时用 GORM `Preload("Partner")` / `Preload("Warehouse")`(或 finance Summary 的 `LEFT JOIN partners`)按外键直接回连,**没有再按 `shop_id` 二次过滤**。因此攻击者能引用其他门店的 `partner_id`/`warehouse_id`,读回时把对方数据带出来。 + +**攻击场景**:攻击者登录 A 店,POST 一张草稿入库单 `{"warehouse_id":1,"partner_id":<枚举 N>,...}`,随后 GET `/api/v1/stock-in/orders/:id`,响应中的 `partner` 对象会返回 B 店该往来单位的 `name / contact / phone / address / bank_account / credit_limit / balance`(见 `model/partner.go` 字段)。循环枚举 `partner_id` 即可导出全平台所有门店的客户/供应商联系人、电话、地址、银行账号——多租户隔离被彻底击穿,且泄露 PII。finance `ListRecords`/`Summary` 同理会带出他店往来单位名。 + +**修复建议**: +1. 写入侧:Create/Update 时校验 `partner_id`、`warehouse_id` 归属当前 `shop_id`(`SELECT 1 FROM partners WHERE id=? AND shop_id=?`),不符则 400。 +2. 读取侧纵深防御:Preload 加租户条件,如 `Preload("Partner", "shop_id = ?", shopID)`、`Preload("Warehouse", "shop_id = ?", shopID)`;finance Summary/ListRecords 的 join/preload 同样补 `p.shop_id = f.shop_id`。 +3. 同类需一并修:`inventory.CreateCheck`(item.ProductID 未验归属)→ `GetCheck`/`CompleteCheck` 的 `Preload("Items.Product")` 会带出他店 product。 + +**验证方式**:用 A、B 两店 token;A 建单引用 B 的 partner_id,GET 单据确认 `partner` 为 null 或 400,而非返回 B 店数据。 + +--- + +## High — 本次发布前修复 + +### SEC-002: 默认 JWT / License 密钥随仓库提交,且 release 模式无校验 +**文件**:`backend/config/config.yaml:10,15`、`backend/main.go:24-28` +**描述**:`config.yaml`(已进 git)内含 `jwt.secret: "change-this-to-a-random-secret-in-production"` 与 `license.hmac_secret: "change-this-license-secret-in-production"`。`main.go` 仅在 release 模式校验 `cors_origin != "*"`,**未校验 JWT secret 是否仍为默认值**。生产靠 `production.env` 的 `JWT_SECRET` 覆盖,但一旦漏配 env / 配置加载顺序异常,服务会以仓库里公开的默认密钥启动。 +**攻击场景**:攻击者用公开的默认 secret 自签任意 `{user_id, shop_id, role:"superadmin"}` 的 JWT(`middleware/auth.go:46-48` 只验签名),无 sid 的 token 走「存量放行」分支(`auth.go:56` 注释),直接以任意门店超管身份访问全部接口——完全绕过认证。 +**修复建议**:在 `main.go` release 前置检查里追加:`jwt.secret` 为空或等于默认占位串时 `log.Fatal`;同理校验 license hmac_secret。默认值不应是可用值。 +**验证方式**:release 模式下不设 `JWT_SECRET` 启动,应直接 fatal 退出。 + +### SEC-003: 运维 workflow 在命令行内联 DB 密码(`mysql -p${DB_PASSWORD}`) +**文件**:`.gitea/workflows/reset-db.yml:38,45,60,64`、`.gitea/workflows/debug-db.yml:23-42`、`.gitea/workflows/seed.yml:44,59` +**描述**:这三个 workflow 把 `${{ secrets.DB_PASSWORD }}` 以 `-p${DB_PASSWORD}` 形式内联进通过 SSH 下发到生产/容器执行的 `mysql`/`docker exec` 命令。`debug-db.yml` 用未加引号的 `<< EOF` heredoc,密码在 runner 本地展开后拼进远端命令串。风险:(a) 目标主机 `ps aux` 可见明文密码;(b) mysql 会打印 "password on the command line is insecure";(c) 若任一步骤开 `set -x` 或命令回显,CI 日志泄露密码。对比 `scripts/ci/backup-db.sh:33-34` 已用 `MYSQL_PWD` env 正确处理——说明团队知道正确姿势,只是这三个 workflow 没跟上。 +**修复建议**:改为 `docker exec -e MYSQL_PWD="$DB_PASSWORD" jiu_mysql mysql -uroot ...`,去掉 `-p`;heredoc 用单引号 `<< 'EOF'` 让密码只在远端环境变量里展开,不进命令行。 +**验证方式**:远端 `ps -ef | grep mysql` 执行期间看不到密码;CI 日志无密码串。 + +--- + +## Medium — 近期修复 + +### SEC-004: finance / 库存盘点写入未校验外键归属(SEC-001 写入侧的独立体现) +**文件**:`backend/internal/handler/finance.go:81-127`、`backend/internal/handler/inventory.go:239-271` +**描述**:即便按 SEC-001 修好读取侧 Preload,写入侧仍应独立校验:`finance.Create` 存任意 `partner_id`、`CreateCheck` 存任意 `product_id`,会在本店数据里留下指向他店实体的脏引用,破坏数据一致性与后续对账。 +**修复建议**:所有接收外键的写接口统一做「归属当前 shop」前置校验,抽成公共 helper。 + +### SEC-005: 公开错误上报端点可伪造身份字段、`error_msg` 无长度上限 +**文件**:`backend/internal/handler/error_report.go:19-72`、路由 `router.go:78` +**描述**:`POST /api/v1/public/errors` 无需认证,`shop_id / username / role / shop_no` 全部取自请求体,攻击者可任意伪造,污染 superadmin 后台的错误列表(`admin/errors`)。`stack_trace` 截断到 4000,但 `error_msg` 未设上限,可提交超大 body 撑存储(仅 IP 限流 30/min 缓解)。 +**修复建议**:`error_msg` 加长度上限(如 2000);后台展示对这些「客户端自报字段」标注不可信;考虑对 `shop_id` 做存在性校验或改标为 `reported_shop_id`。 + +### SEC-006: 生产库密码经 `python3 re.search` 从 DSN 裸解析,DSN 特殊字符易误解析 +**文件**:`scripts/ci/backup-db.sh:33` +**描述**:`re.match(r"[^:]+:([^@]+)@", d)` 从 DSN 提取密码,若密码含 `@` 或 `:` 会截断/错取,导致备份静默失败(虽有 100KB 体积兜底,但错误密码下 dump 会直接失败退出,属可用性风险而非泄露)。当前实现本身不泄露密码(走 `MYSQL_PWD`,正确)。 +**修复建议**:改为在远端 source `production.env` 后用 shell 参数解析,或约束 DB 密码字符集;此条为健壮性加固。 + +--- + +## Low — 备案,酌情处理 + +### SEC-007: 兑换码熵与取模偏置 +**文件**:`backend/internal/util/redeem_code.go:16-31` +**描述**:兑换码 8 位 × 31 字母表 ≈ 40 bit;`int(b)%len(codeAlphabet)`(256 % 31 ≠ 0)引入轻微取模偏置,进一步压缩有效熵。`/license/activate` 需登录且受 per-shop 限流(20rps),在线爆破 40bit 不现实,风险低。 +**修复建议**:用 `rand.Int(rand.Reader, big.NewInt(len))` 消除偏置;如担心批量猜码可加每店激活失败限速。 + +### SEC-008: `product_option` / `product_attr` keyword 搜索 LIKE 通配未转义 +**文件**:`backend/internal/handler/product_option.go:31,97,163,231` 等 +**描述**:`"%"+kw+"%"` 参数化传入(无注入),但用户传入的 `%`/`_` 会被当通配符,可致模糊匹配范围异常(功能性,非安全边界)。同现象遍布各列表搜索。 +**修复建议**:如需精确前缀/包含语义,转义 `%`、`_`。 + +### SEC-009: CORS 允许头未含限流/凭证约束,`*` 仅靠 release 守卫 +**文件**:`backend/main.go:57-68`、`config.go:93` +**描述**:默认 `cors_origin: "*"`,release 模式已 fatal 拦截 `*`(`main.go:25-27`),debug 模式放开属预期。当前无 `Allow-Credentials`,风险低。保持现状即可,备案。 + +### SEC-010: 公开商品接口暴露面 +**文件**:`backend/internal/handler/public.go`(`GetProduct`/`ListShopProducts`/`ProductPage`)、`version.go`、`/health` +**描述**:`/health` 仅回 `{"status":"ok"}`,`/version` 与 `/public/release` 实时读 `version.yaml`(无敏感信息)。`public/shops/:shop_code/products` 按 shop_code 列出有库存商品(含 sale_price)——属商品公开展示的设计意图,已加最紧 IP 限流(`shop_list_per_min`)。`ProductPage` 注入的 OG 字段均经 `html.EscapeString`(`public.go:355,411-418`),无 XSS。暴露面可接受,备案。 + +--- + +## 通过检查项 + +- ✅ 本会话新增代码多租户隔离到位:`summaryBounds`/`rolling30`、`stockIn/Out.Summary`(`stock_in.go:191-200`/`stock_out.go:90-99`)、`finance.Trend`(`finance.go:198-205`)、`inventory.Summary`(`inventory.go:187-207`)所有聚合均带 `shop_id = ?`。 +- ✅ 库存/出入库搜索的 `EXISTS/IN` 子查询含 `it.shop_id = ?`(`stock_in.go:63-68,139-144`、`stock_out.go:59-65`、`inventory.go:115-121`);`itemsTable` 为硬编码常量非用户输入(`stock_in.go:87` 注释确认),`series/spec` 多值用 `strings.Repeat("?,")` 生成占位符后参数化,无拼接注入。 +- ✅ 所有 LIKE/keyword 均走 GORM `?` 参数化,未见字符串拼接进 SQL。 +- ✅ `?window` 参数只用 `== "rolling30"` 相等判断,不参与 SQL(`stock_in.go:173`);`months` 经 `strconv.Atoi` + `1..24` 范围校验(`finance.go:186`)。 +- ✅ 新增路由 `/finance/trend`(`router.go:216`)在 `api.Use(JWT)` + `ReadOnly` + `LicenseGuard` 组内,为 GET 只读;所有写操作路由都在 `ReadOnly()` 之后;`/users` 挂 `AdminOnly`、`/admin/*` 挂 `SuperAdminOnly`。 +- ✅ 密码 bcrypt 哈希(`service/auth.go:443,496`),`PasswordHash` 标 `json:"-"`(`model/user.go:9`)不外泄。 +- ✅ JWT 会话校验支持踢人/禁用即时失效(`middleware/auth.go:56-80`),登录按账号+IP 双重失败锁定、多档 IP/店限流。 +- ✅ 库存扣减/单号生成用事务 + `FOR UPDATE` 行锁(`inventory.go:348`、`service/stock.go` GenerateOrderNo);`admin.ClearData` 用表名白名单 + `shop_id` 过滤(`admin.go:30-51`)。 +- ✅ CI secrets 通过 `${{ secrets.* }}` 注入 env,未见 `echo`/`set -x` 打印密钥;`setup_ssh` 私钥写盘后 `teardown_ssh`/`if: always()` 清理(`lib-forgejo.sh:128-130`、各 workflow cleanup 步骤)。`backup-db.sh` 用 `MYSQL_PWD` env 传密码(正确)。 +- ✅ `SetTrustedProxies(127.0.0.1/::1)` + `RemoteIPHeaders=X-Real-IP`,客户端无法伪造限流/审计所依赖的真实 IP(`main.go:54-55`)。 +- ✅ 图片上传校验:解码校验真实格式、限 1MB、限每商品 5 张、UUID 文件名、按 productID 归档(`product_image.go:49-96`)。 diff --git a/docs/security/2026-07-03-precommit-audit.md b/docs/security/2026-07-03-precommit-audit.md new file mode 100644 index 0000000..2f699f4 --- /dev/null +++ b/docs/security/2026-07-03-precommit-audit.md @@ -0,0 +1,90 @@ +# 安全审计报告(发版前增量) — 2026-07-03 + +**审计范围**:分支 `worktree-front-refactor` 的全部未提交改动(`git diff HEAD` + 未跟踪文件),聚焦**本日首轮审计(`docs/security/2026-07-03-audit.md`,Critical/High 已修复)之后**的新增/变更部分: + +1. `client/lib/screens/auth/`(登录/注册屏全新重写)+ `client/lib/core/storage/login_history.dart`(记住我)+ `client/lib/repositories/auth_repository.dart` +2. `deploy/nginx-jiu-ali.conf`(443 TLS 回切 + 80→443 跳转,8443 拆除) +3. `scripts/ci/*` + `.gitea/workflows/*`(EC2→Ali URL/secret 回切) +4. 三端明文豁免(ATS / usesCleartextTraffic)还原核查 +5. `backend/` 改动回归扫描(新增 `handler/ownership.go`、多租户 Preload/写入侧校验、`main.go` SEC-002 修复) + +**发现问题**:Critical 0,High 0,Medium 2,Low 4 + +**总体结论**:首轮审计的 Critical(SEC-001 跨租户泄露)与 High(SEC-002 默认密钥、SEC-003 CI 内联 DB 密码)在本轮改动中**均已闭环修复且实现正确**。新增的登录/注册屏、nginx 回切、CI 回切**未引入新的 Critical/High**。剩余为纵深加固项(HSTS、盘点写入侧归属校验、商品字典外键归属),可发版后处理。 + +--- + +## Critical — 必须立即修复 + +无。 + +--- + +## High — 本次发布前修复 + +无。 + +--- + +## Medium — 近期修复 + +### SEC-P01: 盘点建单(CreateCheck)写入侧未校验 product_id / warehouse_id 归属 +**文件**:`backend/internal/handler/inventory.go:239-273`(`CreateCheck`,无 `ensureShopRef`)、`inventory.go:307-345`(`CompleteCheck` 盘盈按 `item.ProductID` 建 `Inventory` 行) +**描述**:本轮为 stock-in / stock-out / finance 的写接口补齐了 `ensureShopRef`/`ensureShopRefOpt` 外键归属校验(`ownership.go`),但**盘点建单 `CreateCheck` 漏了同款校验**——`req.Items[i].ProductID` 与 `req.WarehouseID` 直接透传入库。首轮审计 SEC-001 修复说明第 3 条已点名此处待修,读取侧 `Preload("Items.Product", "shop_id = ?", shopID)` 现已补上(跨店 product 读不出来),但写入侧仍缺。 +**攻击/影响场景**:攻击者登录 A 店,`POST /api/v1/inventory/checks` 提交 `items:[{product_id:}]`;随后 `CompleteCheck` 走盘盈分支会在 **A 店 inventory 表**新建一行 `product_id=B店id` 的库存记录(`inventory.go:331-343`),snapshot 列因 `item.Product` 被 shop 作用域 Preload 置 nil 而为空。结果:不是直接跨租户读取(读侧已挡),而是**在本店数据里留下指向他店 product 的脏引用**,破坏库存一致性与后续对账/显示兜底。盘亏分支 FIFO 扣减带 `shop_id AND product_id` 双条件,跨店 id 匹配不到批次,无副作用。 +**修复建议**:在 `CreateCheck` 复用 `ensureShopRef(h.db, "warehouses", req.WarehouseID, shopID)` 与逐条 `ensureShopRef(h.db, "products", item.ProductID, shopID)`,不符即 400。与 stock-in/out 保持同一道防线。 +**验证方式**:A 店 token 提交引用 B 店 product_id 的盘点单,应 400 拒绝,而非建单成功。 + +### SEC-P02: nginx 443 回切后缺失 HSTS,且 TLS/安全响应头未加固 +**文件**:`deploy/nginx-jiu-ali.conf:15-23`(server 块无 `Strict-Transport-Security` / `ssl_prefer_server_ciphers` / `X-Frame-Options` / `X-Content-Type-Options`) +**描述**:8443 明文过渡口刚拆除、回切 443 TLS(`ssl_protocols TLSv1.2 TLSv1.3` 正确,已排除 TLSv1.0/1.1),并新增 80→443 跳转(`:107-112`)。但: +- **无 HSTS**:首次通过 80 访问的用户在被 301 跳转前存在一次明文窗口,可被 SSL-strip 中间人拦截(尤其刚从明文 8443 时代迁移、部分设备可能仍缓存 http 直连习惯)。 +- `ssl_ciphers HIGH:!aNULL:!MD5` 较宽松,未配 `ssl_prefer_server_ciphers on`,未启用 OCSP stapling / `ssl_session_cache`,仍允许部分 TLSv1.2 CBC 套件。 +- 无 `X-Content-Type-Options: nosniff` / `X-Frame-Options`(`/app/` 管理端 + 营销站 `location /` 都由此块服务,存在点击劫持/嗅探面)。 +**影响**:非直接可利用漏洞,属传输层与浏览器侧纵深加固缺口;在「刚结束明文过渡」的时点尤其值得补 HSTS。 +**修复建议**:443 server 块加 `add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;`(确认所有 51yanmei 子域都已上 HTTPS 后再考虑 `preload`);补 `ssl_prefer_server_ciphers on;` + 收敛 cipher 列表(Mozilla intermediate);对 `/app/` 与 `location /` 补 `X-Content-Type-Options nosniff`、`X-Frame-Options SAMEORIGIN`。 +**验证方式**:`curl -sI https://jiu.51yanmei.com/ | grep -i strict-transport`;SSL Labs 评级 A 以上。 + +--- + +## Low — 备案,酌情处理 + +### SEC-P03: 商品字典外键(origin/shelf_life/storage/category/description_doc)无归属校验,读侧 Preload 未按 shop_id 作用域 +**文件**:`backend/internal/handler/product.go:193-206`(Update 透传 `category_id/origin_id/shelf_life_id/storage_id/description_doc_id`,无 `ensureShopRef`)、`product.go:224-225`(Detail `Preload("Category").Preload("Origin").Preload("ShelfLife").Preload("Storage")` 无 `shop_id` 作用域)、`public.go:40-44,335`(公开页同样无 shop 作用域 Preload) +**描述**:`ownership.go` 的归属校验模式本轮只覆盖了 partner/warehouse/product,**未延伸到商品字典类外键**。用户可把自家 product 的 `origin_id` 等设成他店字典项 id,Detail/公开页会按外键回连读出他店字典的 `Name/Remark`(这些表 = `TenantBase` 门店级)。注意 `product.go` 本轮未改动,属存量残余;且泄露内容为低敏感字典文本(如「酱香型白酒」「阴凉避光保存」),非 PII。 +**修复建议**:product Create/Update 对上述 5 个外键复用 `ensureShopRefOpt`(对应表 `product_origin_options` / `product_shelf_life_options` / `product_storage_options` / `product_categories` / `product_description_docs`);Detail/public 的 Preload 补 `"shop_id = ?", shopID`(公开页用 product 自身 shop_id)。抽成与 SEC-001 同源的统一收口。 +**验证方式**:A 店 product 设 B 店 origin_id,GET detail 应返回 origin=null 或建单时 400。 + +### SEC-P04: 「记住我」默认开启,门店编号+登录账号明文留在设备 SharedPreferences +**文件**:`client/lib/core/storage/login_history.dart:15,34-47`(`getRemember` 默认 `true`;`record` 存 hotel code + username 到 `SharedPreferences`)、`login_screen.dart:52,124-139` +**描述**:`_remember` 默认 `true`,登录成功后把门店编号与登录账号(各最多 5 条)明文写入 `SharedPreferences`,下拉历史展示给下一位使用者。**已正确规避:密码从不落盘/不入历史**(仅内存 `TextEditingController`,`dispose` 释放;无任何 password 日志)——这是本项的关键安全边界,已达标。残余点是共享设备上门店编号/账号可被他人枚举,属账号名泄露(非凭证泄露),且是「记住我」标准取舍。 +**修复建议**:可接受现状。如需更严:共享/多用户场景下默认 `false`,或提供「清除登录历史」入口。 +**验证方式**:登录后检查 `SharedPreferences`,确认 `login_history_*` 只含 shop code / username,无 password 键。 + +### SEC-P05: 自助注册字段无长度上限,可提交超大 body +**文件**:`backend/internal/service/auth.go:423-431`(`RegisterInput` 仅 `binding:"required"` + 密码 `min=6`,各字段无 `max`)、`handler/auth.go:76-90` +**描述**:`/api/v1/public/register` 无需认证,`shop_name/address/manager_name/phone/description` 均无长度上限,可提交超大字符串撑存储/日志。已由 per-IP 限流缓解(`register_per_min=5`,`router.go:79`),且创建时套事务;单次滥用成本有限。同类现象亦见于注册可无限建试用门店(业务滥用,非安全漏洞,限流已缓解)。 +**修复建议**:各字段加 `max`(如 name ≤100、address ≤255、phone ≤20、description ≤500);phone 加格式校验。 +**验证方式**:提交 100KB 的 shop_name,应 400 而非入库。 + +### SEC-P06: 443 `default_server` + `server_name "... _"` 使裸 IP/任意 Host 均命中 jiu 块 +**文件**:`deploy/nginx-jiu-ali.conf:17-18` +**描述**:`listen 443 ssl http2 default_server;` 且 `server_name jiu.51yanmei.com _;`,意味着任意 Host 头(含 `https://182.92.213.171` 裸 IP)都会命中该块并用 jiu 证书应答(证书 CN 不匹配裸 IP,浏览器告警,但 API/静态仍可达)。这是 default_server 的预期行为、也是「存量客户端域名 BASE_URL 回切即恢复」所需,暴露面与回切前一致,无新增可利用点。 +**修复建议**:可接受。如需收紧,可为裸 IP/未知 Host 单设一个 `return 444` 的默认块,仅 jiu 域名走业务块。 +**验证方式**:`curl -k -H 'Host: x' https:///health` 行为符合预期即可。 + +--- + +## 通过检查项(本轮重点复核) + +- ✅ **SEC-001 跨租户泄露闭环**:新增 `backend/internal/handler/ownership.go`(`ensureShopRef`/`ensureShopRefOpt`,table 为调用点硬编码常量、非用户输入)。写入侧已挂:`stock_in.go:235-240,301-306`(Create/Update warehouse+partner)、`stock_out.go:206-211,273-278`、`finance.go:97`(Create partner)。读取侧 Preload 全部补 `"shop_id = ?", shopID`:`stock_in.go:133-136,209-211`、`stock_out.go:145-149,271-273`、`finance.go:19`(ListRecords)、`finance.go:244`(Summary `LEFT JOIN partners ... AND p.shop_id = f.shop_id`)、`inventory.go:233-234,278,293`(Logs/GetCheck/CompleteCheck)。partner/warehouse 双向防线完整。 +- ✅ **SEC-002 默认 JWT 密钥**:`main.go:28-33` release 模式新增前置校验,`jwt.secret` 为空或等于仓库默认占位串即 `log.Fatal`,杜绝带公开密钥上线。 +- ✅ **SEC-003 CI 内联 DB 密码**:`debug-db.yml:28-40` / `reset-db.yml:31,59-64` / `seed.yml:43,58` 已全部改为 `docker exec jiu_mysql sh -c 'exec mysql -uroot -p\$MYSQL_ROOT_PASSWORD ...'`——密码从**容器内环境变量**读取,不再以 `-p${DB_PASSWORD}` 出现在下发到远端的命令行,且从 workflow 移除了 `DB_PASSWORD` secret 依赖。远端 `ps` 与 CI 日志均不再暴露密码。`backup-db.sh:31-33` 仍走 `MYSQL_PWD` env(正确)。 +- ✅ **CI/workflow 回切无 secret 泄漏**:EC2→Ali 改用 `ALI_SSH_KEY/ALI_HOST/ALI_USER`(`backup.yml`/`reset-db.yml`/`seed.yml`/`debug-db.yml`/`manual.yml`),经 `${{ secrets.* }}` 注入 env,无 echo/`set -x` 打印。`manual.yml:31-40` 将旧 `EC2_*` 同值指向 Ali(回滚旧 tag 时不复活 EC2),`setup_ssh` 私钥写盘后 `teardown_ssh`/cleanup 清理。`notify.sh`/`release-client.sh`/`deploy-server.sh`/`local_test.sh` 仅注释/URL 说明变更,全部 `https://jiu.51yanmei.com`,无明文残留。 +- ✅ **三端明文豁免已还原且无残留**:`git diff HEAD` 对 `client/android/app/src/main/AndroidManifest.xml`、`client/ios/Runner/Info.plist` 无改动;全仓 grep `usesCleartextTraffic`/`cleartext`/`network_security_config`/`NSAllowsArbitraryLoads`/`NSExceptionDomains` 于 `client/android`、`client/ios`、`client/macos` 均**零命中**。前端 `AppConfig` 基址走 `https://` 域名,回切后自动恢复加密传输。 +- ✅ **nginx 回切暴露面收敛**:8443 明文口拆除(减少一个明文入口);443 `ssl_protocols TLSv1.2 TLSv1.3`(已排除弱协议);未鉴权 `(public|auth)/`、`/product/`、`/app/product/` 均挂 `limit_req zone=jiu_pub`;`X-Real-IP $remote_addr` 透传供后端限流/审计取真实 IP。 +- ✅ **登录/注册屏无凭证泄露**:密码仅存内存 `TextEditingController`,`dispose` 释放;`login_history.dart` 只存门店编号+账号,**不存密码**;`api_client.dart:135-139` 的 5xx 上报只发 `[method] path → status`,**不含请求体**(登录失败为 401 不触发上报,即便 5xx 也不带 password);注册成功弹窗仅回显门店编号,无敏感信息。 +- ✅ **shop_id 全程取自 JWT**:登录/注册屏及后端改动无从请求参数读取 `shop_id` 的路径;所有查询经 `middleware.GetShopID(c)`。 +- ✅ **注册鉴权与限流**:`/public/register` 挂 `registerIP`(5/min/IP);`/auth/login` 挂 `loginIP`(10/min/IP);密码入库 bcrypt(`service/auth.go:443`)。 +- ✅ **会话失效提示链路**:`login_screen.dart:116-120,334-340` 首帧主动读 + `ref.listen` 双通道捕获 `sessionEndedMessageProvider`,被顶下线有弹窗;消息内容无敏感信息。 +- ✅ **model 改动为纯 gofmt 对齐**:`user_session.go`/`license_device.go`/`license.go` 等仅列对齐空白变更,无语义/字段/tag 改动,多租户索引与 `json:"-"`(`RefreshJTI`)保持不变。