feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
This commit is contained in:
2026-09-18 15:34:29 +08:00
parent 5e78809b93
commit 5ed6b39deb
142 changed files with 7878 additions and 964 deletions

View File

@@ -47,12 +47,35 @@ func (s *PhoneAssetAssociationStore) ExistsValid(ctx context.Context, phone, ass
}
// CountValidByPhone 统计手机号当前有效关系数,用于十项上限判定;已失效关系不占用额度。
// 实现委托给批量计数,保证「上限判定」与「关联查询投影」共用同一计数口径,不会出现两套漂移的计数。
func (s *PhoneAssetAssociationStore) CountValidByPhone(ctx context.Context, phone string) (int64, error) {
var count int64
if err := s.validScope(ctx).Where("phone = ?", phone).Count(&count).Error; err != nil {
counts, err := s.CountValidByPhones(ctx, []string{phone})
if err != nil {
return 0, err
}
return count, nil
return counts[phone], nil
}
// CountValidByPhones 一次批量统计一批手机号各自的当前有效关系数,返回「手机号 → 数量」。
// 口径与十项上限判定完全一致:只统计 status = 有效 且未软删的关系,卡与设备各计一项。
// 调用方必须传入整页涉及的手机号集合,禁止逐手机号或逐资产放大查询次数。
func (s *PhoneAssetAssociationStore) CountValidByPhones(ctx context.Context, phones []string) (map[string]int64, error) {
counts := make(map[string]int64, len(phones))
if len(phones) == 0 {
return counts, nil
}
var rows []struct {
Phone string
Total int64
}
if err := s.validScope(ctx).Select("phone, COUNT(*) AS total").
Where("phone IN ?", phones).Group("phone").Find(&rows).Error; err != nil {
return nil, err
}
for _, row := range rows {
counts[row.Phone] = row.Total
}
return counts, nil
}
// PhoneAssetAssociationListFilter 后台关联列表筛选条件。
@@ -202,12 +225,14 @@ func (s *PhoneAssetAssociationStore) LockValidByAsset(ctx context.Context, tx *g
// InvalidateByIDs 以 expected-status 条件把有效关系置为失效,返回实际失效行数。
// 状态流转必须带 status = 有效 条件并核对影响行数ENG-CONC-001否则并发解除会覆盖已成事实的失效信息。
// invalidatorName 是解绑当时的后台账号名称快照只写进已失效行有效关系保持空串MUST NOT 以创建人或更新时间填充。
func (s *PhoneAssetAssociationStore) InvalidateByIDs(
ctx context.Context,
tx *gorm.DB,
ids []uint,
method, reason string,
invalidator uint,
invalidatorName string,
now time.Time,
) (int64, error) {
if len(ids) == 0 {
@@ -216,12 +241,13 @@ func (s *PhoneAssetAssociationStore) InvalidateByIDs(
result := tx.WithContext(ctx).Model(&model.PhoneAssetAssociation{}).
Where("id IN ? AND status = ?", ids, constants.PhoneAssetAssociationStatusValid).
Updates(map[string]any{
"status": constants.PhoneAssetAssociationStatusInvalid,
"invalidated_at": now,
"invalidation_method": method,
"invalidation_reason": reason,
"invalidator": invalidator,
"updated_at": now,
"status": constants.PhoneAssetAssociationStatusInvalid,
"invalidated_at": now,
"invalidation_method": method,
"invalidation_reason": reason,
"invalidator": invalidator,
"invalidator_name_snapshot": invalidatorName,
"updated_at": now,
})
return result.RowsAffected, result.Error
}

View File

@@ -24,11 +24,18 @@ const pollingPriorityActiveConstraint = "uq_polling_priority_item_active"
// ShopIDSnapshot 是数据范围下推nil 表示不受限(超级管理员与平台账号),
// 非 nil 时按店铺快照过滤,空切片表示空范围(无可见行)。平台卡的店铺快照为空,
// 因此空范围与平台卡都不会被代理账号看到,越权与不存在由调用方收敛为同一响应。
//
// CreatedFrom/CreatedTo 按入队时间created_at取闭区间nil 表示该端不限。
// AbnormalOnly 是「异常与重试」视图条件:失败终态、已过期、尝试次数达上限或存在失败原因,
// 与其余筛选是「与」关系,视图本身仍复用同一事实表。
type PollingPriorityItemListFilter struct {
CardID *uint
TaskType *string
Status *string
TriggerType *string
CreatedFrom *time.Time
CreatedTo *time.Time
AbnormalOnly bool
ShopIDSnapshot []uint
Page int
PageSize int
@@ -49,6 +56,73 @@ func (s *PollingPriorityItemStore) WithTx(tx *gorm.DB) *PollingPriorityItemStore
return &PollingPriorityItemStore{db: tx}
}
// PollingPriorityAssetSnapshot 是入队时冻结的资产身份与代理归属快照。
type PollingPriorityAssetSnapshot struct {
// AssetType 取值与 constants.AssetType* 一致:卡绑定有效设备时资产为设备,独立卡为卡自身。
AssetType string
// AssetID 与 AssetType 共同构成资产身份。
AssetID uint
// DeviceNo 是绑定设备的虚拟号,独立卡为空字符串。
DeviceNo string
// AgentShopID 是卡所属店铺的直接上级代理店铺,一级代理与平台卡为 nil。
AgentShopID *uint
}
// AssetSnapshots 按卡批量解析入队快照,口径与迁移 000234 的回填严格一致:
// 卡存在有效设备绑定时资产为该设备(设备号取绑定设备虚拟号),否则资产为卡自身;
// 代理归属取卡所属店铺的直接上级代理店铺;同一卡存在多条绑定时取 is_current 优先、其次最新一条。
//
// 未查到卡行的卡按「资产为卡自身」兜底:入队必写资产身份,同时不因卡已软删而阻断加急事实
// (这类项会在执行时以「卡不存在」进入失败终态,与既有语义一致)。
func (s *PollingPriorityItemStore) AssetSnapshots(ctx context.Context, cardIDs []uint) (map[uint]PollingPriorityAssetSnapshot, error) {
snapshots := make(map[uint]PollingPriorityAssetSnapshot, len(cardIDs))
if len(cardIDs) == 0 {
return snapshots, nil
}
type assetSnapshotRow struct {
CardID uint `gorm:"column:card_id"`
ParentShopID *uint `gorm:"column:parent_shop_id"`
DeviceID *uint `gorm:"column:device_id"`
DeviceNo string `gorm:"column:device_no"`
}
var rows []assetSnapshotRow
if err := s.db.WithContext(ctx).Table("tb_iot_card AS c").
Select("c.id AS card_id, sp.parent_id AS parent_shop_id, b.device_id AS device_id, COALESCE(d.virtual_no, '') AS device_no").
Joins("LEFT JOIN tb_shop AS sp ON sp.id = c.shop_id AND sp.deleted_at IS NULL").
Joins(`LEFT JOIN (SELECT DISTINCT ON (iot_card_id) iot_card_id, device_id FROM tb_device_sim_binding
WHERE bind_status = ? AND deleted_at IS NULL
ORDER BY iot_card_id, is_current DESC, id DESC) AS b ON b.iot_card_id = c.id`,
constants.BindStatusBound).
Joins("LEFT JOIN tb_device AS d ON d.id = b.device_id AND d.deleted_at IS NULL").
Where("c.id IN ?", cardIDs).
Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "解析优先轮询项资产快照失败")
}
for _, row := range rows {
snapshots[row.CardID] = PollingPriorityAssetSnapshot{
AssetType: constants.AssetTypeIotCard,
AssetID: row.CardID,
AgentShopID: row.ParentShopID,
}
if row.DeviceID != nil && *row.DeviceID > 0 {
snapshots[row.CardID] = PollingPriorityAssetSnapshot{
AssetType: constants.AssetTypeDevice,
AssetID: *row.DeviceID,
DeviceNo: row.DeviceNo,
AgentShopID: row.ParentShopID,
}
}
}
for _, cardID := range cardIDs {
if _, exists := snapshots[cardID]; !exists {
snapshots[cardID] = PollingPriorityAssetSnapshot{
AssetType: constants.AssetTypeIotCard, AssetID: cardID,
}
}
}
return snapshots, nil
}
// InsertOrMerge 以 (card_id, task_type) 为活动项唯一键合并入队。
//
// 先显式插入命中活动项部分唯一索引23505时把本次触发合并进已有活动行
@@ -171,7 +245,9 @@ func (s *PollingPriorityItemStore) ClaimPending(ctx context.Context, cardID uint
Updates(map[string]any{
"status": constants.PollingPriorityStatusProcessing,
"claimed_at": now,
"updated_at": now,
// 计划执行时间已兑现:执行中的项不应再显示一个已经过去的下一次执行时间。
"next_run_at": nil,
"updated_at": now,
})
if result.Error != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "认领优先轮询项失败")
@@ -197,8 +273,9 @@ func (s *PollingPriorityItemStore) TakeOverExpiredClaim(ctx context.Context, car
Where("card_id = ? AND task_type = ? AND status = ? AND (claimed_at IS NULL OR claimed_at < ?)",
cardID, taskType, constants.PollingPriorityStatusProcessing, deadline).
Updates(map[string]any{
"claimed_at": now,
"updated_at": now,
"claimed_at": now,
"next_run_at": nil,
"updated_at": now,
})
if result.Error != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "接管超租约优先轮询项失败")
@@ -209,14 +286,17 @@ func (s *PollingPriorityItemStore) TakeOverExpiredClaim(ctx context.Context, car
return s.FindActive(ctx, cardID, taskType)
}
// IncrementAttempt 累加真实发起执行的次数。调用点必须在发起上游调用之前,
// 使崩溃遗留的执行中项在接管后仍能按次数上限收敛
func (s *PollingPriorityItemStore) IncrementAttempt(ctx context.Context, id uint) (bool, error) {
// MarkExecutionStarted 标记真正发起一次执行:累加尝试次数并写执行开始时间。
// 调用点必须在发起上游调用之前,使崩溃遗留的执行中项在接管后仍能按次数上限收敛
// 同时清空上一次尝试的结束时间,避免读侧把两次尝试拼成一段耗时。
func (s *PollingPriorityItemStore) MarkExecutionStarted(ctx context.Context, id uint, now time.Time) (bool, error) {
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status = ?", id, constants.PollingPriorityStatusProcessing).
Updates(map[string]any{
"attempt_count": gorm.Expr("attempt_count + 1"),
"updated_at": time.Now(),
"started_at": now,
"finished_at": nil,
"updated_at": now,
})
if result.Error != nil {
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "累加优先轮询项尝试次数失败")
@@ -224,15 +304,34 @@ func (s *PollingPriorityItemStore) IncrementAttempt(ctx context.Context, id uint
return result.RowsAffected > 0, nil
}
// RecordIntegrationLog 记录本次执行对应的集成交互日志标识。
// 只保存日志标识,不复制凭证或上游响应原文;标识为空时忽略,不写入空覆盖。
func (s *PollingPriorityItemStore) RecordIntegrationLog(ctx context.Context, id uint, integrationLogID string) error {
if strings.TrimSpace(integrationLogID) == "" {
return nil
}
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status = ?", id, constants.PollingPriorityStatusProcessing).
Updates(map[string]any{"integration_log_id": integrationLogID, "updated_at": time.Now()})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "记录优先轮询项集成交互日志标识失败")
}
return nil
}
// MarkCompleted 把执行中的优先项置为完成终态并出队。
func (s *PollingPriorityItemStore) MarkCompleted(ctx context.Context, id uint) (bool, error) {
now := time.Now()
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status = ?", id, constants.PollingPriorityStatusProcessing).
Updates(map[string]any{
"status": constants.PollingPriorityStatusCompleted,
"result": constants.PollingPriorityResultSuccess,
"failure_reason": "",
"updated_at": time.Now(),
"finished_at": now,
"next_run_at": nil,
"dequeued_at": now,
"updated_at": now,
})
if result.Error != nil {
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "更新优先轮询项完成状态失败")
@@ -242,13 +341,17 @@ func (s *PollingPriorityItemStore) MarkCompleted(ctx context.Context, id uint) (
// MarkFailed 把执行中的优先项置为失败终态并出队failureReason 只写可安全对外展示的原因。
func (s *PollingPriorityItemStore) MarkFailed(ctx context.Context, id uint, failureReason string) (bool, error) {
now := time.Now()
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status = ?", id, constants.PollingPriorityStatusProcessing).
Updates(map[string]any{
"status": constants.PollingPriorityStatusFailed,
"result": constants.PollingPriorityResultFailed,
"failure_reason": failureReason,
"updated_at": time.Now(),
"finished_at": now,
"next_run_at": nil,
"dequeued_at": now,
"updated_at": now,
})
if result.Error != nil {
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "更新优先轮询项失败状态失败")
@@ -256,9 +359,10 @@ func (s *PollingPriorityItemStore) MarkFailed(ctx context.Context, id uint, fail
return result.RowsAffected > 0, nil
}
// ReturnToPending 把未达尝试上限的可恢复失败退回活动态,并按既有轮询间隔由调用方重新排期
// 保留本次失败结果与原因,供读侧展示在途重试;释放认领租约。
func (s *PollingPriorityItemStore) ReturnToPending(ctx context.Context, id uint, failureReason string) (bool, error) {
// ReturnToPending 把未达尝试上限的可恢复失败退回活动态,并写下一次计划执行时间
// 保留本次失败结果与原因,供读侧展示在途重试;释放认领租约并记录本次执行的结束时间
func (s *PollingPriorityItemStore) ReturnToPending(ctx context.Context, id uint, failureReason string, nextRunAt time.Time) (bool, error) {
now := time.Now()
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status = ?", id, constants.PollingPriorityStatusProcessing).
Updates(map[string]any{
@@ -266,7 +370,9 @@ func (s *PollingPriorityItemStore) ReturnToPending(ctx context.Context, id uint,
"result": constants.PollingPriorityResultFailed,
"failure_reason": failureReason,
"claimed_at": nil,
"updated_at": time.Now(),
"finished_at": now,
"next_run_at": nextRunAt,
"updated_at": now,
})
if result.Error != nil {
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "退回优先轮询项活动状态失败")
@@ -274,6 +380,136 @@ func (s *PollingPriorityItemStore) ReturnToPending(ctx context.Context, id uint,
return result.RowsAffected > 0, nil
}
// Close 人工关闭未完成(待执行或执行中)的优先项:转已关闭终态并出队。
// 以条件更新限定「仅未完成项」并被调用方检查行数,避免与并发执行互相覆盖;
// 既有执行结果与失败原因一律保留,关闭原因只进审计,不写入失败原因。
func (s *PollingPriorityItemStore) Close(ctx context.Context, id uint) (bool, error) {
now := time.Now()
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status IN ?", id, constants.PollingPriorityActiveStatuses()).
Updates(map[string]any{
"status": constants.PollingPriorityStatusClosed,
"claimed_at": nil,
"next_run_at": nil,
"dequeued_at": now,
"updated_at": now,
})
if result.Error != nil {
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "关闭优先轮询项失败")
}
return result.RowsAffected > 0, nil
}
// ListOverdueActive 扫描已超过有效期且仍未完成的优先项,供有效期到期周期任务逐条终结。
//
// 判定依据是有效期起算时间加固定有效期,与下一次计划执行时间无关:
// next_run_at 为空只表示「未排期」,读侧与扫描都 MUST NOT 据此判定已过期。
// 有效期起算时间缺失的历史行按入队时间created_at兜底避免这类行永不被终结。
func (s *PollingPriorityItemStore) ListOverdueActive(ctx context.Context, deadline time.Time, limit int) ([]*model.PollingPriorityItem, error) {
if limit <= 0 {
return nil, nil
}
var items []*model.PollingPriorityItem
if err := s.db.WithContext(ctx).
Where("status IN ? AND COALESCE(priority_effective_from, created_at) <= ?",
constants.PollingPriorityActiveStatuses(), deadline).
Order("COALESCE(priority_effective_from, created_at) ASC, id ASC").
Limit(limit).
Find(&items).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "扫描超期优先轮询项失败")
}
return items, nil
}
// MarkExpired 把未完成的优先项转已过期终态并出队。
// 触发类型、尝试次数与最近失败原因全部保留:超期是队列出口,不是对既有事实的否定。
func (s *PollingPriorityItemStore) MarkExpired(ctx context.Context, id uint) (bool, error) {
now := time.Now()
result := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("id = ? AND status IN ?", id, constants.PollingPriorityActiveStatuses()).
Updates(map[string]any{
"status": constants.PollingPriorityStatusExpired,
"claimed_at": nil,
"next_run_at": nil,
"dequeued_at": now,
"updated_at": now,
})
if result.Error != nil {
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "标记优先轮询项已过期失败")
}
return result.RowsAffected > 0, nil
}
// PriorityPollingCardProjection 是单张卡在资产详情投影里的优先轮询事实摘要。
type PriorityPollingCardProjection struct {
// HasActive 表示该卡存在待执行或执行中的优先轮询项。
HasActive bool
// Latest 是该卡最近一条优先轮询事实(含终态),无事实时为 nil。
Latest *model.PollingPriorityItem
}
// ProjectPriorityPolling 返回指定卡的优先轮询只读投影摘要:是否存在活动项与最近一条事实。
//
// 只读:不创建、不合并、不推进、不出队,也不发起任何上游调用。
// shopIDs 数据范围语义与 GetByIDScoped 一致nil 表示不受限,空切片等于空结果。
// 活动项受部分唯一索引约束(每卡每任务类型至多一条),因此第一条查询行数有界;
// 最近一条事实按卡逐条取一行(卡的集合来自单个资产的绑定关系,规模有界)。
func (s *PollingPriorityItemStore) ProjectPriorityPolling(ctx context.Context, cardIDs []uint, shopIDs []uint) (map[uint]PriorityPollingCardProjection, error) {
result := make(map[uint]PriorityPollingCardProjection, len(cardIDs))
if len(cardIDs) == 0 {
return result, nil
}
activeQuery := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).
Where("card_id IN ? AND status IN ?", cardIDs, constants.PollingPriorityActiveStatuses())
activeQuery = applyPollingPriorityShopScope(activeQuery, shopIDs)
var activeItems []*model.PollingPriorityItem
if err := activeQuery.Find(&activeItems).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询优先轮询活动项投影失败")
}
for _, item := range activeItems {
if item == nil {
continue
}
summary := result[item.CardID]
summary.HasActive = true
summary.Latest = pickNewerPriorityItem(summary.Latest, item)
result[item.CardID] = summary
}
for _, cardID := range cardIDs {
summary, exists := result[cardID]
if exists && summary.Latest != nil {
continue
}
latestQuery := s.db.WithContext(ctx).Model(&model.PollingPriorityItem{}).Where("card_id = ?", cardID)
latestQuery = applyPollingPriorityShopScope(latestQuery, shopIDs)
var latest model.PollingPriorityItem
if err := latestQuery.Order("id DESC").Limit(1).Find(&latest).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询优先轮询最近事实投影失败")
}
if latest.ID == 0 {
if !exists {
result[cardID] = PriorityPollingCardProjection{}
}
continue
}
summary.Latest = &latest
result[cardID] = summary
}
return result, nil
}
// pickNewerPriorityItem 取两条事实里更新的一条(按创建顺序,与列表排序口径一致)。
func pickNewerPriorityItem(current, candidate *model.PollingPriorityItem) *model.PollingPriorityItem {
if candidate == nil {
return current
}
if current == nil || candidate.ID > current.ID {
return candidate
}
return current
}
// GetByIDScoped 按主键读取优先项,并应用店铺快照数据范围。
// shopIDs 为 nil 表示不受限(超级管理员与平台账号);非 nil 时空切片表示空范围,必然无结果。
// 越权与不存在都返回 gorm.ErrRecordNotFound由调用方收敛为同一响应不产生可枚举差异。
@@ -317,6 +553,19 @@ func (s *PollingPriorityItemStore) List(ctx context.Context, filter PollingPrior
if filter.TriggerType != nil && *filter.TriggerType != "" {
query = query.Where("trigger_types LIKE ?", "%,"+*filter.TriggerType+",%")
}
if filter.CreatedFrom != nil {
query = query.Where("created_at >= ?", *filter.CreatedFrom)
}
if filter.CreatedTo != nil {
query = query.Where("created_at <= ?", *filter.CreatedTo)
}
if filter.AbnormalOnly {
// 「异常与重试」视图:四个条件取或,全部来自同一事实行的状态、尝试次数与失败原因,
// 不引入独立异常事实表,也不改变其余筛选的「与」语义。
query = query.Where(
"(status IN ? OR attempt_count >= attempt_limit OR failure_reason <> '')",
[]string{constants.PollingPriorityStatusFailed, constants.PollingPriorityStatusExpired})
}
if filter.ShopIDSnapshot != nil {
query = applyPollingPriorityShopScope(query, filter.ShopIDSnapshot)
}
@@ -336,18 +585,34 @@ func (s *PollingPriorityItemStore) List(ctx context.Context, filter PollingPrior
}
// normalizePollingPriorityItem 收敛优先项自身的结构不变量,调用方只需给出业务字段。
// 入队时间同时作为有效期起算时间:有效期长度是代码常量,既有活动行不因合并触发延长或重置。
func normalizePollingPriorityItem(item *model.PollingPriorityItem) error {
if item == nil || item.CardID == 0 || strings.TrimSpace(item.TaskType) == "" || strings.TrimSpace(item.TriggerType) == "" {
return errors.New(errors.CodeInvalidParam, "优先轮询项缺少卡、任务类型或触发类型")
}
if strings.TrimSpace(item.AssetType) == "" || item.AssetID == nil || *item.AssetID == 0 {
// 资产身份是入队时解析的必填事实:缺失说明调用方跳过了快照解析,读侧将无法给出资产标识。
return errors.New(errors.CodeInvalidParam, "优先轮询项缺少资产身份快照")
}
now := time.Now()
item.Status = constants.PollingPriorityStatusPending
item.TriggerTypes = "," + item.TriggerType + ","
item.TriggerCount = 1
if item.LastTriggeredAt.IsZero() {
item.LastTriggeredAt = time.Now()
item.LastTriggeredAt = now
}
item.AttemptCount = 0
item.AttemptLimit = constants.PollingPriorityMaxAttempts
if item.PriorityEffectiveFrom == nil {
effectiveFrom := now
item.PriorityEffectiveFrom = &effectiveFrom
}
item.ClaimedAt = nil
item.StartedAt = nil
item.FinishedAt = nil
item.NextRunAt = nil
item.DequeuedAt = nil
item.IntegrationLogID = ""
item.Result = ""
item.FailureReason = ""
return nil