feat(notify): add percentage threshold type for balance low notification

- Add threshold_type field (fixed/percentage) to system and user settings
- Add total_recharged field to users table, auto-incremented on balance credit
- Percentage mode: effective threshold = total_recharged × percentage / 100
- User-level threshold_type inherits from system default when not set
- Update admin settings UI with radio selector (fixed amount / percentage)
- Migration: 102_add_balance_notify_threshold_type.sql
This commit is contained in:
erio
2026-04-12 13:53:02 +08:00
parent d0674e0ff9
commit f694afbbf4
27 changed files with 838 additions and 74 deletions

View File

@@ -257,6 +257,20 @@ func (_u *UserUpdate) SetNillableBalanceNotifyEnabled(v *bool) *UserUpdate {
return _u
}
// SetBalanceNotifyThresholdType sets the "balance_notify_threshold_type" field.
func (_u *UserUpdate) SetBalanceNotifyThresholdType(v string) *UserUpdate {
_u.mutation.SetBalanceNotifyThresholdType(v)
return _u
}
// SetNillableBalanceNotifyThresholdType sets the "balance_notify_threshold_type" field if the given value is not nil.
func (_u *UserUpdate) SetNillableBalanceNotifyThresholdType(v *string) *UserUpdate {
if v != nil {
_u.SetBalanceNotifyThresholdType(*v)
}
return _u
}
// SetBalanceNotifyThreshold sets the "balance_notify_threshold" field.
func (_u *UserUpdate) SetBalanceNotifyThreshold(v float64) *UserUpdate {
_u.mutation.ResetBalanceNotifyThreshold()
@@ -298,6 +312,27 @@ func (_u *UserUpdate) SetNillableBalanceNotifyExtraEmails(v *string) *UserUpdate
return _u
}
// SetTotalRecharged sets the "total_recharged" field.
func (_u *UserUpdate) SetTotalRecharged(v float64) *UserUpdate {
_u.mutation.ResetTotalRecharged()
_u.mutation.SetTotalRecharged(v)
return _u
}
// SetNillableTotalRecharged sets the "total_recharged" field if the given value is not nil.
func (_u *UserUpdate) SetNillableTotalRecharged(v *float64) *UserUpdate {
if v != nil {
_u.SetTotalRecharged(*v)
}
return _u
}
// AddTotalRecharged adds value to the "total_recharged" field.
func (_u *UserUpdate) AddTotalRecharged(v float64) *UserUpdate {
_u.mutation.AddTotalRecharged(v)
return _u
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_u *UserUpdate) AddAPIKeyIDs(ids ...int64) *UserUpdate {
_u.mutation.AddAPIKeyIDs(ids...)
@@ -804,6 +839,9 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.BalanceNotifyEnabled(); ok {
_spec.SetField(user.FieldBalanceNotifyEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.BalanceNotifyThresholdType(); ok {
_spec.SetField(user.FieldBalanceNotifyThresholdType, field.TypeString, value)
}
if value, ok := _u.mutation.BalanceNotifyThreshold(); ok {
_spec.SetField(user.FieldBalanceNotifyThreshold, field.TypeFloat64, value)
}
@@ -816,6 +854,12 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if value, ok := _u.mutation.BalanceNotifyExtraEmails(); ok {
_spec.SetField(user.FieldBalanceNotifyExtraEmails, field.TypeString, value)
}
if value, ok := _u.mutation.TotalRecharged(); ok {
_spec.SetField(user.FieldTotalRecharged, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedTotalRecharged(); ok {
_spec.AddField(user.FieldTotalRecharged, field.TypeFloat64, value)
}
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,
@@ -1518,6 +1562,20 @@ func (_u *UserUpdateOne) SetNillableBalanceNotifyEnabled(v *bool) *UserUpdateOne
return _u
}
// SetBalanceNotifyThresholdType sets the "balance_notify_threshold_type" field.
func (_u *UserUpdateOne) SetBalanceNotifyThresholdType(v string) *UserUpdateOne {
_u.mutation.SetBalanceNotifyThresholdType(v)
return _u
}
// SetNillableBalanceNotifyThresholdType sets the "balance_notify_threshold_type" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillableBalanceNotifyThresholdType(v *string) *UserUpdateOne {
if v != nil {
_u.SetBalanceNotifyThresholdType(*v)
}
return _u
}
// SetBalanceNotifyThreshold sets the "balance_notify_threshold" field.
func (_u *UserUpdateOne) SetBalanceNotifyThreshold(v float64) *UserUpdateOne {
_u.mutation.ResetBalanceNotifyThreshold()
@@ -1559,6 +1617,27 @@ func (_u *UserUpdateOne) SetNillableBalanceNotifyExtraEmails(v *string) *UserUpd
return _u
}
// SetTotalRecharged sets the "total_recharged" field.
func (_u *UserUpdateOne) SetTotalRecharged(v float64) *UserUpdateOne {
_u.mutation.ResetTotalRecharged()
_u.mutation.SetTotalRecharged(v)
return _u
}
// SetNillableTotalRecharged sets the "total_recharged" field if the given value is not nil.
func (_u *UserUpdateOne) SetNillableTotalRecharged(v *float64) *UserUpdateOne {
if v != nil {
_u.SetTotalRecharged(*v)
}
return _u
}
// AddTotalRecharged adds value to the "total_recharged" field.
func (_u *UserUpdateOne) AddTotalRecharged(v float64) *UserUpdateOne {
_u.mutation.AddTotalRecharged(v)
return _u
}
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs.
func (_u *UserUpdateOne) AddAPIKeyIDs(ids ...int64) *UserUpdateOne {
_u.mutation.AddAPIKeyIDs(ids...)
@@ -2095,6 +2174,9 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if value, ok := _u.mutation.BalanceNotifyEnabled(); ok {
_spec.SetField(user.FieldBalanceNotifyEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.BalanceNotifyThresholdType(); ok {
_spec.SetField(user.FieldBalanceNotifyThresholdType, field.TypeString, value)
}
if value, ok := _u.mutation.BalanceNotifyThreshold(); ok {
_spec.SetField(user.FieldBalanceNotifyThreshold, field.TypeFloat64, value)
}
@@ -2107,6 +2189,12 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) {
if value, ok := _u.mutation.BalanceNotifyExtraEmails(); ok {
_spec.SetField(user.FieldBalanceNotifyExtraEmails, field.TypeString, value)
}
if value, ok := _u.mutation.TotalRecharged(); ok {
_spec.SetField(user.FieldTotalRecharged, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedTotalRecharged(); ok {
_spec.AddField(user.FieldTotalRecharged, field.TypeFloat64, value)
}
if _u.mutation.APIKeysCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.O2M,