package usecase import ( "context" "strings" "haixun-backend/internal/library/clock" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" "haixun-backend/internal/model/publish_inventory/domain/entity" domrepo "haixun-backend/internal/model/publish_inventory/domain/repository" domusecase "haixun-backend/internal/model/publish_inventory/domain/usecase" threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase" "github.com/google/uuid" ) type inventoryUseCase struct { repo domrepo.Repository threadsAccount threadsaccountdomain.UseCase } func NewUseCase(repo domrepo.Repository, threadsAccount threadsaccountdomain.UseCase) domusecase.UseCase { return &inventoryUseCase{repo: repo, threadsAccount: threadsAccount} } func (u *inventoryUseCase) Get(ctx context.Context, tenantID, ownerUID, accountID string) (*domusecase.PolicySummary, error) { if err := u.require(ctx, tenantID, ownerUID, accountID); err != nil { return nil, err } item, err := u.repo.Get(ctx, tenantID, ownerUID, accountID) if err != nil { if strings.Contains(err.Error(), "not found") { item = defaultPolicy(tenantID, ownerUID, accountID) } else { return nil, err } } out := toSummary(item) return &out, nil } func (u *inventoryUseCase) Upsert(ctx context.Context, req domusecase.UpsertRequest) (*domusecase.PolicySummary, error) { if err := u.require(ctx, req.TenantID, req.OwnerUID, req.AccountID); err != nil { return nil, err } item := defaultPolicy(req.TenantID, req.OwnerUID, req.AccountID) item.Enabled = req.Enabled if req.TargetDailyCount > 0 { item.TargetDailyCount = req.TargetDailyCount } if req.LowStockThreshold > 0 { item.LowStockThreshold = req.LowStockThreshold } if req.LookaheadDays > 0 { item.LookaheadDays = req.LookaheadDays } if strings.TrimSpace(req.Timezone) != "" { item.Timezone = strings.TrimSpace(req.Timezone) } item.Slots = slotsToEntity(req.Slots) item.SourceRefs = sourceRefsToEntity(req.SourceRefs) now := clock.NowUnixNano() item.UpdateAt = now saved, err := u.repo.Upsert(ctx, item) if err != nil { return nil, err } out := toSummary(saved) return &out, nil } func (u *inventoryUseCase) ListEnabled(ctx context.Context, limit int) ([]domusecase.PolicySummary, error) { items, err := u.repo.ListEnabled(ctx, limit) if err != nil { return nil, err } out := make([]domusecase.PolicySummary, 0, len(items)) for _, item := range items { summary := toSummary(&item) out = append(out, summary) } return out, nil } func (u *inventoryUseCase) require(ctx context.Context, tenantID, ownerUID, accountID string) error { if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" { return app.For(code.Auth).AuthUnauthorized("missing actor") } if strings.TrimSpace(accountID) == "" { return app.For(code.ThreadsAccount).InputMissingRequired("account_id is required") } if u.threadsAccount != nil { _, err := u.threadsAccount.Get(ctx, tenantID, ownerUID, accountID) return err } return nil } func defaultPolicy(tenantID, ownerUID, accountID string) *entity.Policy { now := clock.NowUnixNano() return &entity.Policy{ ID: uuid.NewString(), TenantID: strings.TrimSpace(tenantID), OwnerUID: strings.TrimSpace(ownerUID), AccountID: strings.TrimSpace(accountID), Enabled: false, TargetDailyCount: 2, LowStockThreshold: 3, LookaheadDays: 7, Timezone: "Asia/Taipei", Slots: []entity.Slot{ {Weekday: 1, Time: "09:30"}, {Weekday: 3, Time: "12:30"}, {Weekday: 5, Time: "18:30"}, }, CreateAt: now, UpdateAt: now, } } func toSummary(item *entity.Policy) domusecase.PolicySummary { if item == nil { return domusecase.PolicySummary{} } return domusecase.PolicySummary{ AccountID: item.AccountID, Enabled: item.Enabled, TargetDailyCount: item.TargetDailyCount, LowStockThreshold: item.LowStockThreshold, LookaheadDays: item.LookaheadDays, Timezone: item.Timezone, Slots: slotsFromEntity(item.Slots), SourceRefs: sourceRefsFromEntity(item.SourceRefs), UpdateAt: item.UpdateAt, } } func slotsToEntity(items []domusecase.Slot) []entity.Slot { out := make([]entity.Slot, 0, len(items)) for _, item := range items { if strings.TrimSpace(item.Time) == "" { continue } out = append(out, entity.Slot{Weekday: item.Weekday, Time: strings.TrimSpace(item.Time)}) } return out } func slotsFromEntity(items []entity.Slot) []domusecase.Slot { out := make([]domusecase.Slot, 0, len(items)) for _, item := range items { out = append(out, domusecase.Slot{Weekday: item.Weekday, Time: item.Time}) } return out } func sourceRefsToEntity(items []domusecase.SourceRef) []entity.SourceRef { out := make([]entity.SourceRef, 0, len(items)) for _, item := range items { if strings.TrimSpace(item.Type) == "" { continue } out = append(out, entity.SourceRef{ Type: strings.TrimSpace(item.Type), PersonaID: strings.TrimSpace(item.PersonaID), CopyMissionID: strings.TrimSpace(item.CopyMissionID), ScanPostID: strings.TrimSpace(item.ScanPostID), ManualSeed: strings.TrimSpace(item.ManualSeed), }) } return out } func sourceRefsFromEntity(items []entity.SourceRef) []domusecase.SourceRef { out := make([]domusecase.SourceRef, 0, len(items)) for _, item := range items { out = append(out, domusecase.SourceRef{ Type: item.Type, PersonaID: item.PersonaID, CopyMissionID: item.CopyMissionID, ScanPostID: item.ScanPostID, ManualSeed: item.ManualSeed, }) } return out }