diff --git a/app/Filament/Resources/System/MedalResource.php b/app/Filament/Resources/System/MedalResource.php index f960227a..1c500cec 100644 --- a/app/Filament/Resources/System/MedalResource.php +++ b/app/Filament/Resources/System/MedalResource.php @@ -12,6 +12,7 @@ use Filament\Resources\Table; use Filament\Tables; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Support\HtmlString; class MedalResource extends Resource { @@ -45,14 +46,30 @@ class MedalResource extends Resource ->options(Medal::listGetTypes(true)) ->inline() ->label(__('label.medal.get_type')) - ->required(), + ->required() + , Forms\Components\Toggle::make('display_on_medal_page') ->label(__('label.medal.display_on_medal_page')) - ->required(), + ->required() + , Forms\Components\TextInput::make('duration') ->integer() ->label(__('label.medal.duration')) - ->helperText(__('label.medal.duration_help')), + ->helperText(__('label.medal.duration_help')) + , + Forms\Components\TextInput::make('inventory') + ->integer() + ->label(__('medal.fields.inventory')) + ->helperText(__('medal.fields.inventory_help')) + , + Forms\Components\DateTimePicker::make('sale_begin_time') + ->label(__('medal.fields.sale_begin_time')) + ->helperText(__('medal.fields.sale_begin_time_help')) + , + Forms\Components\DateTimePicker::make('sale_end_time') + ->label(__('medal.fields.sale_end_time')) + ->helperText(__('medal.fields.sale_end_time_help')) + , Forms\Components\Textarea::make('description') ->label(__('label.description')) , @@ -65,12 +82,22 @@ class MedalResource extends Resource ->columns([ Tables\Columns\TextColumn::make('id')->sortable(), Tables\Columns\TextColumn::make('name')->label(__('label.name'))->searchable(), - Tables\Columns\ImageColumn::make('image_large')->height(120)->label(__('label.medal.image_large')), - Tables\Columns\ImageColumn::make('image_small')->height(120)->label(__('label.medal.image_small')), + Tables\Columns\ImageColumn::make('image_large')->height(60)->label(__('label.medal.image_large')), Tables\Columns\TextColumn::make('getTypeText')->label('Get type')->label(__('label.medal.get_type')), - Tables\Columns\TextColumn::make('price')->label(__('label.price')), - Tables\Columns\TextColumn::make('duration')->label(__('label.medal.duration')), Tables\Columns\IconColumn::make('display_on_medal_page')->label(__('label.medal.display_on_medal_page'))->boolean(), + Tables\Columns\TextColumn::make('sale_begin_end_time') + ->label(__('medal.fields.sale_begin_end_time')) + ->formatStateUsing(fn ($record) => new HtmlString(sprintf('%s ~
%s', $record->sale_begin_time ?? '--', $record->sale_end_time ?? '--'))) + , + Tables\Columns\TextColumn::make('price')->label(__('label.price')), + + Tables\Columns\TextColumn::make('duration')->label(__('label.medal.duration')), + + Tables\Columns\TextColumn::make('inventory') + ->label(__('medal.fields.inventory')) + ->formatStateUsing(fn ($record) => $record->inventory ?? nexus_trans('label.infinite')) + , + Tables\Columns\TextColumn::make('users_count')->label(__('medal.fields.users_count')), ]) ->defaultSort('id', 'desc') ->filters([ diff --git a/app/Filament/Resources/System/MedalResource/Pages/ListMedals.php b/app/Filament/Resources/System/MedalResource/Pages/ListMedals.php index 9dce5c8d..b8ef036a 100644 --- a/app/Filament/Resources/System/MedalResource/Pages/ListMedals.php +++ b/app/Filament/Resources/System/MedalResource/Pages/ListMedals.php @@ -4,8 +4,10 @@ namespace App\Filament\Resources\System\MedalResource\Pages; use App\Filament\PageList; use App\Filament\Resources\System\MedalResource; +use App\Models\Medal; use Filament\Pages\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Database\Eloquent\Builder; class ListMedals extends PageList { @@ -17,4 +19,9 @@ class ListMedals extends PageList Actions\CreateAction::make(), ]; } + + protected function getTableQuery(): Builder + { + return Medal::query()->withCount('users'); + } } diff --git a/app/Models/Medal.php b/app/Models/Medal.php index ffe755d6..271c8651 100644 --- a/app/Models/Medal.php +++ b/app/Models/Medal.php @@ -15,10 +15,18 @@ class Medal extends NexusModel self::GET_TYPE_GRANT => ['text' => 'Grant'], ]; - protected $fillable = ['name', 'description', 'image_large', 'image_small', 'price', 'duration', 'get_type', 'display_on_medal_page']; + protected $fillable = [ + 'name', 'description', 'image_large', 'image_small', 'price', 'duration', 'get_type', + 'display_on_medal_page', 'sale_begin_time', 'sale_end_time', 'inventory', + ]; public $timestamps = true; + protected $casts = [ + 'sale_begin_time' => 'datetime', + 'sale_end_time' => 'datetime', + ]; + public static function listGetTypes($onlyKeyValues = false): array { $results = self::$getTypeText; diff --git a/app/Repositories/BonusRepository.php b/app/Repositories/BonusRepository.php index a918093a..1945a0a0 100644 --- a/app/Repositories/BonusRepository.php +++ b/app/Repositories/BonusRepository.php @@ -61,6 +61,16 @@ class BonusRepository extends BaseRepository if ($medal->get_type != Medal::GET_TYPE_EXCHANGE) { throw new \LogicException("This medal can not be buy."); } + if ($medal->inventory !== null && $medal->inventory <= 0) { + throw new \LogicException("Inventory empty."); + } + $now = now(); + if ($medal->sale_begin_time && $medal->sale_begin_time->gt($now)) { + throw new \LogicException("Before sale begin time."); + } + if ($medal->sale_end_time && $medal->sale_end_time->lt($now)) { + throw new \LogicException("After sale end time."); + } $requireBonus = $medal->price; NexusDB::transaction(function () use ($user, $medal, $requireBonus) { $comment = nexus_trans('bonus.comment_buy_medal', [ @@ -74,6 +84,16 @@ class BonusRepository extends BaseRepository $expireAt = Carbon::now()->addDays($medal->duration)->toDateTimeString(); } $user->medals()->attach([$medal->id => ['expire_at' => $expireAt, 'status' => UserMedal::STATUS_NOT_WEARING]]); + if ($medal->inventory !== null) { + $affectedRows = NexusDB::table('medals') + ->where('id', $medal->id) + ->where('inventory', $medal->inventory) + ->decrement('inventory') + ; + if ($affectedRows != 1) { + throw new \RuntimeException("Decrement medal({$medal->id}) inventory affected rows != 1($affectedRows)"); + } + } }); diff --git a/database/migrations/2023_01_24_132053_add_sale_begin_end_time_and_inventory_to_medals_table.php b/database/migrations/2023_01_24_132053_add_sale_begin_end_time_and_inventory_to_medals_table.php new file mode 100644 index 00000000..68013061 --- /dev/null +++ b/database/migrations/2023_01_24_132053_add_sale_begin_end_time_and_inventory_to_medals_table.php @@ -0,0 +1,34 @@ +dateTime('sale_begin_time')->nullable(true); + $table->dateTime('sale_end_time')->nullable(true); + $table->integer('inventory')->nullable(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('medals', function (Blueprint $table) { + $table->dropColumn(['sale_begin_time', 'sale_end_time', 'inventory']); + }); + } +}; diff --git a/include/constants.php b/include/constants.php index 64e9d08a..c89c35dd 100644 --- a/include/constants.php +++ b/include/constants.php @@ -1,6 +1,6 @@ '.$title.''; $filterForm = <<
@@ -38,13 +40,16 @@ $table = <<ID + + TABLE; +$now = now(); $table .= ''; $userMedals = \App\Models\UserMedal::query()->where('uid', $CURUSER['id']) ->orderBy('id', 'desc') @@ -58,6 +63,12 @@ foreach ($rows as $row) { $btnText = nexus_trans('medal.buy_already'); } elseif ($row->get_type == \App\Models\Medal::GET_TYPE_GRANT) { $btnText = nexus_trans('medal.grant_only'); + } elseif ($row->sale_begin_time && $row->sale_begin_time->gt($now)) { + $btnText = nexus_trans('medal.before_sale_begin_time'); + } elseif ($row->sale_end_time && $row->sale_end_time->lt($now)) { + $btnText = nexus_trans('medal.after_sale_end_time'); + } elseif ($row->inventory !== null && $row->inventory <= 0) { + $btnText = nexus_trans('medal.inventory_empty'); } elseif ($CURUSER['seedbonus'] < $row->price) { $btnText = nexus_trans('medal.require_more_bonus'); } else { @@ -70,8 +81,8 @@ foreach ($rows as $row) { $class, $row->id, $btnText, $disabled ); $table .= sprintf( - '', - $row->id, $row->name, $row->image_large, number_format($row->price), $row->durationText, $row->description, $action + '', + $row->id, $row->name, $row->image_large, $row->sale_begin_time ?? '--', $row->sale_end_time ?? '--', number_format($row->price), $row->durationText, $row->inventory ?? nexus_trans('label.infinite'), $row->description, $action ); } $table .= '
$columnNameLabel $columnImageLargeLabel$columnSaleBeginEndTimeLabel $columnPriceLabel $columnDurationLabel$columnInventoryLabel $columnDescriptionLabel $columnActionLabel
%s%s%s%s%s%s
%s%s%s ~
%s
%s%s%s%s%s
'; diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php index 24c620c6..6ed46bc6 100644 --- a/resources/lang/en/label.php +++ b/resources/lang/en/label.php @@ -34,6 +34,7 @@ return [ 'cancel' => 'Cancel', 'reset' => 'Reset', 'anonymous' => 'Anonymous', + 'infinite' => 'Infinite', 'setting' => [ 'nav_text' => 'Setting', 'backup' => [ diff --git a/resources/lang/en/medal.php b/resources/lang/en/medal.php index c7ca8b98..ac36a9de 100644 --- a/resources/lang/en/medal.php +++ b/resources/lang/en/medal.php @@ -18,6 +18,14 @@ return [ 'image_large' => 'Image', 'price' => 'Price', 'duration' => 'Valid after buy (days)', + 'sale_begin_time' => 'Sale begin time', + 'sale_begin_time_help' => 'User can buy after this time, leave blank without restriction', + 'sale_end_time' => 'Sale end time', + 'sale_end_time_help' => 'User can buy before this time, leave blank without restriction', + 'inventory' => 'Inventory', + 'inventory_help' => 'Leave blank without restriction', + 'sale_begin_end_time' => 'Available for sale', + 'users_count' => 'Sold counts', ], 'buy_already' => 'Already buy', 'buy_btn' => 'Buy', diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php index f914db22..c024f511 100644 --- a/resources/lang/zh_CN/label.php +++ b/resources/lang/zh_CN/label.php @@ -34,6 +34,7 @@ return [ 'cancel' => '取消', 'reset' => '重置', 'anonymous' => '匿名', + 'infinite' => '无限', 'setting' => [ 'nav_text' => '设置', 'backup' => [ @@ -112,6 +113,7 @@ return [ 'duration' => '有效时长', 'duration_help' => '单位:天。如果留空,用户永久拥有', 'display_on_medal_page' => '展示在勋章页面', + ], 'user_medal' => [ 'label' => '用户勋章', diff --git a/resources/lang/zh_CN/medal.php b/resources/lang/zh_CN/medal.php index 6455bb39..ffb8a3dc 100644 --- a/resources/lang/zh_CN/medal.php +++ b/resources/lang/zh_CN/medal.php @@ -18,10 +18,21 @@ return [ 'image_large' => '图片', 'price' => '价格', 'duration' => '购买后有效期(天)', + 'sale_begin_time' => '上架开始时间', + 'sale_begin_time_help' => '此时间之后可以购买,留空不限制', + 'sale_end_time' => '上架结束时间', + 'sale_end_time_help' => '此时间之前可以购买,留空不限制', + 'inventory' => '库存', + 'inventory_help' => '留空表示无限', + 'sale_begin_end_time' => '可购买时间', + 'users_count' => '已售数量', ], 'buy_already' => '已经购买', 'buy_btn' => '购买', 'confirm_to_buy' => '确定要购买吗?', 'require_more_bonus' => '需要更多魔力值', 'grant_only' => '仅授予', + 'before_sale_begin_time' => '未到可购买时间', + 'after_sale_end_time' => '已过可购买时间', + 'inventory_empty' => '库存不足', ]; diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php index 6573498c..b48812e9 100644 --- a/resources/lang/zh_TW/label.php +++ b/resources/lang/zh_TW/label.php @@ -34,6 +34,7 @@ return [ 'cancel' => '取消', 'reset' => '重置', 'anonymous' => '匿名', + 'infinite' => '無限', 'setting' => [ 'nav_text' => '設置', 'backup' => [ diff --git a/resources/lang/zh_TW/medal.php b/resources/lang/zh_TW/medal.php index a78247d5..c5703111 100644 --- a/resources/lang/zh_TW/medal.php +++ b/resources/lang/zh_TW/medal.php @@ -18,6 +18,14 @@ return [ 'image_large' => '圖片', 'price' => '價格', 'duration' => '購買後有效期(天)', + 'sale_begin_time' => '上架開始時間', + 'sale_begin_time_help' => '此時間之後可以購買,留空不限製', + 'sale_end_time' => '上架結束時間', + 'sale_end_time_help' => '此時間之前可以購買,留空不限製', + 'inventory' => '庫存', + 'inventory_help' => '留空表示無限', + 'sale_begin_end_time' => '可購買時間', + 'users_count' => '已售數量', ], 'buy_already' => '已經購買', 'buy_btn' => '購買',