refactor: 出库时间更名为首次购买套餐时间,换货新资产不再按换货完成时间记录
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 38s

- 字段 outbound_at → first_purchase_at,记录表 tb_asset_outbound_record → tb_asset_first_purchase_record,接口 /outbound-records → /first-purchase-records,筛选参数与导出列同步更名
- 换货后的新资产不因换货写入,在当前世代真正支付套餐订单时才记录;记录来源只保留 order / backfill,去掉换货单字段
- 迁移 000266 重命名列、表、索引与约束;同步 Spec、设计、任务与前端接口变更说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
邹进
2026-10-27 16:47:02 +09:00
parent 909f4d0ec2
commit 62ec94adec
46 changed files with 570 additions and 560 deletions

View File

@@ -231,11 +231,11 @@ func (s *DeviceStore) applyDeviceFilters(ctx context.Context, query *gorm.DB, fi
Select("device_id").
Where("enterprise_id = ? AND revoked_at IS NULL AND deleted_at IS NULL", enterpriseID))
}
if start, ok := filters["outbound_start"].(time.Time); ok {
query = query.Where("outbound_at >= ?", start)
if start, ok := filters["first_purchase_start"].(time.Time); ok {
query = query.Where("first_purchase_at >= ?", start)
}
if end, ok := filters["outbound_end"].(time.Time); ok {
query = query.Where("outbound_at <= ?", end)
if end, ok := filters["first_purchase_end"].(time.Time); ok {
query = query.Where("first_purchase_at <= ?", end)
}
if isAuthorized, ok := filters["is_authorized_to_enterprise"].(bool); ok {
subQ := s.db.WithContext(ctx).Table("tb_enterprise_device_authorization").

View File

@@ -915,11 +915,11 @@ func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.D
Select("card_id").
Where("enterprise_id = ? AND revoked_at IS NULL AND deleted_at IS NULL", enterpriseID))
}
if start, ok := filters["outbound_start"].(time.Time); ok {
query = query.Where("outbound_at >= ?", start)
if start, ok := filters["first_purchase_start"].(time.Time); ok {
query = query.Where("first_purchase_at >= ?", start)
}
if end, ok := filters["outbound_end"].(time.Time); ok {
query = query.Where("outbound_at <= ?", end)
if end, ok := filters["first_purchase_end"].(time.Time); ok {
query = query.Where("first_purchase_at <= ?", end)
}
if isAuthorized, ok := filters["is_authorized_to_enterprise"].(bool); ok {
subQ := s.db.WithContext(ctx).Table("tb_enterprise_card_authorization").

View File

@@ -278,16 +278,16 @@ func (s *PackageSilenceOrderStore) TransitionStatus(ctx context.Context, id uint
return result.RowsAffected, result.Error
}
// AssetOutboundRecordStore 资产出库记录 Store(只新增与填写结束信息)。
type AssetOutboundRecordStore struct{ db *gorm.DB }
// AssetFirstPurchaseRecordStore 资产首次购买套餐记录 Store(只新增与填写结束信息)。
type AssetFirstPurchaseRecordStore struct{ db *gorm.DB }
// NewAssetOutboundRecordStore 创建出库记录 Store。
func NewAssetOutboundRecordStore(db *gorm.DB) *AssetOutboundRecordStore {
return &AssetOutboundRecordStore{db: db}
// NewAssetFirstPurchaseRecordStore 创建首次购买套餐记录 Store。
func NewAssetFirstPurchaseRecordStore(db *gorm.DB) *AssetFirstPurchaseRecordStore {
return &AssetFirstPurchaseRecordStore{db: db}
}
// CreateIfAbsent 插入一条出库记录;同一资产同一世代已存在时不插入,返回是否插入。
func (s *AssetOutboundRecordStore) CreateIfAbsent(ctx context.Context, record *model.AssetOutboundRecord) (bool, error) {
// CreateIfAbsent 插入一条首次购买套餐记录;同一资产同一世代已存在时不插入,返回是否插入。
func (s *AssetFirstPurchaseRecordStore) CreateIfAbsent(ctx context.Context, record *model.AssetFirstPurchaseRecord) (bool, error) {
result := s.db.WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "asset_type"}, {Name: "asset_id"}, {Name: "generation"}},
DoNothing: true,
@@ -295,17 +295,17 @@ func (s *AssetOutboundRecordStore) CreateIfAbsent(ctx context.Context, record *m
return result.RowsAffected == 1, result.Error
}
// EndGeneration 为某资产某世代的出库记录填写结束时间与原因,返回影响行数。
func (s *AssetOutboundRecordStore) EndGeneration(ctx context.Context, assetType string, assetID uint, generation int, endedAt time.Time, reason string) (int64, error) {
result := s.db.WithContext(ctx).Model(&model.AssetOutboundRecord{}).
// EndGeneration 为某资产某世代的首次购买套餐记录填写结束时间与原因,返回影响行数。
func (s *AssetFirstPurchaseRecordStore) EndGeneration(ctx context.Context, assetType string, assetID uint, generation int, endedAt time.Time, reason string) (int64, error) {
result := s.db.WithContext(ctx).Model(&model.AssetFirstPurchaseRecord{}).
Where("asset_type = ? AND asset_id = ? AND generation = ? AND ended_at IS NULL", assetType, assetID, generation).
Updates(map[string]any{"ended_at": endedAt, "end_reason": reason})
return result.RowsAffected, result.Error
}
// ListByAsset 按资产查询各世代出库记录(世代倒序)。
func (s *AssetOutboundRecordStore) ListByAsset(ctx context.Context, assetType string, assetID uint) ([]model.AssetOutboundRecord, error) {
var records []model.AssetOutboundRecord
// ListByAsset 按资产查询各世代首次购买套餐记录(世代倒序)。
func (s *AssetFirstPurchaseRecordStore) ListByAsset(ctx context.Context, assetType string, assetID uint) ([]model.AssetFirstPurchaseRecord, error) {
var records []model.AssetFirstPurchaseRecord
err := s.db.WithContext(ctx).Where("asset_type = ? AND asset_id = ?", assetType, assetID).
Order("generation DESC").Find(&records).Error
return records, err