Compare commits
17 Commits
ca44e5b0db
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cf0e6f82fd | |||
| ca43502ffb | |||
| d1d42b38d1 | |||
| c2319a55cb | |||
| 12d149e5d9 | |||
| 3029f80d16 | |||
| 97fc7855ca | |||
| f8de292f12 | |||
| c57cf9bcb9 | |||
| 0607b45491 | |||
| 34ce6d810f | |||
| 8ddce5e792 | |||
| e2a6d50cb6 | |||
| 12b10db063 | |||
| e34744ad99 | |||
| 10a63b46bf | |||
| 6fff2d0285 |
@@ -2,7 +2,7 @@ APP_NAME=Hoshpoint
|
|||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
APP_URL=https://example.com
|
APP_URL=https://hoshpoint-api.treenix.ir
|
||||||
HTTP_PORT=8080
|
HTTP_PORT=8080
|
||||||
|
|
||||||
APP_LOCALE=fa
|
APP_LOCALE=fa
|
||||||
|
|||||||
68
Dockerfile
68
Dockerfile
@@ -1,57 +1,28 @@
|
|||||||
# syntax=docker/dockerfile:1.7
|
FROM php:8.4-fpm-bookworm AS app
|
||||||
|
|
||||||
ARG DOCKER_REGISTRY=docker.arvancloud.ir
|
|
||||||
ARG PHP_VERSION=8.4
|
|
||||||
|
|
||||||
FROM ${DOCKER_REGISTRY}/node:24-alpine AS assets
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN sed -i 's|https://dl-cdn.alpinelinux.org|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories
|
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
COPY resources ./resources
|
|
||||||
COPY public ./public
|
|
||||||
COPY vite.config.js ./
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
ARG DOCKER_REGISTRY
|
|
||||||
ARG PHP_VERSION
|
|
||||||
FROM ${DOCKER_REGISTRY}/php:${PHP_VERSION}-fpm-bookworm AS app
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
ENV COMPOSER_ALLOW_SUPERUSER=1
|
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||||
|
ENV COMPOSER_PROCESS_TIMEOUT=600
|
||||||
|
|
||||||
COPY docker/php/install-php-extensions /usr/local/bin/install-php-extensions
|
RUN apt-get update \
|
||||||
|
|
||||||
RUN if [ -f /etc/apt/sources.list.d/debian.sources ]; then \
|
|
||||||
sed -i \
|
|
||||||
-e 's|https\?://deb\.debian\.org/debian|http://mirror.arvancloud.ir/debian|g' \
|
|
||||||
-e 's|https\?://security\.debian\.org/debian-security|http://mirror.arvancloud.ir/debian|g' \
|
|
||||||
-e 's| bookworm-updates||g' \
|
|
||||||
/etc/apt/sources.list.d/debian.sources; \
|
|
||||||
fi \
|
|
||||||
&& apt-get -o Acquire::Check-Valid-Until=false update \
|
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
curl \
|
|
||||||
default-mysql-client \
|
default-mysql-client \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
git \
|
||||||
|
libicu-dev \
|
||||||
|
libzip-dev \
|
||||||
unzip \
|
unzip \
|
||||||
&& install-php-extensions \
|
&& docker-php-ext-install -j"$(nproc)" bcmath intl pcntl pdo_mysql zip \
|
||||||
bcmath \
|
&& apt-mark manual libzip4 libicu72 \
|
||||||
dom \
|
|
||||||
intl \
|
|
||||||
mbstring \
|
|
||||||
opcache \
|
|
||||||
pcntl \
|
|
||||||
pdo_mysql \
|
|
||||||
xml \
|
|
||||||
xmlreader \
|
|
||||||
zip \
|
|
||||||
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
|
|
||||||
&& apt-get purge -y --auto-remove \
|
&& apt-get purge -y --auto-remove \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
libicu-dev \
|
||||||
|
libzip-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY docker/php/composer.phar /usr/local/bin/composer
|
||||||
|
RUN chmod +x /usr/local/bin/composer
|
||||||
|
|
||||||
COPY docker/php/php.ini /usr/local/etc/php/conf.d/99-production.ini
|
COPY docker/php/php.ini /usr/local/etc/php/conf.d/99-production.ini
|
||||||
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/99-opcache.ini
|
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/99-opcache.ini
|
||||||
|
|
||||||
@@ -65,7 +36,6 @@ RUN composer install \
|
|||||||
&& composer check-platform-reqs --no-dev
|
&& composer check-platform-reqs --no-dev
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
COPY --from=assets /app/public/build ./public/build
|
|
||||||
|
|
||||||
RUN composer dump-autoload --no-dev --optimize --no-scripts \
|
RUN composer dump-autoload --no-dev --optimize --no-scripts \
|
||||||
&& php artisan package:discover --ansi \
|
&& php artisan package:discover --ansi \
|
||||||
@@ -90,12 +60,12 @@ EXPOSE 9000
|
|||||||
ENTRYPOINT ["docker-entrypoint"]
|
ENTRYPOINT ["docker-entrypoint"]
|
||||||
CMD ["php-fpm"]
|
CMD ["php-fpm"]
|
||||||
|
|
||||||
ARG DOCKER_REGISTRY
|
FROM nginx:1.27-alpine AS nginx
|
||||||
FROM ${DOCKER_REGISTRY}/nginx:1.27-alpine AS nginx
|
|
||||||
|
|
||||||
RUN sed -i 's|https://dl-cdn.alpinelinux.org|https://mirror.arvancloud.ir/alpine|g' /etc/apk/repositories
|
|
||||||
|
|
||||||
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
|
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||||
COPY --from=app /var/www/html/public /var/www/html/public
|
COPY --from=app /var/www/html/public /var/www/html/public
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
# npm build (run locally before docker build):
|
||||||
|
# npm ci && npm run build
|
||||||
|
|||||||
@@ -93,3 +93,4 @@ docker compose --env-file .env.production down -v
|
|||||||
```bash
|
```bash
|
||||||
docker compose --env-file .env.production restart app queue scheduler
|
docker compose --env-file .env.production restart app queue scheduler
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
17
app/Enums/WalletTransactionType.php
Normal file
17
app/Enums/WalletTransactionType.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum WalletTransactionType: string
|
||||||
|
{
|
||||||
|
case Credit = 'credit';
|
||||||
|
case Debit = 'debit';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Credit => 'واریز',
|
||||||
|
self::Debit => 'برداشت',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\WalletTransactions\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\WalletTransactions\WalletTransactionResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListWalletTransactions extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = WalletTransactionResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\WalletTransactions\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\WalletTransactions\WalletTransactionResource;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewWalletTransaction extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = WalletTransactionResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\WalletTransactions\Tables;
|
||||||
|
|
||||||
|
use App\Enums\WalletTransactionType;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class WalletTransactionsTable
|
||||||
|
{
|
||||||
|
public static function configure(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
TextColumn::make('id')
|
||||||
|
->label('#')
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('wallet.user.name')
|
||||||
|
->label('کاربر')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('wallet.user.mobile')
|
||||||
|
->label('موبایل')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(),
|
||||||
|
TextColumn::make('type')
|
||||||
|
->label('نوع')
|
||||||
|
->badge()
|
||||||
|
->formatStateUsing(fn (WalletTransactionType $state): string => $state->label())
|
||||||
|
->color(fn (WalletTransactionType $state): string => $state === WalletTransactionType::Credit ? 'success' : 'danger'),
|
||||||
|
TextColumn::make('amount')
|
||||||
|
->label('مبلغ')
|
||||||
|
->sortable()
|
||||||
|
->formatStateUsing(fn (int $state): string => number_format($state).' تومان'),
|
||||||
|
TextColumn::make('balance_after')
|
||||||
|
->label('موجودی بعد')
|
||||||
|
->formatStateUsing(fn (int $state): string => number_format($state).' تومان'),
|
||||||
|
TextColumn::make('description')
|
||||||
|
->label('توضیحات')
|
||||||
|
->limit(50)
|
||||||
|
->searchable(),
|
||||||
|
TextColumn::make('creator.name')
|
||||||
|
->label('ثبتکننده')
|
||||||
|
->placeholder('سیستم'),
|
||||||
|
TextColumn::make('created_at')
|
||||||
|
->label('تاریخ')
|
||||||
|
->dateTime()
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->defaultSort('created_at', 'desc')
|
||||||
|
->filters([
|
||||||
|
SelectFilter::make('type')
|
||||||
|
->label('نوع')
|
||||||
|
->options([
|
||||||
|
WalletTransactionType::Credit->value => WalletTransactionType::Credit->label(),
|
||||||
|
WalletTransactionType::Debit->value => WalletTransactionType::Debit->label(),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
->recordActions([
|
||||||
|
ViewAction::make(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\WalletTransactions;
|
||||||
|
|
||||||
|
use App\Filament\Resources\WalletTransactions\Pages\ListWalletTransactions;
|
||||||
|
use App\Filament\Resources\WalletTransactions\Pages\ViewWalletTransaction;
|
||||||
|
use App\Filament\Resources\WalletTransactions\Tables\WalletTransactionsTable;
|
||||||
|
use App\Models\WalletTransaction;
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
|
class WalletTransactionResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = WalletTransaction::class;
|
||||||
|
|
||||||
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedArrowsRightLeft;
|
||||||
|
|
||||||
|
protected static string|UnitEnum|null $navigationGroup = 'مالی';
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'id';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'تراکنش';
|
||||||
|
|
||||||
|
protected static ?string $pluralModelLabel = 'تراکنشها';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'تراکنشها';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 11;
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return WalletTransactionsTable::configure($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => ListWalletTransactions::route('/'),
|
||||||
|
'view' => ViewWalletTransaction::route('/{record}'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canCreate(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canEdit($record): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canDelete($record): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Filament/Resources/Wallets/Pages/EditWallet.php
Normal file
19
app/Filament/Resources/Wallets/Pages/EditWallet.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\Wallets\WalletResource;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditWallet extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = WalletResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
ViewAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Filament/Resources/Wallets/Pages/ListWallets.php
Normal file
11
app/Filament/Resources/Wallets/Pages/ListWallets.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\Wallets\WalletResource;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListWallets extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = WalletResource::class;
|
||||||
|
}
|
||||||
19
app/Filament/Resources/Wallets/Pages/ViewWallet.php
Normal file
19
app/Filament/Resources/Wallets/Pages/ViewWallet.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Resources\Wallets\WalletResource;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Resources\Pages\ViewRecord;
|
||||||
|
|
||||||
|
class ViewWallet extends ViewRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = WalletResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
EditAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets\RelationManagers;
|
||||||
|
|
||||||
|
use App\Enums\WalletTransactionType;
|
||||||
|
use Filament\Resources\RelationManagers\RelationManager;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class TransactionsRelationManager extends RelationManager
|
||||||
|
{
|
||||||
|
protected static string $relationship = 'transactions';
|
||||||
|
|
||||||
|
protected static ?string $title = 'تراکنشها';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'تراکنش';
|
||||||
|
|
||||||
|
protected static ?string $pluralModelLabel = 'تراکنشها';
|
||||||
|
|
||||||
|
public function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
TextColumn::make('type')
|
||||||
|
->label('نوع')
|
||||||
|
->badge()
|
||||||
|
->formatStateUsing(fn (WalletTransactionType $state): string => $state->label())
|
||||||
|
->color(fn (WalletTransactionType $state): string => $state === WalletTransactionType::Credit ? 'success' : 'danger'),
|
||||||
|
TextColumn::make('amount')
|
||||||
|
->label('مبلغ')
|
||||||
|
->formatStateUsing(fn (int $state): string => number_format($state).' تومان'),
|
||||||
|
TextColumn::make('balance_before')
|
||||||
|
->label('قبل')
|
||||||
|
->formatStateUsing(fn (int $state): string => number_format($state)),
|
||||||
|
TextColumn::make('balance_after')
|
||||||
|
->label('بعد')
|
||||||
|
->formatStateUsing(fn (int $state): string => number_format($state)),
|
||||||
|
TextColumn::make('description')
|
||||||
|
->label('توضیحات')
|
||||||
|
->limit(40),
|
||||||
|
TextColumn::make('creator.name')
|
||||||
|
->label('ثبتکننده')
|
||||||
|
->placeholder('سیستم'),
|
||||||
|
TextColumn::make('created_at')
|
||||||
|
->label('تاریخ')
|
||||||
|
->dateTime()
|
||||||
|
->sortable(),
|
||||||
|
])
|
||||||
|
->defaultSort('created_at', 'desc')
|
||||||
|
->filters([
|
||||||
|
SelectFilter::make('type')
|
||||||
|
->label('نوع')
|
||||||
|
->options([
|
||||||
|
WalletTransactionType::Credit->value => WalletTransactionType::Credit->label(),
|
||||||
|
WalletTransactionType::Debit->value => WalletTransactionType::Debit->label(),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
->recordActions([])
|
||||||
|
->toolbarActions([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Filament/Resources/Wallets/Schemas/WalletForm.php
Normal file
19
app/Filament/Resources/Wallets/Schemas/WalletForm.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets\Schemas;
|
||||||
|
|
||||||
|
use Filament\Forms\Components\Toggle;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
|
||||||
|
class WalletForm
|
||||||
|
{
|
||||||
|
public static function configure(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return $schema
|
||||||
|
->components([
|
||||||
|
Toggle::make('is_active')
|
||||||
|
->label('فعال')
|
||||||
|
->default(true),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
app/Filament/Resources/Wallets/Tables/WalletsTable.php
Normal file
106
app/Filament/Resources/Wallets/Tables/WalletsTable.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets\Tables;
|
||||||
|
|
||||||
|
use App\Models\Wallet;
|
||||||
|
use App\Services\WalletService;
|
||||||
|
use Filament\Actions\Action;
|
||||||
|
use Filament\Actions\EditAction;
|
||||||
|
use Filament\Actions\ViewAction;
|
||||||
|
use Filament\Forms\Components\Textarea;
|
||||||
|
use Filament\Forms\Components\TextInput;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
use Filament\Tables\Columns\IconColumn;
|
||||||
|
use Filament\Tables\Columns\TextColumn;
|
||||||
|
use Filament\Tables\Filters\TernaryFilter;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class WalletsTable
|
||||||
|
{
|
||||||
|
public static function configure(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
TextColumn::make('user.name')
|
||||||
|
->label('کاربر')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
TextColumn::make('user.email')
|
||||||
|
->label('ایمیل')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(),
|
||||||
|
TextColumn::make('user.mobile')
|
||||||
|
->label('موبایل')
|
||||||
|
->searchable()
|
||||||
|
->toggleable(),
|
||||||
|
TextColumn::make('balance')
|
||||||
|
->label('موجودی (تومان)')
|
||||||
|
->numeric()
|
||||||
|
->sortable()
|
||||||
|
->formatStateUsing(fn (int $state): string => number_format($state).' تومان'),
|
||||||
|
IconColumn::make('is_active')
|
||||||
|
->label('فعال')
|
||||||
|
->boolean(),
|
||||||
|
TextColumn::make('updated_at')
|
||||||
|
->label('آخرین بروزرسانی')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->defaultSort('balance', 'desc')
|
||||||
|
->filters([
|
||||||
|
TernaryFilter::make('is_active')
|
||||||
|
->label('وضعیت')
|
||||||
|
->placeholder('همه')
|
||||||
|
->trueLabel('فعال')
|
||||||
|
->falseLabel('غیرفعال'),
|
||||||
|
])
|
||||||
|
->recordActions([
|
||||||
|
ViewAction::make(),
|
||||||
|
EditAction::make(),
|
||||||
|
self::adjustAction('credit', 'واریز', Heroicon::OutlinedPlusCircle, 'success'),
|
||||||
|
self::adjustAction('debit', 'برداشت', Heroicon::OutlinedMinusCircle, 'danger'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function adjustAction(string $type, string $label, Heroicon $icon, string $color): Action
|
||||||
|
{
|
||||||
|
return Action::make($type)
|
||||||
|
->label($label)
|
||||||
|
->icon($icon)
|
||||||
|
->color($color)
|
||||||
|
->schema([
|
||||||
|
TextInput::make('amount')
|
||||||
|
->label('مبلغ (تومان)')
|
||||||
|
->numeric()
|
||||||
|
->required()
|
||||||
|
->minValue(1)
|
||||||
|
->integer(),
|
||||||
|
Textarea::make('description')
|
||||||
|
->label('توضیحات')
|
||||||
|
->maxLength(500)
|
||||||
|
->rows(2),
|
||||||
|
])
|
||||||
|
->action(function (Wallet $record, array $data, WalletService $walletService): void {
|
||||||
|
try {
|
||||||
|
$transaction = $type === 'credit'
|
||||||
|
? $walletService->credit($record, (int) $data['amount'], $data['description'] ?? null, auth()->user())
|
||||||
|
: $walletService->debit($record, (int) $data['amount'], $data['description'] ?? null, auth()->user());
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title($type === 'credit' ? 'واریز انجام شد' : 'برداشت انجام شد')
|
||||||
|
->body('موجودی جدید: '.number_format($transaction->balance_after).' تومان')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
} catch (RuntimeException $exception) {
|
||||||
|
Notification::make()
|
||||||
|
->title('خطا')
|
||||||
|
->body($exception->getMessage())
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
67
app/Filament/Resources/Wallets/WalletResource.php
Normal file
67
app/Filament/Resources/Wallets/WalletResource.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Resources\Wallets;
|
||||||
|
|
||||||
|
use App\Filament\Resources\Wallets\Pages\EditWallet;
|
||||||
|
use App\Filament\Resources\Wallets\Pages\ListWallets;
|
||||||
|
use App\Filament\Resources\Wallets\Pages\ViewWallet;
|
||||||
|
use App\Filament\Resources\Wallets\RelationManagers\TransactionsRelationManager;
|
||||||
|
use App\Filament\Resources\Wallets\Schemas\WalletForm;
|
||||||
|
use App\Filament\Resources\Wallets\Tables\WalletsTable;
|
||||||
|
use App\Models\Wallet;
|
||||||
|
use BackedEnum;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Schemas\Schema;
|
||||||
|
use Filament\Support\Icons\Heroicon;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
|
class WalletResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Wallet::class;
|
||||||
|
|
||||||
|
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedWallet;
|
||||||
|
|
||||||
|
protected static string|UnitEnum|null $navigationGroup = 'مالی';
|
||||||
|
|
||||||
|
protected static ?string $recordTitleAttribute = 'id';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'کیف پول';
|
||||||
|
|
||||||
|
protected static ?string $pluralModelLabel = 'کیف پولها';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'کیف پولها';
|
||||||
|
|
||||||
|
protected static ?int $navigationSort = 10;
|
||||||
|
|
||||||
|
public static function form(Schema $schema): Schema
|
||||||
|
{
|
||||||
|
return WalletForm::configure($schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return WalletsTable::configure($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TransactionsRelationManager::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => ListWallets::route('/'),
|
||||||
|
'view' => ViewWallet::route('/{record}'),
|
||||||
|
'edit' => EditWallet::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function canCreate(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ use Illuminate\Database\Eloquent\Attributes\Fillable;
|
|||||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
@@ -29,6 +31,22 @@ class User extends Authenticatable implements FilamentUser
|
|||||||
return $this->belongsToMany(Role::class);
|
return $this->belongsToMany(Role::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasOne<Wallet, $this>
|
||||||
|
*/
|
||||||
|
public function wallet(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(Wallet::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasManyThrough<WalletTransaction, Wallet, $this>
|
||||||
|
*/
|
||||||
|
public function walletTransactions(): HasManyThrough
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(WalletTransaction::class, Wallet::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function hasRole(string|array $roles): bool
|
public function hasRole(string|array $roles): bool
|
||||||
{
|
{
|
||||||
$roles = (array) $roles;
|
$roles = (array) $roles;
|
||||||
|
|||||||
39
app/Models/Wallet.php
Normal file
39
app/Models/Wallet.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
#[Fillable(['user_id', 'balance', 'is_active'])]
|
||||||
|
class Wallet extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return BelongsTo<User, $this>
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany<WalletTransaction, $this>
|
||||||
|
*/
|
||||||
|
public function transactions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(WalletTransaction::class)->latest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'balance' => 'integer',
|
||||||
|
'is_active' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
49
app/Models/WalletTransaction.php
Normal file
49
app/Models/WalletTransaction.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\WalletTransactionType;
|
||||||
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
#[Fillable([
|
||||||
|
'wallet_id',
|
||||||
|
'type',
|
||||||
|
'amount',
|
||||||
|
'balance_before',
|
||||||
|
'balance_after',
|
||||||
|
'description',
|
||||||
|
'created_by',
|
||||||
|
])]
|
||||||
|
class WalletTransaction extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return BelongsTo<Wallet, $this>
|
||||||
|
*/
|
||||||
|
public function wallet(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Wallet::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo<User, $this>
|
||||||
|
*/
|
||||||
|
public function creator(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'created_by');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => WalletTransactionType::class,
|
||||||
|
'amount' => 'integer',
|
||||||
|
'balance_before' => 'integer',
|
||||||
|
'balance_after' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Observers/UserObserver.php
Normal file
16
app/Observers/UserObserver.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\WalletService;
|
||||||
|
|
||||||
|
class UserObserver
|
||||||
|
{
|
||||||
|
public function __construct(private WalletService $walletService) {}
|
||||||
|
|
||||||
|
public function created(User $user): void
|
||||||
|
{
|
||||||
|
$this->walletService->createForUser($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/Policies/WalletPolicy.php
Normal file
29
app/Policies/WalletPolicy.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Wallet;
|
||||||
|
|
||||||
|
class WalletPolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $this->canManage($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Wallet $wallet): bool
|
||||||
|
{
|
||||||
|
return $this->canManage($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Wallet $wallet): bool
|
||||||
|
{
|
||||||
|
return $this->canManage($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canManage(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasRole('admin') || $user->hasPermission('wallets.manage');
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Policies/WalletTransactionPolicy.php
Normal file
24
app/Policies/WalletTransactionPolicy.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\WalletTransaction;
|
||||||
|
|
||||||
|
class WalletTransactionPolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $this->canManage($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, WalletTransaction $transaction): bool
|
||||||
|
{
|
||||||
|
return $this->canManage($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canManage(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasRole('admin') || $user->hasPermission('wallets.manage');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,16 @@ namespace App\Providers;
|
|||||||
use App\Models\Permission;
|
use App\Models\Permission;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\Wallet;
|
||||||
|
use App\Models\WalletTransaction;
|
||||||
|
use App\Observers\UserObserver;
|
||||||
use App\Policies\PermissionPolicy;
|
use App\Policies\PermissionPolicy;
|
||||||
use App\Policies\RolePolicy;
|
use App\Policies\RolePolicy;
|
||||||
use App\Policies\UserPolicy;
|
use App\Policies\UserPolicy;
|
||||||
|
use App\Policies\WalletPolicy;
|
||||||
|
use App\Policies\WalletTransactionPolicy;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -26,8 +32,16 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
if (app()->environment('production') || str_starts_with((string) config('app.url'), 'https://')) {
|
||||||
|
URL::forceScheme('https');
|
||||||
|
}
|
||||||
|
|
||||||
|
User::observe(UserObserver::class);
|
||||||
|
|
||||||
Gate::policy(User::class, UserPolicy::class);
|
Gate::policy(User::class, UserPolicy::class);
|
||||||
Gate::policy(Role::class, RolePolicy::class);
|
Gate::policy(Role::class, RolePolicy::class);
|
||||||
Gate::policy(Permission::class, PermissionPolicy::class);
|
Gate::policy(Permission::class, PermissionPolicy::class);
|
||||||
|
Gate::policy(Wallet::class, WalletPolicy::class);
|
||||||
|
Gate::policy(WalletTransaction::class, WalletTransactionPolicy::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
app/Services/WalletService.php
Normal file
81
app/Services/WalletService.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Enums\WalletTransactionType;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Wallet;
|
||||||
|
use App\Models\WalletTransaction;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class WalletService
|
||||||
|
{
|
||||||
|
public function createForUser(User $user): Wallet
|
||||||
|
{
|
||||||
|
return Wallet::query()->firstOrCreate(
|
||||||
|
['user_id' => $user->id],
|
||||||
|
['balance' => 0, 'is_active' => true],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function credit(
|
||||||
|
Wallet $wallet,
|
||||||
|
int $amount,
|
||||||
|
?string $description = null,
|
||||||
|
?User $performedBy = null,
|
||||||
|
): WalletTransaction {
|
||||||
|
return $this->apply($wallet, WalletTransactionType::Credit, $amount, $description, $performedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function debit(
|
||||||
|
Wallet $wallet,
|
||||||
|
int $amount,
|
||||||
|
?string $description = null,
|
||||||
|
?User $performedBy = null,
|
||||||
|
): WalletTransaction {
|
||||||
|
return $this->apply($wallet, WalletTransactionType::Debit, $amount, $description, $performedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function apply(
|
||||||
|
Wallet $wallet,
|
||||||
|
WalletTransactionType $type,
|
||||||
|
int $amount,
|
||||||
|
?string $description,
|
||||||
|
?User $performedBy,
|
||||||
|
): WalletTransaction {
|
||||||
|
if ($amount <= 0) {
|
||||||
|
throw new InvalidArgumentException('مبلغ باید بزرگتر از صفر باشد.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($wallet, $type, $amount, $description, $performedBy): WalletTransaction {
|
||||||
|
$wallet = Wallet::query()->lockForUpdate()->findOrFail($wallet->id);
|
||||||
|
|
||||||
|
if (! $wallet->is_active) {
|
||||||
|
throw new RuntimeException('کیف پول غیرفعال است.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$balanceBefore = $wallet->balance;
|
||||||
|
$balanceAfter = $type === WalletTransactionType::Credit
|
||||||
|
? $balanceBefore + $amount
|
||||||
|
: $balanceBefore - $amount;
|
||||||
|
|
||||||
|
if ($balanceAfter < 0) {
|
||||||
|
throw new RuntimeException('موجودی کیف پول کافی نیست.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$wallet->update(['balance' => $balanceAfter]);
|
||||||
|
|
||||||
|
return WalletTransaction::query()->create([
|
||||||
|
'wallet_id' => $wallet->id,
|
||||||
|
'type' => $type,
|
||||||
|
'amount' => $amount,
|
||||||
|
'balance_before' => $balanceBefore,
|
||||||
|
'balance_after' => $balanceAfter,
|
||||||
|
'description' => $description,
|
||||||
|
'created_by' => $performedBy?->id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,14 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
$middleware->trustProxies(
|
||||||
|
at: '*',
|
||||||
|
headers: Request::HEADER_X_FORWARDED_FOR
|
||||||
|
| Request::HEADER_X_FORWARDED_HOST
|
||||||
|
| Request::HEADER_X_FORWARDED_PORT
|
||||||
|
| Request::HEADER_X_FORWARDED_PROTO
|
||||||
|
| Request::HEADER_X_FORWARDED_AWS_ELB,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
$exceptions->shouldRenderJsonWhen(
|
$exceptions->shouldRenderJsonWhen(
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ return [
|
|||||||
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
|
* Edit to trust the proxy's ip address - needed for AWS Load Balancer
|
||||||
* string[]
|
* string[]
|
||||||
*/
|
*/
|
||||||
'proxy' => false,
|
'proxy' => env('L5_SWAGGER_BEHIND_PROXY', '*'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
|
* Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle.
|
||||||
@@ -315,7 +315,7 @@ return [
|
|||||||
* Constants which can be used in annotations
|
* Constants which can be used in annotations
|
||||||
*/
|
*/
|
||||||
'constants' => [
|
'constants' => [
|
||||||
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
|
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', env('APP_URL', 'http://localhost')),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('wallets', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->unique()->constrained()->cascadeOnDelete();
|
||||||
|
$table->unsignedBigInteger('balance')->default(0);
|
||||||
|
$table->boolean('is_active')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('wallets');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('wallet_transactions', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('wallet_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->string('type');
|
||||||
|
$table->unsignedBigInteger('amount');
|
||||||
|
$table->unsignedBigInteger('balance_before');
|
||||||
|
$table->unsignedBigInteger('balance_after');
|
||||||
|
$table->string('description')->nullable();
|
||||||
|
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index(['wallet_id', 'created_at']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('wallet_transactions');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ namespace Database\Seeders;
|
|||||||
use App\Models\Permission;
|
use App\Models\Permission;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\WalletService;
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
['name' => 'مدیریت کاربران', 'slug' => 'users.manage'],
|
['name' => 'مدیریت کاربران', 'slug' => 'users.manage'],
|
||||||
['name' => 'مدیریت نقشها', 'slug' => 'roles.manage'],
|
['name' => 'مدیریت نقشها', 'slug' => 'roles.manage'],
|
||||||
['name' => 'مدیریت دسترسیها', 'slug' => 'permissions.manage'],
|
['name' => 'مدیریت دسترسیها', 'slug' => 'permissions.manage'],
|
||||||
|
['name' => 'مدیریت کیف پول', 'slug' => 'wallets.manage'],
|
||||||
])->map(fn (array $permission) => Permission::query()->updateOrCreate(
|
])->map(fn (array $permission) => Permission::query()->updateOrCreate(
|
||||||
['slug' => $permission['slug']],
|
['slug' => $permission['slug']],
|
||||||
['name' => $permission['name']],
|
['name' => $permission['name']],
|
||||||
@@ -47,5 +49,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
);
|
);
|
||||||
|
|
||||||
$user->roles()->sync([$adminRole->id]);
|
$user->roles()->sync([$adminRole->id]);
|
||||||
|
|
||||||
|
User::query()->each(fn (User $user) => app(WalletService::class)->createForUser($user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
target: app
|
target: app
|
||||||
args:
|
|
||||||
DOCKER_REGISTRY: docker.arvancloud.ir
|
|
||||||
image: hoshpoint-backend-app:production
|
image: hoshpoint-backend-app:production
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
@@ -27,14 +25,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
target: nginx
|
target: nginx
|
||||||
args:
|
|
||||||
DOCKER_REGISTRY: docker.arvancloud.ir
|
|
||||||
image: hoshpoint-backend-nginx:production
|
image: hoshpoint-backend-nginx:production
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
ports:
|
ports:
|
||||||
- "${HTTP_PORT:-8080}:80"
|
- "127.0.0.1:${HTTP_PORT:-8080}:80"
|
||||||
volumes:
|
volumes:
|
||||||
- app-storage:/var/www/html/storage:ro
|
- app-storage:/var/www/html/storage:ro
|
||||||
networks:
|
networks:
|
||||||
@@ -53,8 +49,8 @@ services:
|
|||||||
DB_PORT: 3306
|
DB_PORT: 3306
|
||||||
command: php artisan queue:work database --sleep=3 --tries=3 --timeout=90
|
command: php artisan queue:work database --sleep=3 --tries=3 --timeout=90
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
- app
|
||||||
condition: service_healthy
|
- mariadb
|
||||||
volumes:
|
volumes:
|
||||||
- app-storage:/var/www/html/storage
|
- app-storage:/var/www/html/storage
|
||||||
networks:
|
networks:
|
||||||
@@ -73,8 +69,8 @@ services:
|
|||||||
DB_PORT: 3306
|
DB_PORT: 3306
|
||||||
command: sh -c "while true; do php artisan schedule:run --no-interaction; sleep 60; done"
|
command: sh -c "while true; do php artisan schedule:run --no-interaction; sleep 60; done"
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
- app
|
||||||
condition: service_healthy
|
- mariadb
|
||||||
volumes:
|
volumes:
|
||||||
- app-storage:/var/www/html/storage
|
- app-storage:/var/www/html/storage
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
map $http_x_forwarded_proto $forwarded_proto {
|
||||||
|
"" $scheme;
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $http_x_forwarded_host $forwarded_host {
|
||||||
|
"" $http_host;
|
||||||
|
default $http_x_forwarded_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $http_x_forwarded_for $forwarded_for {
|
||||||
|
"" $remote_addr;
|
||||||
|
default $http_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
@@ -39,6 +54,10 @@ server {
|
|||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
fastcgi_param DOCUMENT_ROOT $realpath_root;
|
fastcgi_param DOCUMENT_ROOT $realpath_root;
|
||||||
|
fastcgi_param HTTP_HOST $http_host;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PROTO $forwarded_proto;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_FOR $forwarded_for;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_HOST $forwarded_host;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_hide_header X-Powered-By;
|
fastcgi_hide_header X-Powered-By;
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docker/php/composer.phar
Normal file
BIN
docker/php/composer.phar
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user