七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
@@ -99,6 +99,13 @@ func (s *AccountStore) Update(ctx context.Context, account *model.Account) error
|
||||
return s.db.WithContext(ctx).Save(account).Error
|
||||
}
|
||||
|
||||
// BindWeCom 更新账号的企业微信成员绑定快照。
|
||||
func (s *AccountStore) BindWeCom(ctx context.Context, accountID uint, corpID, userID, name string, updater uint) error {
|
||||
return s.db.WithContext(ctx).Model(&model.Account{}).Where("id = ?", accountID).Updates(map[string]any{
|
||||
"wecom_corp_id": corpID, "wecom_userid": userID, "wecom_name": name, "updater": updater,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// Delete 软删除账号
|
||||
func (s *AccountStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Account{}, id).Error
|
||||
@@ -254,6 +261,22 @@ func (s *AccountStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Accou
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// GetDisplayAccountsByIDs 批量读取历史业务记录的账号展示信息。
|
||||
// 该查询只返回 ID 和用户名,包含已软删除账号,不用于授权判定。
|
||||
func (s *AccountStore) GetDisplayAccountsByIDs(ctx context.Context, ids []uint) ([]*model.Account, error) {
|
||||
if len(ids) == 0 {
|
||||
return []*model.Account{}, nil
|
||||
}
|
||||
var accounts []*model.Account
|
||||
if err := s.db.WithContext(ctx).Unscoped().
|
||||
Select("id", "username").
|
||||
Where("id IN ?", ids).
|
||||
Find(&accounts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (s *AccountStore) GetPrimaryAccountsByShopIDs(ctx context.Context, shopIDs []uint) ([]*model.Account, error) {
|
||||
if len(shopIDs) == 0 {
|
||||
return []*model.Account{}, nil
|
||||
|
||||
106
internal/store/postgres/asset_package_batch_order_task_store.go
Normal file
106
internal/store/postgres/asset_package_batch_order_task_store.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/asynctask"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
)
|
||||
|
||||
// AssetPackageBatchOrderTaskStore 批量订购任务数据访问层。
|
||||
type AssetPackageBatchOrderTaskStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAssetPackageBatchOrderTaskStore 创建批量订购任务 Store。
|
||||
func NewAssetPackageBatchOrderTaskStore(db *gorm.DB) *AssetPackageBatchOrderTaskStore {
|
||||
return &AssetPackageBatchOrderTaskStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建批量订购任务。
|
||||
func (s *AssetPackageBatchOrderTaskStore) Create(ctx context.Context, task *model.AssetPackageBatchOrderTask) error {
|
||||
return s.db.WithContext(ctx).Create(task).Error
|
||||
}
|
||||
|
||||
// GetByID 按 ID 查询批量订购任务。
|
||||
func (s *AssetPackageBatchOrderTaskStore) GetByID(ctx context.Context, id uint) (*model.AssetPackageBatchOrderTask, error) {
|
||||
var task model.AssetPackageBatchOrderTask
|
||||
query := s.applyVisibleScope(ctx, s.db.WithContext(ctx).Where("id = ?", id))
|
||||
if err := query.First(&task).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
// List 分页查询批量订购任务。
|
||||
func (s *AssetPackageBatchOrderTaskStore) List(ctx context.Context, opts *store.QueryOptions, status *int) ([]*model.AssetPackageBatchOrderTask, int64, error) {
|
||||
query := s.applyVisibleScope(ctx, s.db.WithContext(ctx).Model(&model.AssetPackageBatchOrderTask{}))
|
||||
if status != nil {
|
||||
query = query.Where("status = ?", *status)
|
||||
}
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if opts == nil {
|
||||
opts = &store.QueryOptions{Page: 1, PageSize: constants.DefaultPageSize}
|
||||
}
|
||||
var tasks []*model.AssetPackageBatchOrderTask
|
||||
if err := query.Order("created_at DESC").Offset((opts.Page - 1) * opts.PageSize).Limit(opts.PageSize).Find(&tasks).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return tasks, total, nil
|
||||
}
|
||||
|
||||
// Claim 领取待处理或已超时的处理中任务,避免正常执行期间被重复消费。
|
||||
func (s *AssetPackageBatchOrderTaskStore) Claim(ctx context.Context, id uint) (bool, error) {
|
||||
now := time.Now()
|
||||
staleBefore := now.Add(-constants.AssetPackageBatchOrderTaskTimeout)
|
||||
result := s.db.WithContext(ctx).Model(&model.AssetPackageBatchOrderTask{}).
|
||||
Where("id = ? AND (status = ? OR (status = ? AND started_at < ?))", id, asynctask.StatusPending, asynctask.StatusProcessing, staleBefore).
|
||||
Updates(map[string]any{"status": asynctask.StatusProcessing, "started_at": now, "updated_at": now})
|
||||
return result.RowsAffected == 1, result.Error
|
||||
}
|
||||
|
||||
func (s *AssetPackageBatchOrderTaskStore) applyVisibleScope(ctx context.Context, query *gorm.DB) *gorm.DB {
|
||||
if middleware.GetUserTypeFromContext(ctx) != constants.UserTypeAgent {
|
||||
return query
|
||||
}
|
||||
shopIDs := middleware.GetSubordinateShopIDs(ctx)
|
||||
if len(shopIDs) == 0 {
|
||||
return query.Where("1 = 0")
|
||||
}
|
||||
return query.Where("creator_shop_id IN ?", shopIDs)
|
||||
}
|
||||
|
||||
// MarkFailed 将任务标记为整体失败。
|
||||
func (s *AssetPackageBatchOrderTaskStore) MarkFailed(ctx context.Context, id uint, message string) error {
|
||||
now := time.Now()
|
||||
return s.db.WithContext(ctx).Model(&model.AssetPackageBatchOrderTask{}).Where("id = ?", id).
|
||||
Updates(map[string]any{"status": asynctask.StatusFailed, "error_message": message, "completed_at": now, "updated_at": now}).Error
|
||||
}
|
||||
|
||||
// Complete 保存逐行结果并完成任务。
|
||||
func (s *AssetPackageBatchOrderTaskStore) Complete(ctx context.Context, id uint, items model.AssetPackageBatchOrderResultItems, successCount, failCount int) error {
|
||||
now := time.Now()
|
||||
return s.db.WithContext(ctx).Model(&model.AssetPackageBatchOrderTask{}).
|
||||
Where("id = ? AND status = ?", id, asynctask.StatusProcessing).
|
||||
Updates(map[string]any{
|
||||
"status": asynctask.StatusCompleted, "total_count": len(items),
|
||||
"success_count": successCount, "fail_count": failCount,
|
||||
"result_items": items, "completed_at": now, "updated_at": now,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// GenerateTaskNo 生成批量订购任务编号。
|
||||
func (s *AssetPackageBatchOrderTaskStore) GenerateTaskNo() string {
|
||||
now := time.Now()
|
||||
return fmt.Sprintf("BPO-%s-%06d", now.Format("20060102"), now.UnixNano()%1000000)
|
||||
}
|
||||
@@ -101,6 +101,9 @@ func (s *DeviceImportTaskStore) List(ctx context.Context, opts *store.QueryOptio
|
||||
if status, ok := filters["status"].(int); ok && status > 0 {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
if operationType, ok := filters["operation_type"].(string); ok && operationType != "" {
|
||||
query = query.Where("operation_type = ?", operationType)
|
||||
}
|
||||
if batchNo, ok := filters["batch_no"].(string); ok && batchNo != "" {
|
||||
query = query.Where("batch_no LIKE ?", "%"+batchNo+"%")
|
||||
}
|
||||
|
||||
@@ -70,6 +70,21 @@ func (s *DeviceStore) GetByIdentifier(ctx context.Context, identifier string) (*
|
||||
return &device, nil
|
||||
}
|
||||
|
||||
// GetByIdentifiers 批量按 VirtualNo、IMEI 或 SN 查询设备,并应用当前数据权限。
|
||||
func (s *DeviceStore) GetByIdentifiers(ctx context.Context, identifiers []string) ([]*model.Device, error) {
|
||||
devices := make([]*model.Device, 0)
|
||||
if len(identifiers) == 0 {
|
||||
return devices, nil
|
||||
}
|
||||
query := s.db.WithContext(ctx).
|
||||
Where("virtual_no IN ? OR imei IN ? OR sn IN ?", identifiers, identifiers, identifiers)
|
||||
query = middleware.ApplyShopFilter(ctx, query)
|
||||
if err := query.Find(&devices).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (s *DeviceStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Device, error) {
|
||||
var devices []*model.Device
|
||||
if len(ids) == 0 {
|
||||
@@ -152,6 +167,9 @@ func (s *DeviceStore) applyDeviceFilters(ctx context.Context, query *gorm.DB, fi
|
||||
if activationStatus, ok := filters["activation_status"].(int); ok {
|
||||
query = s.applyActivationStatusFilter(query, activationStatus)
|
||||
}
|
||||
if realNameStatus, ok := filters["real_name_status"].(int); ok {
|
||||
query = s.applyRealNameStatusFilter(query, realNameStatus)
|
||||
}
|
||||
if shopIDs, ok := filters["shop_ids"].([]uint); ok {
|
||||
if len(shopIDs) == 0 {
|
||||
query = query.Where("1 = 0")
|
||||
@@ -206,6 +224,27 @@ func (s *DeviceStore) applyDeviceFilters(ctx context.Context, query *gorm.DB, fi
|
||||
return query
|
||||
}
|
||||
|
||||
// applyRealNameStatusFilter 按设备当前有效绑定卡的实名状态过滤。
|
||||
// 任意有效绑定卡已实名时设备视为已实名,否则视为未实名。
|
||||
func (s *DeviceStore) applyRealNameStatusFilter(query *gorm.DB, realNameStatus int) *gorm.DB {
|
||||
verifiedBindingCondition := `EXISTS (
|
||||
SELECT 1
|
||||
FROM tb_device_sim_binding AS b
|
||||
JOIN tb_iot_card AS c
|
||||
ON c.id = b.iot_card_id
|
||||
AND c.real_name_status = ?
|
||||
AND c.deleted_at IS NULL
|
||||
WHERE b.device_id = tb_device.id
|
||||
AND b.bind_status = ?
|
||||
AND b.deleted_at IS NULL
|
||||
)`
|
||||
args := []any{constants.RealNameStatusVerified, constants.BindStatusBound}
|
||||
if realNameStatus == constants.RealNameStatusVerified {
|
||||
return query.Where(verifiedBindingCondition, args...)
|
||||
}
|
||||
return query.Where("NOT "+verifiedBindingCondition, args...)
|
||||
}
|
||||
|
||||
// applyHasActivePackageFilter 按"是否有生效中主套餐"过滤设备。
|
||||
// true: 设备在 tb_package_usage 中存在生效中(status=PackageUsageStatusActive)的主套餐记录;
|
||||
// false: 不存在上述记录。
|
||||
|
||||
@@ -902,6 +902,9 @@ func (s *IotCardStore) applyStandaloneFilters(ctx context.Context, query *gorm.D
|
||||
if networkStatus, ok := filters["network_status"].(int); ok {
|
||||
query = query.Where("network_status = ?", networkStatus)
|
||||
}
|
||||
if realNameStatus, ok := filters["real_name_status"].(int); ok {
|
||||
query = query.Where("real_name_status = ?", realNameStatus)
|
||||
}
|
||||
if enterpriseID, ok := filters["authorized_enterprise_id"].(uint); ok && enterpriseID > 0 {
|
||||
// 只返回有效授权给该企业的卡
|
||||
query = query.Where("id IN (?)",
|
||||
|
||||
@@ -144,3 +144,29 @@ func (s *RefundStore) FindActiveByOrderID(ctx context.Context, orderID uint) (*m
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
// HasUnfinishedByAsset 检查资产是否存在仍会影响资产状态的退款申请。
|
||||
// 待审批、已退回,以及已通过但退款后资产处理尚未完成的申请均视为未终结。
|
||||
func (s *RefundStore) HasUnfinishedByAsset(ctx context.Context, assetType string, assetID uint) (bool, error) {
|
||||
var assetColumn string
|
||||
switch assetType {
|
||||
case constants.ExchangeAssetTypeIotCard:
|
||||
assetColumn = "iot_card_id"
|
||||
case constants.ExchangeAssetTypeDevice:
|
||||
assetColumn = "device_id"
|
||||
default:
|
||||
return false, gorm.ErrInvalidData
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.RefundRequest{}).
|
||||
Where(assetColumn+" = ?", assetID).
|
||||
Where("status IN ? OR (status = ? AND asset_reset = ?)",
|
||||
[]int{model.RefundStatusPending, model.RefundStatusReturned},
|
||||
model.RefundStatusApproved,
|
||||
false,
|
||||
).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user