Merge pull request #335 from geminiwen/main
feat(subscription): 支持调整订阅时长(延长/缩短)
This commit is contained in:
@@ -53,9 +53,9 @@ type BulkAssignSubscriptionRequest struct {
|
|||||||
Notes string `json:"notes"`
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendSubscriptionRequest represents extend subscription request
|
// AdjustSubscriptionRequest represents adjust subscription request (extend or shorten)
|
||||||
type ExtendSubscriptionRequest struct {
|
type AdjustSubscriptionRequest struct {
|
||||||
Days int `json:"days" binding:"required,min=1,max=36500"` // max 100 years
|
Days int `json:"days" binding:"required,min=-36500,max=36500"` // negative to shorten, positive to extend
|
||||||
}
|
}
|
||||||
|
|
||||||
// List handles listing all subscriptions with pagination and filters
|
// List handles listing all subscriptions with pagination and filters
|
||||||
@@ -180,7 +180,7 @@ func (h *SubscriptionHandler) BulkAssign(c *gin.Context) {
|
|||||||
response.Success(c, dto.BulkAssignResultFromService(result))
|
response.Success(c, dto.BulkAssignResultFromService(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend handles extending a subscription
|
// Extend handles adjusting a subscription (extend or shorten)
|
||||||
// POST /api/v1/admin/subscriptions/:id/extend
|
// POST /api/v1/admin/subscriptions/:id/extend
|
||||||
func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
||||||
subscriptionID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
subscriptionID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
@@ -189,7 +189,7 @@ func (h *SubscriptionHandler) Extend(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req ExtendSubscriptionRequest
|
var req AdjustSubscriptionRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
response.BadRequest(c, "Invalid request: "+err.Error())
|
response.BadRequest(c, "Invalid request: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var (
|
|||||||
ErrWeeklyLimitExceeded = infraerrors.TooManyRequests("WEEKLY_LIMIT_EXCEEDED", "weekly usage limit exceeded")
|
ErrWeeklyLimitExceeded = infraerrors.TooManyRequests("WEEKLY_LIMIT_EXCEEDED", "weekly usage limit exceeded")
|
||||||
ErrMonthlyLimitExceeded = infraerrors.TooManyRequests("MONTHLY_LIMIT_EXCEEDED", "monthly usage limit exceeded")
|
ErrMonthlyLimitExceeded = infraerrors.TooManyRequests("MONTHLY_LIMIT_EXCEEDED", "monthly usage limit exceeded")
|
||||||
ErrSubscriptionNilInput = infraerrors.BadRequest("SUBSCRIPTION_NIL_INPUT", "subscription input cannot be nil")
|
ErrSubscriptionNilInput = infraerrors.BadRequest("SUBSCRIPTION_NIL_INPUT", "subscription input cannot be nil")
|
||||||
|
ErrAdjustWouldExpire = infraerrors.BadRequest("ADJUST_WOULD_EXPIRE", "adjustment would result in expired subscription (remaining days must be > 0)")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SubscriptionService 订阅服务
|
// SubscriptionService 订阅服务
|
||||||
@@ -308,17 +309,20 @@ func (s *SubscriptionService) RevokeSubscription(ctx context.Context, subscripti
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendSubscription 延长订阅
|
// ExtendSubscription 调整订阅时长(正数延长,负数缩短)
|
||||||
func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscriptionID int64, days int) (*UserSubscription, error) {
|
func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscriptionID int64, days int) (*UserSubscription, error) {
|
||||||
sub, err := s.userSubRepo.GetByID(ctx, subscriptionID)
|
sub, err := s.userSubRepo.GetByID(ctx, subscriptionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrSubscriptionNotFound
|
return nil, ErrSubscriptionNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限制延长天数
|
// 限制调整天数范围
|
||||||
if days > MaxValidityDays {
|
if days > MaxValidityDays {
|
||||||
days = MaxValidityDays
|
days = MaxValidityDays
|
||||||
}
|
}
|
||||||
|
if days < -MaxValidityDays {
|
||||||
|
days = -MaxValidityDays
|
||||||
|
}
|
||||||
|
|
||||||
// 计算新的过期时间
|
// 计算新的过期时间
|
||||||
newExpiresAt := sub.ExpiresAt.AddDate(0, 0, days)
|
newExpiresAt := sub.ExpiresAt.AddDate(0, 0, days)
|
||||||
@@ -326,6 +330,14 @@ func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscripti
|
|||||||
newExpiresAt = MaxExpiresAt
|
newExpiresAt = MaxExpiresAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果是缩短(负数),检查新的过期时间必须大于当前时间
|
||||||
|
if days < 0 {
|
||||||
|
now := time.Now()
|
||||||
|
if !newExpiresAt.After(now) {
|
||||||
|
return nil, ErrAdjustWouldExpire
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.userSubRepo.ExtendExpiry(ctx, subscriptionID, newExpiresAt); err != nil {
|
if err := s.userSubRepo.ExtendExpiry(ctx, subscriptionID, newExpiresAt); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<button @click="$emit('refresh')" :disabled="loading" class="btn btn-secondary">
|
<button @click="$emit('refresh')" :disabled="loading" class="btn btn-secondary">
|
||||||
<Icon name="refresh" size="md" :class="[loading ? 'animate-spin' : '']" />
|
<Icon name="refresh" size="md" :class="[loading ? 'animate-spin' : '']" />
|
||||||
</button>
|
</button>
|
||||||
|
<slot name="after"></slot>
|
||||||
<button @click="$emit('sync')" class="btn btn-secondary">{{ t('admin.accounts.syncFromCrs') }}</button>
|
<button @click="$emit('sync')" class="btn btn-secondary">{{ t('admin.accounts.syncFromCrs') }}</button>
|
||||||
<button @click="$emit('create')" class="btn btn-primary">{{ t('admin.accounts.createAccount') }}</button>
|
<button @click="$emit('create')" class="btn btn-primary">{{ t('admin.accounts.createAccount') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -950,7 +950,7 @@ export default {
|
|||||||
title: 'Subscription Management',
|
title: 'Subscription Management',
|
||||||
description: 'Manage user subscriptions and quota limits',
|
description: 'Manage user subscriptions and quota limits',
|
||||||
assignSubscription: 'Assign Subscription',
|
assignSubscription: 'Assign Subscription',
|
||||||
extendSubscription: 'Extend Subscription',
|
adjustSubscription: 'Adjust Subscription',
|
||||||
revokeSubscription: 'Revoke Subscription',
|
revokeSubscription: 'Revoke Subscription',
|
||||||
allStatus: 'All Status',
|
allStatus: 'All Status',
|
||||||
allGroups: 'All Groups',
|
allGroups: 'All Groups',
|
||||||
@@ -965,6 +965,7 @@ export default {
|
|||||||
resetInHoursMinutes: 'Resets in {hours}h {minutes}m',
|
resetInHoursMinutes: 'Resets in {hours}h {minutes}m',
|
||||||
resetInDaysHours: 'Resets in {days}d {hours}h',
|
resetInDaysHours: 'Resets in {days}d {hours}h',
|
||||||
daysRemaining: 'days remaining',
|
daysRemaining: 'days remaining',
|
||||||
|
remainingDays: 'Remaining days',
|
||||||
noExpiration: 'No expiration',
|
noExpiration: 'No expiration',
|
||||||
status: {
|
status: {
|
||||||
active: 'Active',
|
active: 'Active',
|
||||||
@@ -983,28 +984,32 @@ export default {
|
|||||||
user: 'User',
|
user: 'User',
|
||||||
group: 'Subscription Group',
|
group: 'Subscription Group',
|
||||||
validityDays: 'Validity (Days)',
|
validityDays: 'Validity (Days)',
|
||||||
extendDays: 'Extend by (Days)'
|
adjustDays: 'Adjust by (Days)'
|
||||||
},
|
},
|
||||||
selectUser: 'Select a user',
|
selectUser: 'Select a user',
|
||||||
selectGroup: 'Select a subscription group',
|
selectGroup: 'Select a subscription group',
|
||||||
groupHint: 'Only groups with subscription billing type are shown',
|
groupHint: 'Only groups with subscription billing type are shown',
|
||||||
validityHint: 'Number of days the subscription will be valid',
|
validityHint: 'Number of days the subscription will be valid',
|
||||||
extendingFor: 'Extending subscription for',
|
adjustingFor: 'Adjusting subscription for',
|
||||||
currentExpiration: 'Current expiration',
|
currentExpiration: 'Current expiration',
|
||||||
|
adjustDaysPlaceholder: 'Positive to extend, negative to shorten',
|
||||||
|
adjustHint: 'Enter positive number to extend, negative to shorten (remaining days must be > 0)',
|
||||||
assign: 'Assign',
|
assign: 'Assign',
|
||||||
assigning: 'Assigning...',
|
assigning: 'Assigning...',
|
||||||
extend: 'Extend',
|
adjust: 'Adjust',
|
||||||
extending: 'Extending...',
|
adjusting: 'Adjusting...',
|
||||||
revoke: 'Revoke',
|
revoke: 'Revoke',
|
||||||
noSubscriptionsYet: 'No subscriptions yet',
|
noSubscriptionsYet: 'No subscriptions yet',
|
||||||
assignFirstSubscription: 'Assign a subscription to get started.',
|
assignFirstSubscription: 'Assign a subscription to get started.',
|
||||||
subscriptionAssigned: 'Subscription assigned successfully',
|
subscriptionAssigned: 'Subscription assigned successfully',
|
||||||
subscriptionExtended: 'Subscription extended successfully',
|
subscriptionAdjusted: 'Subscription adjusted successfully',
|
||||||
subscriptionRevoked: 'Subscription revoked successfully',
|
subscriptionRevoked: 'Subscription revoked successfully',
|
||||||
failedToLoad: 'Failed to load subscriptions',
|
failedToLoad: 'Failed to load subscriptions',
|
||||||
failedToAssign: 'Failed to assign subscription',
|
failedToAssign: 'Failed to assign subscription',
|
||||||
failedToExtend: 'Failed to extend subscription',
|
failedToAdjust: 'Failed to adjust subscription',
|
||||||
failedToRevoke: 'Failed to revoke subscription',
|
failedToRevoke: 'Failed to revoke subscription',
|
||||||
|
adjustWouldExpire: 'Remaining days after adjustment must be greater than 0',
|
||||||
|
adjustOutOfRange: 'Adjustment days must be between -36500 and 36500',
|
||||||
pleaseSelectUser: 'Please select a user',
|
pleaseSelectUser: 'Please select a user',
|
||||||
pleaseSelectGroup: 'Please select a group',
|
pleaseSelectGroup: 'Please select a group',
|
||||||
validityDaysRequired: 'Please enter a valid number of days (at least 1)',
|
validityDaysRequired: 'Please enter a valid number of days (at least 1)',
|
||||||
|
|||||||
@@ -1025,7 +1025,7 @@ export default {
|
|||||||
title: '订阅管理',
|
title: '订阅管理',
|
||||||
description: '管理用户订阅和配额限制',
|
description: '管理用户订阅和配额限制',
|
||||||
assignSubscription: '分配订阅',
|
assignSubscription: '分配订阅',
|
||||||
extendSubscription: '延长订阅',
|
adjustSubscription: '调整订阅',
|
||||||
revokeSubscription: '撤销订阅',
|
revokeSubscription: '撤销订阅',
|
||||||
allStatus: '全部状态',
|
allStatus: '全部状态',
|
||||||
allGroups: '全部分组',
|
allGroups: '全部分组',
|
||||||
@@ -1040,6 +1040,7 @@ export default {
|
|||||||
resetInHoursMinutes: '{hours} 小时 {minutes} 分钟后重置',
|
resetInHoursMinutes: '{hours} 小时 {minutes} 分钟后重置',
|
||||||
resetInDaysHours: '{days} 天 {hours} 小时后重置',
|
resetInDaysHours: '{days} 天 {hours} 小时后重置',
|
||||||
daysRemaining: '天剩余',
|
daysRemaining: '天剩余',
|
||||||
|
remainingDays: '剩余天数',
|
||||||
noExpiration: '无过期时间',
|
noExpiration: '无过期时间',
|
||||||
status: {
|
status: {
|
||||||
active: '生效中',
|
active: '生效中',
|
||||||
@@ -1058,28 +1059,32 @@ export default {
|
|||||||
user: '用户',
|
user: '用户',
|
||||||
group: '订阅分组',
|
group: '订阅分组',
|
||||||
validityDays: '有效期(天)',
|
validityDays: '有效期(天)',
|
||||||
extendDays: '延长天数'
|
adjustDays: '调整天数'
|
||||||
},
|
},
|
||||||
selectUser: '选择用户',
|
selectUser: '选择用户',
|
||||||
selectGroup: '选择订阅分组',
|
selectGroup: '选择订阅分组',
|
||||||
groupHint: '仅显示订阅计费类型的分组',
|
groupHint: '仅显示订阅计费类型的分组',
|
||||||
validityHint: '订阅的有效天数',
|
validityHint: '订阅的有效天数',
|
||||||
extendingFor: '为以下用户延长订阅',
|
adjustingFor: '为以下用户调整订阅',
|
||||||
currentExpiration: '当前到期时间',
|
currentExpiration: '当前到期时间',
|
||||||
|
adjustDaysPlaceholder: '正数延长,负数缩短',
|
||||||
|
adjustHint: '输入正数延长订阅,负数缩短订阅(缩短后剩余天数需大于0)',
|
||||||
assign: '分配',
|
assign: '分配',
|
||||||
assigning: '分配中...',
|
assigning: '分配中...',
|
||||||
extend: '延长',
|
adjust: '调整',
|
||||||
extending: '延长中...',
|
adjusting: '调整中...',
|
||||||
revoke: '撤销',
|
revoke: '撤销',
|
||||||
noSubscriptionsYet: '暂无订阅',
|
noSubscriptionsYet: '暂无订阅',
|
||||||
assignFirstSubscription: '分配一个订阅以开始使用。',
|
assignFirstSubscription: '分配一个订阅以开始使用。',
|
||||||
subscriptionAssigned: '订阅分配成功',
|
subscriptionAssigned: '订阅分配成功',
|
||||||
subscriptionExtended: '订阅延长成功',
|
subscriptionAdjusted: '订阅调整成功',
|
||||||
subscriptionRevoked: '订阅撤销成功',
|
subscriptionRevoked: '订阅撤销成功',
|
||||||
failedToLoad: '加载订阅列表失败',
|
failedToLoad: '加载订阅列表失败',
|
||||||
failedToAssign: '分配订阅失败',
|
failedToAssign: '分配订阅失败',
|
||||||
failedToExtend: '延长订阅失败',
|
failedToAdjust: '调整订阅失败',
|
||||||
failedToRevoke: '撤销订阅失败',
|
failedToRevoke: '撤销订阅失败',
|
||||||
|
adjustWouldExpire: '调整后剩余天数必须大于0',
|
||||||
|
adjustOutOfRange: '调整天数必须在 -36500 到 36500 之间',
|
||||||
pleaseSelectUser: '请选择用户',
|
pleaseSelectUser: '请选择用户',
|
||||||
pleaseSelectGroup: '请选择分组',
|
pleaseSelectGroup: '请选择分组',
|
||||||
validityDaysRequired: '请输入有效的天数(至少1天)',
|
validityDaysRequired: '请输入有效的天数(至少1天)',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
@sync="showSync = true"
|
@sync="showSync = true"
|
||||||
@create="showCreate = true"
|
@create="showCreate = true"
|
||||||
>
|
>
|
||||||
<template #before>
|
<template #after>
|
||||||
<!-- Column Settings Dropdown -->
|
<!-- Column Settings Dropdown -->
|
||||||
<div class="relative" ref="columnDropdownRef">
|
<div class="relative" ref="columnDropdownRef">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -85,6 +85,14 @@
|
|||||||
|
|
||||||
<!-- Right: Actions -->
|
<!-- Right: Actions -->
|
||||||
<div class="ml-auto flex flex-wrap items-center justify-end gap-3">
|
<div class="ml-auto flex flex-wrap items-center justify-end gap-3">
|
||||||
|
<button
|
||||||
|
@click="loadSubscriptions"
|
||||||
|
:disabled="loading"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
:title="t('common.refresh')"
|
||||||
|
>
|
||||||
|
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
|
||||||
|
</button>
|
||||||
<!-- Column Settings Dropdown -->
|
<!-- Column Settings Dropdown -->
|
||||||
<div class="relative" ref="columnDropdownRef">
|
<div class="relative" ref="columnDropdownRef">
|
||||||
<button
|
<button
|
||||||
@@ -136,14 +144,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
@click="loadSubscriptions"
|
|
||||||
:disabled="loading"
|
|
||||||
class="btn btn-secondary"
|
|
||||||
:title="t('common.refresh')"
|
|
||||||
>
|
|
||||||
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
|
|
||||||
</button>
|
|
||||||
<button @click="showAssignModal = true" class="btn btn-primary">
|
<button @click="showAssignModal = true" class="btn btn-primary">
|
||||||
<Icon name="plus" size="md" class="mr-2" />
|
<Icon name="plus" size="md" class="mr-2" />
|
||||||
{{ t('admin.subscriptions.assignSubscription') }}
|
{{ t('admin.subscriptions.assignSubscription') }}
|
||||||
@@ -359,10 +359,10 @@
|
|||||||
<button
|
<button
|
||||||
v-if="row.status === 'active'"
|
v-if="row.status === 'active'"
|
||||||
@click="handleExtend(row)"
|
@click="handleExtend(row)"
|
||||||
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400"
|
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
<Icon name="clock" size="sm" />
|
<Icon name="calendar" size="sm" />
|
||||||
<span class="text-xs">{{ t('admin.subscriptions.extend') }}</span>
|
<span class="text-xs">{{ t('admin.subscriptions.adjust') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="row.status === 'active'"
|
v-if="row.status === 'active'"
|
||||||
@@ -512,10 +512,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
||||||
<!-- Extend Subscription Modal -->
|
<!-- Adjust Subscription Modal -->
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
:show="showExtendModal"
|
:show="showExtendModal"
|
||||||
:title="t('admin.subscriptions.extendSubscription')"
|
:title="t('admin.subscriptions.adjustSubscription')"
|
||||||
width="narrow"
|
width="narrow"
|
||||||
@close="closeExtendModal"
|
@close="closeExtendModal"
|
||||||
>
|
>
|
||||||
@@ -527,7 +527,7 @@
|
|||||||
>
|
>
|
||||||
<div class="rounded-lg bg-gray-50 p-4 dark:bg-dark-700">
|
<div class="rounded-lg bg-gray-50 p-4 dark:bg-dark-700">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ t('admin.subscriptions.extendingFor') }}
|
{{ t('admin.subscriptions.adjustingFor') }}
|
||||||
<span class="font-medium text-gray-900 dark:text-white">{{
|
<span class="font-medium text-gray-900 dark:text-white">{{
|
||||||
extendingSubscription.user?.email
|
extendingSubscription.user?.email
|
||||||
}}</span>
|
}}</span>
|
||||||
@@ -542,10 +542,25 @@
|
|||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
<p v-if="extendingSubscription.expires_at" class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{{ t('admin.subscriptions.remainingDays') }}:
|
||||||
|
<span class="font-medium text-gray-900 dark:text-white">
|
||||||
|
{{ getDaysRemaining(extendingSubscription.expires_at) ?? 0 }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="input-label">{{ t('admin.subscriptions.form.extendDays') }}</label>
|
<label class="input-label">{{ t('admin.subscriptions.form.adjustDays') }}</label>
|
||||||
<input v-model.number="extendForm.days" type="number" min="1" required class="input" />
|
<div class="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
v-model.number="extendForm.days"
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
class="input text-center"
|
||||||
|
:placeholder="t('admin.subscriptions.adjustDaysPlaceholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="input-hint">{{ t('admin.subscriptions.adjustHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -559,7 +574,7 @@
|
|||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
>
|
>
|
||||||
{{ submitting ? t('admin.subscriptions.extending') : t('admin.subscriptions.extend') }}
|
{{ submitting ? t('admin.subscriptions.adjusting') : t('admin.subscriptions.adjust') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1000,17 +1015,27 @@ const closeExtendModal = () => {
|
|||||||
const handleExtendSubscription = async () => {
|
const handleExtendSubscription = async () => {
|
||||||
if (!extendingSubscription.value) return
|
if (!extendingSubscription.value) return
|
||||||
|
|
||||||
|
// 前端验证:调整后剩余天数必须 > 0
|
||||||
|
if (extendingSubscription.value.expires_at) {
|
||||||
|
const currentDaysRemaining = getDaysRemaining(extendingSubscription.value.expires_at) ?? 0
|
||||||
|
const newDaysRemaining = currentDaysRemaining + extendForm.days
|
||||||
|
if (newDaysRemaining <= 0) {
|
||||||
|
appStore.showError(t('admin.subscriptions.adjustWouldExpire'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
await adminAPI.subscriptions.extend(extendingSubscription.value.id, {
|
await adminAPI.subscriptions.extend(extendingSubscription.value.id, {
|
||||||
days: extendForm.days
|
days: extendForm.days
|
||||||
})
|
})
|
||||||
appStore.showSuccess(t('admin.subscriptions.subscriptionExtended'))
|
appStore.showSuccess(t('admin.subscriptions.subscriptionAdjusted'))
|
||||||
closeExtendModal()
|
closeExtendModal()
|
||||||
loadSubscriptions()
|
loadSubscriptions()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
appStore.showError(error.response?.data?.detail || t('admin.subscriptions.failedToExtend'))
|
appStore.showError(error.response?.data?.detail || t('admin.subscriptions.failedToAdjust'))
|
||||||
console.error('Error extending subscription:', error)
|
console.error('Error adjusting subscription:', error)
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user