| @@ -0,0 +1,18 @@ | |||
| root = true | |||
| [*] | |||
| charset = utf-8 | |||
| end_of_line = lf | |||
| indent_size = 4 | |||
| indent_style = space | |||
| insert_final_newline = true | |||
| trim_trailing_whitespace = true | |||
| [*.md] | |||
| trim_trailing_whitespace = false | |||
| [*.{yml,yaml}] | |||
| indent_size = 2 | |||
| [docker-compose.yml] | |||
| indent_size = 4 | |||
| @@ -0,0 +1,82 @@ | |||
| APP_NAME=Laravel | |||
| APP_ENV=local | |||
| APP_KEY= | |||
| APP_DEBUG=true | |||
| APP_URL=http://localhost | |||
| LOG_CHANNEL=stack | |||
| LOG_DEPRECATIONS_CHANNEL=null | |||
| LOG_LEVEL=debug | |||
| DB_CONNECTION=mysql | |||
| DB_HOST=pgsql | |||
| DB_PORT=5432 | |||
| DB_DATABASE=ht_ic_web | |||
| DB_USERNAME=sail | |||
| DB_PASSWORD=password | |||
| DB_HOST_HTPMS=pgsql | |||
| DB_PORT_HTPMS=5432 | |||
| DB_DATABASE_HTPMS=htpms | |||
| DB_USERNAME_HTPMS=hellotechno | |||
| DB_PASSWORD_HTPMS=htpass2022 | |||
| DB_HOST_HTPMS_CUSTOMER=pgsql | |||
| DB_PORT_HTPMS_CUSTOMER=5432 | |||
| DB_DATABASE_HTPMS_CUSTOMER=htpms_520 | |||
| DB_USERNAME_HTPMS_CUSTOMER=hellotechno | |||
| DB_PASSWORD_HTPMS_CUSTOMER=htpass2022 | |||
| DB_HOST_HTPMS_SYSTEM=pgsql | |||
| DB_PORT_HTPMS_SYSTEM=5432 | |||
| DB_DATABASE_HTPMS_SYSTEM=htpmssystem | |||
| DB_USERNAME_HTPMS_SYSTEM=hellotechno | |||
| DB_PASSWORD_HTPMS_SYSTEM=htpass2022 | |||
| BROADCAST_DRIVER=log | |||
| CACHE_DRIVER=file | |||
| FILESYSTEM_DISK=local | |||
| QUEUE_CONNECTION=sync | |||
| SESSION_DRIVER=file | |||
| SESSION_LIFETIME=120 | |||
| MEMCACHED_HOST=127.0.0.1 | |||
| REDIS_HOST=127.0.0.1 | |||
| REDIS_PASSWORD=null | |||
| REDIS_PORT=6379 | |||
| MAIL_MAILER=smtp | |||
| MAIL_HOST=mailpit | |||
| MAIL_PORT=1025 | |||
| MAIL_USERNAME=null | |||
| MAIL_PASSWORD=null | |||
| MAIL_ENCRYPTION=null | |||
| MAIL_FROM_ADDRESS="hello@example.com" | |||
| MAIL_FROM_NAME="${APP_NAME}" | |||
| AWS_ACCESS_KEY_ID= | |||
| AWS_SECRET_ACCESS_KEY= | |||
| AWS_DEFAULT_REGION=us-east-1 | |||
| AWS_BUCKET= | |||
| AWS_USE_PATH_STYLE_ENDPOINT=false | |||
| PUSHER_APP_ID= | |||
| PUSHER_APP_KEY= | |||
| PUSHER_APP_SECRET= | |||
| PUSHER_HOST= | |||
| PUSHER_PORT=443 | |||
| PUSHER_SCHEME=https | |||
| PUSHER_APP_CLUSTER=mt1 | |||
| VITE_APP_NAME="${APP_NAME}" | |||
| VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" | |||
| VITE_PUSHER_HOST="${PUSHER_HOST}" | |||
| VITE_PUSHER_PORT="${PUSHER_PORT}" | |||
| VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" | |||
| VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" | |||
| SIF_SERVER_IP_ADDRESS= | |||
| NOW_DATETIME=null | |||
| @@ -0,0 +1,11 @@ | |||
| * text=auto eol=lf | |||
| *.blade.php diff=html | |||
| *.css diff=css | |||
| *.html diff=html | |||
| *.md diff=markdown | |||
| *.php diff=php | |||
| /.github export-ignore | |||
| CHANGELOG.md export-ignore | |||
| .styleci.yml export-ignore | |||
| @@ -0,0 +1,22 @@ | |||
| /.phpunit.cache | |||
| /node_modules | |||
| /public/build | |||
| /public/hot | |||
| /public/storage | |||
| /storage/*.key | |||
| /vendor | |||
| .env | |||
| .env.backup | |||
| .env.production | |||
| .phpunit.result.cache | |||
| Homestead.json | |||
| Homestead.yaml | |||
| auth.json | |||
| npm-debug.log | |||
| yarn-error.log | |||
| /.fleet | |||
| /.idea | |||
| /.vscode | |||
| _ide*.php | |||
| /db_dump | |||
| sail | |||
| @@ -0,0 +1,66 @@ | |||
| <p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p> | |||
| <p align="center"> | |||
| <a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a> | |||
| <a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a> | |||
| <a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a> | |||
| <a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a> | |||
| </p> | |||
| ## About Laravel | |||
| Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: | |||
| - [Simple, fast routing engine](https://laravel.com/docs/routing). | |||
| - [Powerful dependency injection container](https://laravel.com/docs/container). | |||
| - Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. | |||
| - Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). | |||
| - Database agnostic [schema migrations](https://laravel.com/docs/migrations). | |||
| - [Robust background job processing](https://laravel.com/docs/queues). | |||
| - [Real-time event broadcasting](https://laravel.com/docs/broadcasting). | |||
| Laravel is accessible, powerful, and provides tools required for large, robust applications. | |||
| ## Learning Laravel | |||
| Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. | |||
| You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. | |||
| If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. | |||
| ## Laravel Sponsors | |||
| We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). | |||
| ### Premium Partners | |||
| - **[Vehikl](https://vehikl.com/)** | |||
| - **[Tighten Co.](https://tighten.co)** | |||
| - **[WebReinvent](https://webreinvent.com/)** | |||
| - **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** | |||
| - **[64 Robots](https://64robots.com)** | |||
| - **[Curotec](https://www.curotec.com/services/technologies/laravel/)** | |||
| - **[Cyber-Duck](https://cyber-duck.co.uk)** | |||
| - **[DevSquad](https://devsquad.com/hire-laravel-developers)** | |||
| - **[Jump24](https://jump24.co.uk)** | |||
| - **[Redberry](https://redberry.international/laravel/)** | |||
| - **[Active Logic](https://activelogic.com)** | |||
| - **[byte5](https://byte5.de)** | |||
| - **[OP.GG](https://op.gg)** | |||
| ## Contributing | |||
| Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). | |||
| ## Code of Conduct | |||
| In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). | |||
| ## Security Vulnerabilities | |||
| If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. | |||
| ## License | |||
| The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). | |||
| @@ -0,0 +1,11 @@ | |||
| <?php | |||
| namespace App\Codes; | |||
| enum EnvironmentName: string | |||
| { | |||
| case TEST = 'testing'; | |||
| case LOCAL = 'local'; | |||
| case STAGING = 'staging'; | |||
| case PRODUCTOIN = 'production'; | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| <?php | |||
| namespace App\Codes; | |||
| enum HTTPResultCode: int | |||
| { | |||
| case SECCESS = 0; | |||
| case FAILED = 1; | |||
| case UNAUTHORIZED = 2; | |||
| case EXCLUSIVE_ERROR = 3; | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Codes; | |||
| enum QueueName: string | |||
| { | |||
| case EMAIL = 'email'; | |||
| case JOB = 'job'; | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace App\Codes; | |||
| enum UserRole: string | |||
| { | |||
| case ADMIN = "admin"; | |||
| case CUSTOMER = "customer"; | |||
| case SHOP = "shop"; | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| <?php | |||
| namespace App\Console; | |||
| use Illuminate\Console\Scheduling\Schedule; | |||
| use Illuminate\Foundation\Console\Kernel as ConsoleKernel; | |||
| class Kernel extends ConsoleKernel | |||
| { | |||
| /** | |||
| * Define the application's command schedule. | |||
| */ | |||
| protected function schedule(Schedule $schedule): void | |||
| { | |||
| // $schedule->command('inspire')->hourly(); | |||
| } | |||
| /** | |||
| * Register the commands for the application. | |||
| */ | |||
| protected function commands(): void | |||
| { | |||
| $this->load(__DIR__.'/Commands'); | |||
| require base_path('routes/console.php'); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace App\Contexts\Model; | |||
| use App\Models\HtpmsCustomer\Deposit\Deposit as Model; | |||
| /** | |||
| * @extends ModelContext<Model> | |||
| */ | |||
| class Deposit extends ModelContext | |||
| { | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| <?php | |||
| namespace App\Contexts\Model; | |||
| use App\Features\InstanceAble; | |||
| use Illuminate\Database\Eloquent\Model; | |||
| use LogicException; | |||
| /** | |||
| * @template TValue of Model | |||
| */ | |||
| abstract class ModelContext | |||
| { | |||
| use InstanceAble; | |||
| /** @var ?TValue $model */ | |||
| private ?Model $model = null; | |||
| /** | |||
| * @param TValue $model | |||
| * @param boolean $override | |||
| * @return void | |||
| */ | |||
| public function set($model, bool $override = false) | |||
| { | |||
| if ($this->model !== null && $override === false) { | |||
| throw new LogicException("コンテキスト 不正オーバーライド"); | |||
| } | |||
| $this->model = $model; | |||
| } | |||
| /** | |||
| * @return TValue | |||
| */ | |||
| public function get() | |||
| { | |||
| return $this->model; | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace App\Contexts\Model; | |||
| use App\Models\HtpmsCustomer\Mst\Shop as Model; | |||
| /** | |||
| * @extends ModelContext<Model> | |||
| */ | |||
| class Shop extends ModelContext | |||
| { | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| <?php | |||
| namespace App\Events\Email; | |||
| use App\Email\BaseEmailer; | |||
| use App\Models\Email; | |||
| use App\Models\EmailAttachment; | |||
| use Illuminate\Broadcasting\InteractsWithSockets; | |||
| use Illuminate\Database\Eloquent\Collection; | |||
| use Illuminate\Foundation\Events\Dispatchable; | |||
| use Illuminate\Queue\SerializesModels; | |||
| class ConfirmEvent | |||
| { | |||
| use Dispatchable, InteractsWithSockets, SerializesModels; | |||
| public Email $email; | |||
| /** | |||
| * Create a new event instance. | |||
| * | |||
| * @return void | |||
| */ | |||
| public function __construct(Email|BaseEmailer $email, ?Collection $attachments = null) | |||
| { | |||
| if ($email instanceof Email) { | |||
| $this->email = $email; | |||
| } else { | |||
| $this->email = $email->makeModel(); | |||
| $this->email->save(); | |||
| } | |||
| if ($attachments !== null) { | |||
| foreach ($attachments as $attachment) { | |||
| if (!($attachment instanceof EmailAttachment)) continue; | |||
| $emailId = $this->email->id; | |||
| $attachment->email_id = $emailId; | |||
| $attachment->save(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| <?php | |||
| namespace App\Events\Model; | |||
| class CreatedEvent extends ModelChangeEvent | |||
| { | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| <?php | |||
| namespace App\Events\Model; | |||
| class CreatingEvent extends ModelChangeEvent | |||
| { | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| <?php | |||
| namespace App\Events\Model; | |||
| class DeletedEvent extends ModelChangeEvent | |||
| { | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| <?php | |||
| namespace App\Events\Model; | |||
| class DeletingEvent extends ModelChangeEvent | |||
| { | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| <?php | |||
| namespace App\Events\Model; | |||
| use App\Models\Feature\IModelFeature; | |||
| use Illuminate\Broadcasting\InteractsWithSockets; | |||
| use Illuminate\Broadcasting\PrivateChannel; | |||
| use Illuminate\Foundation\Events\Dispatchable; | |||
| use Illuminate\Queue\SerializesModels; | |||
| abstract class ModelChangeEvent | |||
| { | |||
| use Dispatchable, InteractsWithSockets, SerializesModels; | |||
| public IModelFeature $model; | |||
| /** | |||
| * Create a new event instance. | |||
| */ | |||
| public function __construct(IModelFeature $model) | |||
| { | |||
| $this->model = $model; | |||
| } | |||
| /** | |||
| * Get the channels the event should broadcast on. | |||
| * | |||
| * @return array<int, \Illuminate\Broadcasting\Channel> | |||
| */ | |||
| public function broadcastOn(): array | |||
| { | |||
| return [ | |||
| new PrivateChannel('channel-name'), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| <?php | |||
| namespace App\Events\Model; | |||
| class UpdatingEvent extends ModelChangeEvent | |||
| { | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Exception; | |||
| class AppCommonException extends Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Exception; | |||
| class ConfigException extends Exception | |||
| { | |||
| public function __construct(string $key, $value) | |||
| { | |||
| parent::__construct("設定エラー: key:" . $key . " value:" . $value); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Exception; | |||
| /** | |||
| * 排他エラー | |||
| */ | |||
| class ExclusiveException extends Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Exception; | |||
| class GeneralErrorMessageException extends Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; | |||
| use Throwable; | |||
| class Handler extends ExceptionHandler | |||
| { | |||
| /** | |||
| * The list of the inputs that are never flashed to the session on validation exceptions. | |||
| * | |||
| * @var array<int, string> | |||
| */ | |||
| protected $dontFlash = [ | |||
| 'current_password', | |||
| 'password', | |||
| 'password_confirmation', | |||
| ]; | |||
| /** | |||
| * Register the exception handling callbacks for the application. | |||
| */ | |||
| public function register(): void | |||
| { | |||
| $this->reportable(function (Throwable $e) { | |||
| // | |||
| }); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Exception; | |||
| /** | |||
| * スキップ | |||
| */ | |||
| class SkipException extends Exception | |||
| { | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| <?php | |||
| namespace App\Exceptions; | |||
| use Exception; | |||
| class TempFileNotExistsException extends Exception | |||
| { | |||
| private $filepath = ""; | |||
| public function setFilepath(string $filepath): static | |||
| { | |||
| $this->filepath = $filepath; | |||
| return $this; | |||
| } | |||
| public function getFilepath(): string | |||
| { | |||
| return $this->filepath; | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| <?php | |||
| namespace App\Features; | |||
| trait InstanceAble | |||
| { | |||
| /** | |||
| * @return static | |||
| */ | |||
| public static function instance() | |||
| { | |||
| return app()->make(static::class); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace App\Http\Controllers; | |||
| use Illuminate\Foundation\Auth\Access\AuthorizesRequests; | |||
| use Illuminate\Foundation\Validation\ValidatesRequests; | |||
| use Illuminate\Routing\Controller as BaseController; | |||
| class Controller extends BaseController | |||
| { | |||
| use AuthorizesRequests, ValidatesRequests; | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Server; | |||
| use App\Logics\QRService\CertificateLogic; | |||
| use App\Transmission\Layouts\IF24_01Request; | |||
| use App\Transmission\Layouts\IF24_01Response; | |||
| use Exception; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\DB; | |||
| class IF24_01Controller extends IFController | |||
| { | |||
| protected function run(Request $request): array | |||
| { | |||
| try { | |||
| DB::beginTransaction(); | |||
| $layout = IF24_01Request::create($request); | |||
| $response = $this->handleRequest($layout); | |||
| DB::commit(); | |||
| } catch (Exception $e) { | |||
| DB::rollBack(); | |||
| throw $e; | |||
| } | |||
| return $this->successResponse($request, $response); | |||
| } | |||
| private function handleRequest(IF24_01Request $request): IF24_01Response | |||
| { | |||
| $qr = CertificateLogic::getUsable( | |||
| $request->header->parkingManagementCode, | |||
| $request->publishingTerminalCode, | |||
| $request->publishingDate, | |||
| $request->publishingNo, | |||
| ); | |||
| $response = new IF24_01Response(); | |||
| $response->discountTicketCode = $qr->discount_ticket_code; | |||
| $response->shopNo = $qr->shop_no; | |||
| return $response; | |||
| } | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Server; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Logics\QRService\CertificateLogic; | |||
| use App\Logics\QRService\CreateLogic; | |||
| use App\Transmission\Layouts\Code\QRTypeCode; | |||
| use App\Transmission\Layouts\IF24_02Request; | |||
| use Exception; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\DB; | |||
| use Illuminate\Validation\ValidationException; | |||
| use Nette\NotImplementedException; | |||
| class IF24_02Controller extends IFController | |||
| { | |||
| protected function run(Request $request): array | |||
| { | |||
| try { | |||
| IF24_02Request::validateLayout($request); | |||
| } catch (ValidationException $e) { | |||
| logger($e->getMessage(), [__LINE__]); | |||
| logger($e->errors()); | |||
| logger($request->toArray()); | |||
| return $this->failResponse($request); | |||
| } | |||
| try { | |||
| DB::beginTransaction(); | |||
| $layout = IF24_02Request::create($request); | |||
| $this->handleRequest($layout); | |||
| DB::commit(); | |||
| } catch (Exception $e) { | |||
| DB::rollBack(); | |||
| throw $e; | |||
| } | |||
| return $this->successResponse($request); | |||
| } | |||
| private function handleRequest(IF24_02Request $request) | |||
| { | |||
| // 認証チェック | |||
| if ($request->qrTypeCode === QRTypeCode::方式1_認証方式) { | |||
| CertificateLogic::use( | |||
| $request->header->parkingManagementCode, | |||
| $request->publishingTerminalCode, | |||
| $request->publishingDate, | |||
| $request->publishingNo, | |||
| $request->discountTicketCode, | |||
| $request->adjustDatetime, | |||
| $request->discountAmount, | |||
| ); | |||
| } else if ($request->qrTypeCode === QRTypeCode::方式2_印字方式) { | |||
| throw new NotImplementedException("方式2未実装"); | |||
| } else if ($request->qrTypeCode === QRTypeCode::方式3_取得方式) { | |||
| CreateLogic::use( | |||
| $request->shopNo, | |||
| $request->header->parkingManagementCode, | |||
| $request->publishingDate, | |||
| $request->publishingNo, | |||
| $request->discountTicketCode, | |||
| $request->adjustDatetime, | |||
| $request->discountAmount, | |||
| ); | |||
| } else { | |||
| throw new AppCommonException("想定外のQRタイプコード"); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,161 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Server; | |||
| use App\Codes\EnvironmentName; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Http\Controllers\Controller; | |||
| use App\Models\Htpms\MstCustomer; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerConnectionSwitch; | |||
| use App\Transmission\ResultCode; | |||
| use App\Transmission\Layouts\IFCommonHeader; | |||
| use App\Transmission\Layouts\IFResponse; | |||
| use Exception; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Log; | |||
| use Illuminate\Support\Str; | |||
| use Illuminate\Validation\ValidationException; | |||
| abstract class IFController extends Controller | |||
| { | |||
| protected IFCommonHeader $header; | |||
| public function __constract() | |||
| { | |||
| $this->header = new IFCommonHeader(); | |||
| } | |||
| abstract protected function run(Request $request): array; | |||
| public function entry(Request $request) | |||
| { | |||
| try { | |||
| $this->setLogContext($request); | |||
| $this->checkHeader($request); | |||
| logger(sprintf( | |||
| "リクエスト受信:%s 顧客コード:%s 駐車場管理コード:%s IF:%s", | |||
| $request->path(), | |||
| $this->header->customerCode, | |||
| $this->header->parkingManagementCode, | |||
| $this->header->interfaceId | |||
| )); | |||
| if (!$this->checkIp($request)) { | |||
| throw new Exception("IP不正"); | |||
| } | |||
| // 顧客コードから接続先DBの切り替え | |||
| $this->switchDb(); | |||
| $result = $this->run($request); | |||
| } catch (ValidationException $e) { | |||
| logs()->error("バリデーション失敗"); | |||
| logs()->error($e->errors()); | |||
| logs()->debug("Request Contents", $request->toArray()); | |||
| logs()->debug($request->toArray()); | |||
| $result = $this->failResponseFromEmpty(); | |||
| return response()->json($result); | |||
| } catch (AppCommonException $e) { | |||
| logs()->error(sprintf( | |||
| "リクエスト失敗:%s 顧客コード:%s 駐車場管理コード:%s", | |||
| $request->path(), | |||
| $this->header->customerCode, | |||
| $this->header->parkingManagementCode, | |||
| )); | |||
| logs()->error($e->getMessage()); | |||
| $result = $this->failResponse($request); | |||
| return response()->json($result); | |||
| } catch (Exception $e) { | |||
| $contexts = [ | |||
| 'path' => $request->path(), | |||
| 'ip' => $request->ip(), | |||
| 'data' => $request->all(), | |||
| 'message' => $e->getMessage() | |||
| ]; | |||
| logs()->error("例外発生", $contexts); | |||
| logs()->error($e->getMessage()); | |||
| $result = $this->failResponseFromEmpty($request); | |||
| return response()->json($result); | |||
| } | |||
| return response()->json($result); | |||
| } | |||
| private function checkHeader(Request $request) | |||
| { | |||
| // 駐車場情報取得 | |||
| IFCommonHeader::validateLayout($request); | |||
| $this->header = IFCommonHeader::createFromRequest($request); | |||
| } | |||
| private function checkIp(Request $request) | |||
| { | |||
| // SIFのIPが一致しているか確認 | |||
| if (app()->environment([EnvironmentName::LOCAL->value, EnvironmentName::STAGING->value, EnvironmentName::TEST->value])) { | |||
| return true; | |||
| } | |||
| return in_array($request->ip(), config("transmission.sif_ip_address")); | |||
| } | |||
| protected function successResponse(Request $request, array|IFResponse $body = []): array | |||
| { | |||
| if ($body instanceof IFResponse) { | |||
| return $this->Response(ResultCode::SUCCESS, $request, $body->getBodyArray()); | |||
| } else { | |||
| return $this->Response(ResultCode::SUCCESS, $request, $body); | |||
| } | |||
| } | |||
| protected function failResponse(Request $request, array|IFResponse $body = []): array | |||
| { | |||
| if ($body instanceof IFResponse) { | |||
| return $this->Response(ResultCode::FAIL, $request, $body->toArray()); | |||
| } else { | |||
| return $this->Response(ResultCode::FAIL, $request, $body); | |||
| } | |||
| } | |||
| private function Response(ResultCode $resultCode, Request $request, array $body): array | |||
| { | |||
| $response = []; | |||
| $header = IFCommonHeader::createFromRequest($request); | |||
| $header->resultCode = $resultCode->value; | |||
| $response[IFCommonHeader::COL_NAME_HEADER] = $header->toArray(); | |||
| if (!empty($body)) { | |||
| $response[IFCommonHeader::COL_NAME_BODY] = $body; | |||
| } | |||
| return $response; | |||
| } | |||
| protected function failResponseFromEmpty(): array | |||
| { | |||
| $response = []; | |||
| $header = new IFCommonHeader(); | |||
| $header->resultCode = ResultCode::FAIL->value; | |||
| $response[IFCommonHeader::COL_NAME_HEADER] = $header->toArray(); | |||
| return $response; | |||
| } | |||
| protected function setLogContext(Request $request) | |||
| { | |||
| $context = [ | |||
| '__requestUuid__' => strval(Str::uuid()), | |||
| '__path__' => $request->path(), | |||
| '__ip__' => $request->ip(), | |||
| ]; | |||
| if (app()->environment([EnvironmentName::LOCAL->value, EnvironmentName::STAGING->value])) { | |||
| $context["__requestParam__"] = $request->all(); | |||
| } | |||
| Log::withContext($context); | |||
| } | |||
| protected function switchDb() | |||
| { | |||
| $customer = MstCustomer::whereCustomerId($this->header->customerCode)->firstOrFail(); | |||
| HtpmsCustomerConnectionSwitch::switch($customer->id); | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\WebController; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Auth; | |||
| class LoginController extends WebController | |||
| { | |||
| public function name(): string | |||
| { | |||
| return "ログイン"; | |||
| } | |||
| public function description(): string | |||
| { | |||
| return "ログインを行う"; | |||
| } | |||
| public function __construct(protected LoginParam $param) | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| protected function run(Request $request): JsonResponse | |||
| { | |||
| // 取得したユーザ情報を登録しログインを行う | |||
| $param = $this->param; | |||
| if (Auth::attempt([ | |||
| 'email' => $param->email, | |||
| 'password' => $param->password, | |||
| ])) { | |||
| return $this->successResponse(); | |||
| } else { | |||
| return $this->failedResponse(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\BaseParam; | |||
| use App\Http\Controllers\Web\Rule; | |||
| /** | |||
| * @property string $email | |||
| * @property string $password | |||
| */ | |||
| class LoginParam extends BaseParam | |||
| { | |||
| public function rules(): array | |||
| { | |||
| return [ | |||
| 'email' => $this->str([...Rule::email()]), | |||
| 'password' => $this->str(), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\WebController; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Auth; | |||
| class LogoutController extends WebController | |||
| { | |||
| public function name(): string | |||
| { | |||
| return "ログアウト"; | |||
| } | |||
| public function description(): string | |||
| { | |||
| return "ログアウトを行う"; | |||
| } | |||
| public function __construct(protected LogoutParam $param) | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| protected function run(Request $request): JsonResponse | |||
| { | |||
| Auth::logout(); | |||
| return $this->successResponse(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\NoneParams; | |||
| class LogoutParam extends NoneParams | |||
| { | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Exceptions\AppCommonException; | |||
| use Illuminate\Support\Facades\Auth; | |||
| trait Me | |||
| { | |||
| public function me(): array | |||
| { | |||
| if (!Auth::check()) { | |||
| throw new AppCommonException("Me失敗"); | |||
| } | |||
| return Auth::user()->toArray(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Http\Controllers\Web\WebController; | |||
| use App\Kintone\Models\Customer; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Auth; | |||
| class MeController extends WebController | |||
| { | |||
| use Me; | |||
| public function name(): string | |||
| { | |||
| return "ログインユーザー情報の参照"; | |||
| } | |||
| public function description(): string | |||
| { | |||
| return "ログインユーザー情報を取得する"; | |||
| } | |||
| public function __construct(protected MeParam $param) | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| protected function run(Request $request): JsonResponse | |||
| { | |||
| try { | |||
| return $this->successResponse($this->me()); | |||
| } catch (AppCommonException) { | |||
| return $this->failedResponse(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\Auth; | |||
| use App\Http\Controllers\Web\NoneParams; | |||
| class MeParam extends NoneParams | |||
| { | |||
| } | |||
| @@ -0,0 +1,247 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use App\Repositories\BaseRepository; | |||
| use App\Util\DateUtil; | |||
| use Exception; | |||
| use Illuminate\Support\Arr; | |||
| use Illuminate\Support\Carbon; | |||
| use Illuminate\Support\Str; | |||
| use Illuminate\Validation\Rules\Enum; | |||
| use ReflectionClass; | |||
| abstract class BaseParam implements IParam | |||
| { | |||
| const REQUIRED = 'required'; | |||
| const NULLABLE = 'nullable'; | |||
| const STR = 'string'; | |||
| const NUMERIC = 'numeric'; | |||
| const DATE = 'date'; | |||
| const BOOLEAN_ = 'boolean'; | |||
| const IMAGE = 'image'; | |||
| const FILE = 'file'; | |||
| const ARRAY = 'array'; | |||
| const PARAM_NAME_TIMESTAMP = 'timestamp'; | |||
| private array $param = []; | |||
| abstract public function rules(): array; | |||
| public function __set($name, $value) | |||
| { | |||
| $name = Str::snake($name); | |||
| $rule = data_get($this->rules(), $name, null); | |||
| if (!$rule) { | |||
| throw new Exception('存在しないパラメータ ' . $name); | |||
| } | |||
| $this->param[$name] = $value; | |||
| } | |||
| public function __get($name) | |||
| { | |||
| return data_get($this->param, Str::snake($name), null); | |||
| } | |||
| public function toArray(bool $toCamelCase = false): array | |||
| { | |||
| if ($toCamelCase === false) { | |||
| return $this->param; | |||
| } | |||
| $ret = []; | |||
| foreach ($this->param as $key => $val) { | |||
| $camelKey = Str::camel($key); | |||
| $ret[$camelKey] = $val; | |||
| } | |||
| return $ret; | |||
| } | |||
| public function setData(array $data) | |||
| { | |||
| $dots = Arr::dot($data); | |||
| $ruleRegExs = RuleAnalyzer::convertToRegEx($this->rules()); | |||
| // logger($ruleRegExs); | |||
| foreach ($dots as $name => $value) { | |||
| if ($value === null) { | |||
| data_set($this->param, $name, null); | |||
| continue; | |||
| } | |||
| $analyzer = new RuleAnalyzer($name, $ruleRegExs); | |||
| if ($analyzer->isMathed()) { | |||
| $content = $this->getSettableData($analyzer->getType(), $value); | |||
| data_set($this->param, $name, $content); | |||
| } | |||
| } | |||
| // logger($this->param); | |||
| } | |||
| private function getSettableData($rule, $value) | |||
| { | |||
| if (is_string($rule)) { | |||
| if ($rule === self::STR) { | |||
| return strval($value); | |||
| } | |||
| if ($rule === self::NUMERIC) { | |||
| return intval($value); | |||
| } | |||
| if ($rule === self::BOOLEAN_) { | |||
| return boolval($value); | |||
| } | |||
| if ($rule === self::DATE) { | |||
| if (is_string($value)) { | |||
| return DateUtil::parse($value); | |||
| } else { | |||
| return null; | |||
| } | |||
| } | |||
| if ($rule === self::IMAGE || $rule === self::FILE) { | |||
| return $value; | |||
| } | |||
| if ($rule === self::ARRAY) { | |||
| return $value; | |||
| } | |||
| } elseif ($rule instanceof Enum) { | |||
| // リフレクションを使ってEnumの型を取得する | |||
| $ref = new ReflectionClass((get_class($rule))); | |||
| $type = $ref->getProperty('type'); | |||
| $type->setAccessible(true); | |||
| $enum = $type->getValue($rule); | |||
| try { | |||
| return $enum::tryFrom($value); | |||
| } catch (Exception $e) { | |||
| logs()->error('Enum パース失敗', ['rule' => $rule, 'value' => $value, 'exception' => $e->getMessage()]); | |||
| throw $e; | |||
| } | |||
| } | |||
| throw new Exception(sprintf("不正な変換 ",)); | |||
| } | |||
| /** | |||
| * 排他チェック | |||
| * | |||
| * @param Carbon|null $timestamp | |||
| * @return boolean | |||
| */ | |||
| public function checkTimestamp(Carbon|null $timestamp): bool | |||
| { | |||
| if ($timestamp === null) return true; | |||
| $param = $this->__get(self::PARAM_NAME_TIMESTAMP); | |||
| if ($param === null || !$param instanceof Carbon) { | |||
| logger("無効なタイムスタンプ確認"); | |||
| logger($param); | |||
| return false; | |||
| } | |||
| return $param->eq($timestamp); | |||
| } | |||
| private function isNullable(array|bool $condition, bool $nullable): bool | |||
| { | |||
| if (is_array($condition)) { | |||
| return $nullable; | |||
| } else { | |||
| return $condition; | |||
| } | |||
| } | |||
| protected function str(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| $conditionEx = array_merge(is_array($condition) ? $condition : [], ['max:250']); | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::STR | |||
| ], $conditionEx); | |||
| } | |||
| protected function text(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::STR | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function numeric(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::NUMERIC | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function boolean(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::BOOLEAN_ | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function array(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::ARRAY | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function date(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::DATE | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function enum(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function image(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::IMAGE | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function images(string $name, $nullable = false): array | |||
| { | |||
| $need = $nullable ? self::NULLABLE : self::REQUIRED; | |||
| return [ | |||
| $name => [$need, self::ARRAY], | |||
| sprintf("%s.*", $name) => [$need, self::IMAGE] | |||
| ]; | |||
| } | |||
| protected function file(array|bool $condition = [], $nullable = false): array | |||
| { | |||
| return array_merge([ | |||
| $this->isNullable($condition, $nullable) ? self::NULLABLE : self::REQUIRED, | |||
| self::FILE | |||
| ], is_array($condition) ? $condition : []); | |||
| } | |||
| protected function sortableRules() | |||
| { | |||
| return [ | |||
| BaseRepository::CONDITION_SORT_TARGET => $this->str(true), | |||
| BaseRepository::CONDITION_SORT_ORDER => $this->str(true), | |||
| BaseRepository::CONDITION_LIMIT => $this->numeric(true), | |||
| ]; | |||
| } | |||
| protected function timestamp(bool $nullable = false) | |||
| { | |||
| return [self::PARAM_NAME_TIMESTAMP => $this->date($nullable)]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| interface IParam | |||
| { | |||
| public function setData(array $data); | |||
| public function rules(): array; | |||
| public function toArray(): array; | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use Illuminate\Routing\Controller as BaseController; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Auth; | |||
| class IndexController extends BaseController | |||
| { | |||
| public function description(): string | |||
| { | |||
| return "viewの返却"; | |||
| } | |||
| public function entry(Request $request) | |||
| { | |||
| if (!$request->expectsJson()) { | |||
| return response()->view('index')->withHeaders($this->makeHeader()); | |||
| } else { | |||
| return response()->json([], 404); | |||
| } | |||
| } | |||
| private function makeHeader(): array | |||
| { | |||
| $header = []; | |||
| $user = Auth::user(); | |||
| if ($user) { | |||
| $header["Ht-User-Auth"] = sprintf("%d,%d", $user->id, $user->role->value); | |||
| } else { | |||
| $header["Ht-User-Auth"] = 'none'; | |||
| } | |||
| return $header; | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use App\Http\Controllers\Web\BaseParam; | |||
| class NoneParams extends BaseParam | |||
| { | |||
| public function rules(): array | |||
| { | |||
| return []; | |||
| } | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\QRService; | |||
| use App\Http\Controllers\Web\WebController; | |||
| use App\Logics\QRService\CreateLogic; | |||
| use App\Logics\QRService\QRCryptoLogic; | |||
| use App\Models\HtpmsCustomer\QRService\AcquisitionTicket; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Auth; | |||
| class CreateTicketController extends WebController | |||
| { | |||
| public function name(): string | |||
| { | |||
| return "サービス券取得"; | |||
| } | |||
| public function description(): string | |||
| { | |||
| return "サービス券を取得する"; | |||
| } | |||
| public function __construct(protected CreateTicketParam $param) | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| protected function run(Request $request): JsonResponse | |||
| { | |||
| $param = $this->param; | |||
| if ($param->ticketId) { | |||
| $ticket = AcquisitionTicket::findOrFail($param->ticketId); | |||
| if (!$ticket->canUse()) { | |||
| $ticket = CreateLogic::create($param->token); | |||
| } | |||
| } else { | |||
| $ticket = CreateLogic::create($param->token); | |||
| } | |||
| $res = [ | |||
| 'ticket_id' => $ticket->id, | |||
| 'data' => $this->convertToQrStr($ticket), | |||
| ]; | |||
| return $this->successResponse($res); | |||
| } | |||
| private function convertToQrStr(AcquisitionTicket $ticket): string | |||
| { | |||
| $body = sprintf( | |||
| "%02d%s$06d%02d%02d", | |||
| "01", | |||
| $ticket->publishing_date->format('Ymd'), | |||
| $ticket->publishing_no, | |||
| $ticket->shop_no, | |||
| $ticket->discount_ticket_code, | |||
| ); | |||
| return sprintf( | |||
| "HT004%s", | |||
| QRCryptoLogic::encrypt($body) | |||
| ); | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web\QRService; | |||
| use App\Http\Controllers\Web\BaseParam; | |||
| /** | |||
| * @property string $token | |||
| * @property string|null $ticketId | |||
| */ | |||
| class CreateTicketParam extends BaseParam | |||
| { | |||
| public function rules(): array | |||
| { | |||
| return [ | |||
| 'token' => $this->str(), | |||
| 'ticket_id' => $this->str(true), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use Illuminate\Support\Carbon; | |||
| class RoleCheck | |||
| { | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| abstract class Rule | |||
| { | |||
| public static function email(): array | |||
| { | |||
| $ret = []; | |||
| $ret[] = "email:strict,filter,dns"; | |||
| $ret[] = "max:255"; | |||
| return $ret; | |||
| } | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use Illuminate\Support\Str; | |||
| class RuleAnalyzer | |||
| { | |||
| static public function convertToRegEx(array $rules) | |||
| { | |||
| $ret = []; | |||
| foreach ($rules as $name => $ruleList) { | |||
| $pattern = '/^' . Str::replace('*', '\d+', Str::replace('.', '\.', $name)) . '$/'; | |||
| $ret[$pattern] = $ruleList; | |||
| } | |||
| return $ret; | |||
| } | |||
| private string $path; | |||
| private string $pattern; | |||
| private int|null $arrayIndex = null; | |||
| private bool $mathed = false; | |||
| private array $rules; | |||
| public function __construct(string $path, array &$rules) | |||
| { | |||
| $this->path = $path; | |||
| // パターンマッチング | |||
| foreach ($rules as $pattern => $ruleList) { | |||
| if (preg_match($pattern, $path, $matcheds)) { | |||
| $this->pattern = $pattern; | |||
| $this->rules = $ruleList; | |||
| $this->mathed = true; | |||
| break; | |||
| } | |||
| } | |||
| if (!$this->mathed) { | |||
| return; | |||
| } | |||
| // 配列インデックスの取得 | |||
| $this->arrayIndex = $this->getArrayIndexFromPath($path); | |||
| } | |||
| public function isMathed() | |||
| { | |||
| return $this->mathed; | |||
| } | |||
| public function getRules() | |||
| { | |||
| return $this->rules; | |||
| } | |||
| public function getType() | |||
| { | |||
| return $this->rules[1]; | |||
| } | |||
| public function isArrayMember() | |||
| { | |||
| return $this->arrayIndex !== null; | |||
| } | |||
| public function getArrayIndex() | |||
| { | |||
| return $this->arrayIndex; | |||
| } | |||
| private function getArrayIndexFromPath(string $path) | |||
| { | |||
| preg_match('/^.+\.(\d+)\.[0-9A-Za-z_]+$/', $path, $matches); | |||
| if (count($matches) === 0) { | |||
| return null; | |||
| } | |||
| return intval($matches[1]); | |||
| } | |||
| public function getArrayName() | |||
| { | |||
| $list = explode('.*.', $this->pattern); | |||
| array_pop($list); | |||
| return implode('.*.'); | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| /** | |||
| * @property ?string $sort | |||
| * @property ?string $order | |||
| * @property ?int $limit | |||
| */ | |||
| interface SortableParam | |||
| { | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use Illuminate\Support\Carbon; | |||
| /** | |||
| * @property ?Carbon $timestamp | |||
| */ | |||
| interface TimestampParam | |||
| { | |||
| } | |||
| @@ -0,0 +1,394 @@ | |||
| <?php | |||
| namespace App\Http\Controllers\Web; | |||
| use App\Codes\EnvironmentName; | |||
| use App\Codes\HTTPResultCode as ResultCode; | |||
| use App\Codes\UserRole; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Exceptions\ExclusiveException; | |||
| use App\Exceptions\GeneralErrorMessageException; | |||
| use App\Util\DBUtil; | |||
| use Exception; | |||
| use Illuminate\Foundation\Auth\Access\AuthorizesRequests; | |||
| use Illuminate\Foundation\Bus\DispatchesJobs; | |||
| use Illuminate\Foundation\Validation\ValidatesRequests; | |||
| use Illuminate\Http\Client\Response as ClientResponse; | |||
| use Illuminate\Http\JsonResponse; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Http\Response; | |||
| use Illuminate\Routing\Controller as BaseController; | |||
| use Illuminate\Support\Arr; | |||
| use Illuminate\Support\Facades\Auth; | |||
| use Illuminate\Support\Facades\Log; | |||
| use Illuminate\Support\Facades\Validator; | |||
| use Illuminate\Support\Str; | |||
| use Illuminate\Validation\ValidationException; | |||
| use LogicException; | |||
| use Symfony\Component\HttpFoundation\BinaryFileResponse; | |||
| use Symfony\Component\HttpKernel\Exception\HttpException; | |||
| abstract class WebController extends BaseController | |||
| { | |||
| use AuthorizesRequests, DispatchesJobs, ValidatesRequests; | |||
| const COL_NAME_CREATED_AT = 'created_at'; | |||
| const COL_NAME_UPDATED_AT = 'updated_at'; | |||
| const COL_NAME_RESULT_CODE = 'result'; | |||
| const COL_NAME_DATA = 'data'; | |||
| const COL_NAME_MESSAGES = 'messages'; | |||
| const COL_NAME_GENERAL_MESSAGE = 'general'; | |||
| const COL_NAME_EMAIL_ID = 'email_id'; | |||
| const COL_NAME_ERRORS = 'errors'; | |||
| /** | |||
| * バリデートした結果を格納 | |||
| * | |||
| * @var array | |||
| */ | |||
| protected $validated = []; | |||
| /** | |||
| * 画面へ返却するメールID | |||
| * | |||
| * @var integer|null | |||
| */ | |||
| private int|null $emailId = null; | |||
| /** | |||
| * 返却するメッセージ | |||
| * | |||
| * @var array|null | |||
| */ | |||
| private array|null $messages = null; | |||
| /** | |||
| * 返却するメッセージ | |||
| * | |||
| * @var string|null | |||
| */ | |||
| private string|null $generalMessage = null; | |||
| /** | |||
| * 返却するデータ | |||
| * | |||
| * @var mixed|null | |||
| */ | |||
| private $data = null; | |||
| protected DBUtil $transaction; | |||
| /** | |||
| * 返却する結果コード | |||
| * | |||
| * @var ResultCode|null | |||
| */ | |||
| protected ResultCode|null $resultCode = ResultCode::SECCESS; | |||
| public function __construct() | |||
| { | |||
| $this->transaction = DBUtil::instance(); | |||
| } | |||
| /** | |||
| * パラメータオブジェクト | |||
| */ | |||
| protected function getParam(): IParam | |||
| { | |||
| if (!property_exists(static::class, 'param')) { | |||
| throw new LogicException("param未定義"); | |||
| } | |||
| $param = $this->param; | |||
| if (!is_subclass_of($param, IParam::class)) { | |||
| throw new LogicException("param型不正"); | |||
| } | |||
| return $this->param; | |||
| } | |||
| /** | |||
| * コントローラーの名前 | |||
| * オーバーライドされることを想定 | |||
| * 主に、Routeのドキュメント作成用 | |||
| * | |||
| * @return string | |||
| */ | |||
| public function name(): string | |||
| { | |||
| return "---未設定---"; | |||
| } | |||
| /** | |||
| * コントローラーの説明 | |||
| * オーバーライドされることを想定 | |||
| * 主に、Routeのドキュメント作成用 | |||
| * | |||
| * @return string | |||
| */ | |||
| public function description(): string | |||
| { | |||
| return "---未設定---"; | |||
| } | |||
| /** | |||
| * オーバーライド必要 | |||
| * メインロジック | |||
| * | |||
| * @param Request $request | |||
| * @return Response|JsonResponse|string | |||
| */ | |||
| protected function run(Request $request): Response|JsonResponse|BinaryFileResponse|ClientResponse |string | |||
| { | |||
| return $this->successResponse(); | |||
| } | |||
| private function getRules() | |||
| { | |||
| return $this->getParam()->rules(); | |||
| } | |||
| public function entry(Request $request) | |||
| { | |||
| $this->setLogContext($request); | |||
| try { | |||
| $validator = Validator::make($request->all(), $this->getRules()); | |||
| $validator->validate(); | |||
| } catch (ValidationException $e) { | |||
| logger("validate error", ['errors' => $e->errors(), 'request' => $request->all(), 'path' => $request->path()]); | |||
| logger($request->toArray()); | |||
| return $this->validateErrorResponse($e); | |||
| } | |||
| try { | |||
| $this->validated = $validator->validated(); | |||
| $this->getParam()->setData($this->validated); | |||
| $this->authorize(); | |||
| $this->transaction->beginTransaction(); | |||
| $ret = $this->run($request); | |||
| $this->transaction->commit(); | |||
| return $ret; | |||
| } catch (GeneralErrorMessageException $e) { | |||
| $this->transaction->rollBack(); | |||
| return $this->failedResponse([], $e->getMessage()); | |||
| } catch (AppCommonException $e) { | |||
| $this->transaction->rollBack(); | |||
| logs()->error(sprintf("Appエラー:%s File:%s Line:%d", $e->getMessage(), $e->getFile(), $e->getLine())); | |||
| return $this->failedResponse(); | |||
| } catch (ExclusiveException $e) { | |||
| $this->transaction->rollBack(); | |||
| logs()->error(sprintf("排他エラー:%s", $e->getMessage())); | |||
| return $this->exclusiveErrorResponse(); | |||
| } catch (LogicException $e) { | |||
| $this->transaction->rollBack(); | |||
| logs()->error([ | |||
| sprintf("実装エラー:%s", $e->getMessage()), | |||
| get_class($e), | |||
| $e->getFile(), | |||
| $e->getLine(), | |||
| $request->all(), | |||
| ]); | |||
| logger(array_filter($e->getTrace(), function ($val, $key) { | |||
| return $key <= 5; | |||
| }, ARRAY_FILTER_USE_BOTH)); | |||
| return $this->failedResponse(); | |||
| } catch (ValidationException $e) { | |||
| $this->transaction->rollBack(); | |||
| return $this->validateErrorResponse($e); | |||
| } catch (HttpException $e) { | |||
| $this->transaction->rollBack(); | |||
| if ($e->getStatusCode() === 401) { | |||
| return $this->unAuthorizedResponse(); | |||
| } | |||
| throw $e; | |||
| } catch (Exception $e) { | |||
| $this->transaction->rollBack(); | |||
| logs()->error([ | |||
| sprintf("例外エラー:%s", $e->getMessage()), | |||
| get_class($e), | |||
| $e->getFile(), | |||
| $e->getLine(), | |||
| $request->all(), | |||
| ]); | |||
| logger(array_filter($e->getTrace(), function ($val, $key) { | |||
| return $key <= 5; | |||
| }, ARRAY_FILTER_USE_BOTH)); | |||
| return $this->failedResponse(); | |||
| } | |||
| } | |||
| protected function successResponse(array|object $data = [], array|string $messages = []) | |||
| { | |||
| return $this->setData($data) | |||
| ->setMessages($messages) | |||
| ->setResultCode(ResultCode::SECCESS) | |||
| ->makeResponse(); | |||
| } | |||
| protected function failedResponse(array|object $data = [], array|string $messages = []) | |||
| { | |||
| return $this->setData($data) | |||
| ->setMessages($messages) | |||
| ->setResultCode(ResultCode::FAILED) | |||
| ->makeResponse(); | |||
| } | |||
| protected function unAuthorizedResponse(array|object $data = [], array|string $messages = []) | |||
| { | |||
| return $this->setData($data) | |||
| ->setMessages($messages) | |||
| ->setResultCode(ResultCode::UNAUTHORIZED) | |||
| ->makeResponse(); | |||
| } | |||
| protected function exclusiveErrorResponse(array|object $data = [], array|string $messages = []) | |||
| { | |||
| return $this->setData($data) | |||
| ->setMessages($messages) | |||
| ->setResultCode(ResultCode::EXCLUSIVE_ERROR) | |||
| ->makeResponse(); | |||
| } | |||
| protected function validateErrorResponse(ValidationException|array $exception, string|null $generalMessage = null) | |||
| { | |||
| $errorMessages = []; | |||
| $general = null; | |||
| if ($exception instanceof ValidationException) { | |||
| foreach ($exception->errors() as $key => $m) { | |||
| $errorMessages[$key] = $m[0]; | |||
| } | |||
| } | |||
| if (is_array($exception)) { | |||
| $errorMessages = $exception; | |||
| } | |||
| $general = $generalMessage ?? data_get($errorMessages, self::COL_NAME_GENERAL_MESSAGE); | |||
| return $this->setData([]) | |||
| ->setMessages($errorMessages) | |||
| ->setGeneralMessage($general) | |||
| ->setResultCode(ResultCode::FAILED) | |||
| ->makeResponse(); | |||
| } | |||
| protected function makeResponse() | |||
| { | |||
| if ($this->resultCode === null) { | |||
| abort(403); | |||
| } | |||
| $ret = []; | |||
| Arr::set($ret, self::COL_NAME_RESULT_CODE, $this->resultCode->value); | |||
| if ($this->data !== null) { | |||
| Arr::set($ret, self::COL_NAME_DATA, $this->data); | |||
| } | |||
| if ($this->messages !== null) { | |||
| Arr::set($ret, self::COL_NAME_MESSAGES . "." . self::COL_NAME_ERRORS, $this->messages); | |||
| } | |||
| if ($this->generalMessage !== null) { | |||
| Arr::set($ret, self::COL_NAME_MESSAGES . "." . self::COL_NAME_GENERAL_MESSAGE, $this->generalMessage); | |||
| } | |||
| if ($this->emailId !== null) { | |||
| Arr::set($ret, self::COL_NAME_MESSAGES . "." . self::COL_NAME_EMAIL_ID, $this->emailId); | |||
| } | |||
| if (request()->wantsJson()) { | |||
| return response() | |||
| ->json($ret) | |||
| ->withHeaders($this->makeHeader()); | |||
| } else { | |||
| if (app()->environment([EnvironmentName::PRODUCTOIN->value])) { | |||
| abort(500); | |||
| } | |||
| return response() | |||
| ->json($ret) | |||
| ->withHeaders($this->makeHeader()); | |||
| } | |||
| } | |||
| private function makeHeader(): array | |||
| { | |||
| $header = []; | |||
| $user = Auth::user(); | |||
| if ($user) { | |||
| $header["App-User-Auth"] = sprintf("%s", $user->id); | |||
| } else { | |||
| $header["App-User-Auth"] = 'none'; | |||
| } | |||
| return $header; | |||
| } | |||
| // 以下 認可関係 | |||
| protected array|null $roleAllow = null; | |||
| protected array|null $roleDisallow = null; | |||
| protected array|null $customAllow = null; | |||
| protected function roleAllow(UserRole $role) | |||
| { | |||
| $this->roleAllow = []; | |||
| foreach (UserRole::cases() as $ele) { | |||
| if ($role->value <= $ele->value) { | |||
| $this->roleAllow[] = $ele; | |||
| } | |||
| } | |||
| } | |||
| private function authorize() | |||
| { | |||
| if (!Auth::check()) { | |||
| return; | |||
| } | |||
| } | |||
| // 返却用データの登録 | |||
| protected function setEmailId(int $emailId) | |||
| { | |||
| $this->emailId = $emailId; | |||
| return $this; | |||
| } | |||
| protected function setMessages(array|string $messages) | |||
| { | |||
| if (is_array($messages)) { | |||
| $this->messages = $messages; | |||
| } else { | |||
| $this->setGeneralMessage($messages); | |||
| } | |||
| return $this; | |||
| } | |||
| protected function setGeneralMessage(string|null $generalMessage) | |||
| { | |||
| $this->generalMessage = $generalMessage; | |||
| return $this; | |||
| } | |||
| protected function setData($data) | |||
| { | |||
| $this->data = $data; | |||
| return $this; | |||
| } | |||
| protected function setResultCode(ResultCode $resultCode) | |||
| { | |||
| $this->resultCode = $resultCode; | |||
| return $this; | |||
| } | |||
| protected function setLogContext(Request $request) | |||
| { | |||
| Log::withContext([ | |||
| '__requestUuid__' => strval(Str::uuid()), | |||
| '__userId__' => Auth::id(), | |||
| '__path__' => $request->path(), | |||
| ]); | |||
| } | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| <?php | |||
| namespace App\Http; | |||
| use Illuminate\Foundation\Http\Kernel as HttpKernel; | |||
| class Kernel extends HttpKernel | |||
| { | |||
| /** | |||
| * The application's global HTTP middleware stack. | |||
| * | |||
| * These middleware are run during every request to your application. | |||
| * | |||
| * @var array<int, class-string|string> | |||
| */ | |||
| protected $middleware = [ | |||
| // \App\Http\Middleware\TrustHosts::class, | |||
| \App\Http\Middleware\TrustProxies::class, | |||
| \Illuminate\Http\Middleware\HandleCors::class, | |||
| \App\Http\Middleware\PreventRequestsDuringMaintenance::class, | |||
| \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, | |||
| \App\Http\Middleware\TrimStrings::class, | |||
| \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, | |||
| ]; | |||
| /** | |||
| * The application's route middleware groups. | |||
| * | |||
| * @var array<string, array<int, class-string|string>> | |||
| */ | |||
| protected $middlewareGroups = [ | |||
| 'web' => [ | |||
| \App\Http\Middleware\EncryptCookies::class, | |||
| \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, | |||
| \Illuminate\Session\Middleware\StartSession::class, | |||
| \Illuminate\View\Middleware\ShareErrorsFromSession::class, | |||
| \App\Http\Middleware\VerifyCsrfToken::class, | |||
| \Illuminate\Routing\Middleware\SubstituteBindings::class, | |||
| ], | |||
| 'api' => [ | |||
| // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, | |||
| \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', | |||
| \Illuminate\Routing\Middleware\SubstituteBindings::class, | |||
| ], | |||
| ]; | |||
| /** | |||
| * The application's middleware aliases. | |||
| * | |||
| * Aliases may be used instead of class names to conveniently assign middleware to routes and groups. | |||
| * | |||
| * @var array<string, class-string|string> | |||
| */ | |||
| protected $middlewareAliases = [ | |||
| 'auth' => \App\Http\Middleware\Authenticate::class, | |||
| 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, | |||
| 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, | |||
| 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, | |||
| 'can' => \Illuminate\Auth\Middleware\Authorize::class, | |||
| 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, | |||
| 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, | |||
| 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, | |||
| 'signed' => \App\Http\Middleware\ValidateSignature::class, | |||
| 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, | |||
| 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Auth\Middleware\Authenticate as Middleware; | |||
| use Illuminate\Http\Request; | |||
| class Authenticate extends Middleware | |||
| { | |||
| /** | |||
| * Get the path the user should be redirected to when they are not authenticated. | |||
| */ | |||
| protected function redirectTo(Request $request): ?string | |||
| { | |||
| return $request->expectsJson() ? null : route('login'); | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Cookie\Middleware\EncryptCookies as Middleware; | |||
| class EncryptCookies extends Middleware | |||
| { | |||
| /** | |||
| * The names of the cookies that should not be encrypted. | |||
| * | |||
| * @var array<int, string> | |||
| */ | |||
| protected $except = [ | |||
| // | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware; | |||
| class PreventRequestsDuringMaintenance extends Middleware | |||
| { | |||
| /** | |||
| * The URIs that should be reachable while maintenance mode is enabled. | |||
| * | |||
| * @var array<int, string> | |||
| */ | |||
| protected $except = [ | |||
| // | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use App\Providers\RouteServiceProvider; | |||
| use Closure; | |||
| use Illuminate\Http\Request; | |||
| use Illuminate\Support\Facades\Auth; | |||
| use Symfony\Component\HttpFoundation\Response; | |||
| class RedirectIfAuthenticated | |||
| { | |||
| /** | |||
| * Handle an incoming request. | |||
| * | |||
| * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next | |||
| */ | |||
| public function handle(Request $request, Closure $next, string ...$guards): Response | |||
| { | |||
| $guards = empty($guards) ? [null] : $guards; | |||
| foreach ($guards as $guard) { | |||
| if (Auth::guard($guard)->check()) { | |||
| return redirect(RouteServiceProvider::HOME); | |||
| } | |||
| } | |||
| return $next($request); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware; | |||
| class TrimStrings extends Middleware | |||
| { | |||
| /** | |||
| * The names of the attributes that should not be trimmed. | |||
| * | |||
| * @var array<int, string> | |||
| */ | |||
| protected $except = [ | |||
| 'current_password', | |||
| 'password', | |||
| 'password_confirmation', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Http\Middleware\TrustHosts as Middleware; | |||
| class TrustHosts extends Middleware | |||
| { | |||
| /** | |||
| * Get the host patterns that should be trusted. | |||
| * | |||
| * @return array<int, string|null> | |||
| */ | |||
| public function hosts(): array | |||
| { | |||
| return [ | |||
| $this->allSubdomainsOfApplicationUrl(), | |||
| ]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Http\Middleware\TrustProxies as Middleware; | |||
| use Illuminate\Http\Request; | |||
| class TrustProxies extends Middleware | |||
| { | |||
| /** | |||
| * The trusted proxies for this application. | |||
| * | |||
| * @var array<int, string>|string|null | |||
| */ | |||
| protected $proxies; | |||
| /** | |||
| * The headers that should be used to detect proxies. | |||
| * | |||
| * @var int | |||
| */ | |||
| protected $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; | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Routing\Middleware\ValidateSignature as Middleware; | |||
| class ValidateSignature extends Middleware | |||
| { | |||
| /** | |||
| * The names of the query string parameters that should be ignored. | |||
| * | |||
| * @var array<int, string> | |||
| */ | |||
| protected $except = [ | |||
| // 'fbclid', | |||
| // 'utm_campaign', | |||
| // 'utm_content', | |||
| // 'utm_medium', | |||
| // 'utm_source', | |||
| // 'utm_term', | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| <?php | |||
| namespace App\Http\Middleware; | |||
| use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; | |||
| class VerifyCsrfToken extends Middleware | |||
| { | |||
| /** | |||
| * The URIs that should be excluded from CSRF verification. | |||
| * | |||
| * @var array<int, string> | |||
| */ | |||
| protected $except = []; | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| <?php | |||
| namespace App\Listeners\Email; | |||
| use App\Events\Email\ConfirmEvent; | |||
| use App\Jobs\Email\SimpleEmail; | |||
| use App\Util\DateUtil; | |||
| class EmailSendJobRegister | |||
| { | |||
| /** | |||
| * Create the event listener. | |||
| * | |||
| * @return void | |||
| */ | |||
| public function __construct() | |||
| { | |||
| } | |||
| /** | |||
| * Handle the event. | |||
| * | |||
| * @param ConfirmEvent $event | |||
| * @return void | |||
| */ | |||
| public function handle(ConfirmEvent $event) | |||
| { | |||
| $email = $event->email; | |||
| if ($email->confirm_datetime === null && $email->send_datetime === null) { | |||
| $email->confirm_datetime = DateUtil::now(); | |||
| $email->save(); | |||
| SimpleEmail::dispatch($event->email); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| <?php | |||
| namespace App\Listeners\Model; | |||
| use App\Events\Model\CreatedEvent; | |||
| class CreatedListener extends ModelListener | |||
| { | |||
| protected const ACTION = '作成'; | |||
| public function handle(CreatedEvent $event): void | |||
| { | |||
| $this->handleEvent($event->model); | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| <?php | |||
| namespace App\Listeners\Model; | |||
| use App\Events\Model\CreatingEvent; | |||
| use App\Models\User; | |||
| use Illuminate\Support\Facades\Hash; | |||
| class CreatingListener extends ModelListener | |||
| { | |||
| protected const ACTION = '作成中'; | |||
| public function handle(CreatingEvent $event): void | |||
| { | |||
| // ログインパスワードのハッシュ化 | |||
| if ($event->model instanceof User) { | |||
| $event->model->password = Hash::make($event->model->password); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| <?php | |||
| namespace App\Listeners\Model; | |||
| use App\Events\Model\DeletedEvent; | |||
| class DeletedListener extends ModelListener | |||
| { | |||
| protected const ACTION = '削除'; | |||
| public function handle(DeletedEvent $event): void | |||
| { | |||
| $this->handleEvent($event->model); | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| <?php | |||
| namespace App\Listeners\Model; | |||
| use App\Events\Model\DeletingEvent; | |||
| class DeletingListener extends ModelListener | |||
| { | |||
| protected const ACTION = '削除'; | |||
| public function handle(DeletingEvent $event): void | |||
| { | |||
| $this->handleEvent($event->model); | |||
| } | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| <?php | |||
| namespace App\Listeners\Model; | |||
| use App\Models\ColumnName; | |||
| use App\Models\Feature\IModelFeature; | |||
| use Illuminate\Database\Eloquent\Model; | |||
| use Illuminate\Support\Facades\Auth; | |||
| use Illuminate\Support\Facades\Log; | |||
| abstract class ModelListener | |||
| { | |||
| protected const ACTION = '-'; | |||
| protected function handleEvent(IModelFeature $model): void | |||
| { | |||
| // 履歴作成処理 | |||
| $this->createHistory($model); | |||
| } | |||
| protected function createHistory(IModelFeature $model) | |||
| { | |||
| $history = $model->getHistory(); | |||
| $changeMessage = ""; | |||
| if ($history !== null) { | |||
| $history->fillFromOrigin($model); | |||
| $history->save(); | |||
| if ($model instanceof Model) { | |||
| $before = $model->getOriginal(); | |||
| $after = $model; | |||
| $message = $model->getChangeLogMessage($before, $after); | |||
| if ($message !== null) { | |||
| $changeMessage = sprintf("[%s]", $message); | |||
| } | |||
| } | |||
| } | |||
| Log::debug(sprintf( | |||
| "モデル変更検知[%s][%s][ID:%s]%s", | |||
| $model->getModelName(), | |||
| static::ACTION, | |||
| data_get($model, ColumnName::ID), | |||
| $changeMessage | |||
| )); | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace App\Listeners\Model; | |||
| use App\Events\Model\UpdatingEvent; | |||
| use App\Models\User; | |||
| use Illuminate\Support\Facades\Auth; | |||
| use Illuminate\Support\Facades\Hash; | |||
| class UpdatingListener extends ModelListener | |||
| { | |||
| protected const ACTION = '更新'; | |||
| public function handle(UpdatingEvent $event): void | |||
| { | |||
| // 更新者作成者の設定 | |||
| if (Auth::check()) { | |||
| $id = Auth::id(); | |||
| $event->model->updated_by = $id; | |||
| if ($event->model->created_by === null) { | |||
| $event->model->created_by = $id; | |||
| } | |||
| } | |||
| $this->handleEvent($event->model); | |||
| } | |||
| } | |||
| @@ -0,0 +1,115 @@ | |||
| <?php | |||
| namespace App\Logics\QRService; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Models\HtpmsCustomer\Mst\ShopNoRelation; | |||
| use App\Models\HtpmsCustomer\QRService\CertificationAvailableSetting; | |||
| use App\Models\HtpmsCustomer\QRService\CertificationTicket; | |||
| use App\Util\DateUtil; | |||
| use Illuminate\Support\Carbon; | |||
| class CertificateLogic | |||
| { | |||
| use DepositCheck; | |||
| public static function certificate( | |||
| string $parkingManagementCode, | |||
| string $adjusterTerminalCode, | |||
| Carbon $publishingDate, | |||
| int $seqNo, | |||
| string $shopId, | |||
| int $discountTicketCode | |||
| ) { | |||
| // 割引設定有無の確認 | |||
| $check = CertificationAvailableSetting::whereParkingManagementCode($parkingManagementCode) | |||
| ->whereShopId($shopId) | |||
| ->whereDiscountTicketCode($discountTicketCode) | |||
| ->exists(); | |||
| if (!$check) { | |||
| throw new AppCommonException("認証できない割引"); | |||
| } | |||
| // デポジットチェック | |||
| if (!self::canCertificate($shopId)) { | |||
| throw new AppCommonException("認証不可 デポジット"); | |||
| } | |||
| [$shop] = self::getData($shopId); | |||
| $qr = new CertificationTicket(); | |||
| $qr->parking_management_code = $parkingManagementCode; | |||
| $qr->publishing_terminal_code = $adjusterTerminalCode; | |||
| $qr->publishing_date = $publishingDate; | |||
| $qr->publishing_no = $seqNo; | |||
| $qr->shop_id = $shopId; | |||
| $qr->discount_ticket_code = $discountTicketCode; | |||
| // 店舗番号の解決 | |||
| $relation = ShopNoRelation::whereShopId($shopId) | |||
| ->whereParkingManagementCode($parkingManagementCode) | |||
| ->first(); | |||
| if ($relation instanceof ShopNoRelation === false) { | |||
| throw new AppCommonException("店舗番号紐づけ未設定"); | |||
| } | |||
| $qr->shop_no = $relation->shop_no; | |||
| // 有効期限設定 | |||
| $qr->expires_at = DateUtil::now()->addMinutes($shop->qr_service_expire_min); | |||
| $qr->save(); | |||
| return $qr; | |||
| } | |||
| public static function getUsable( | |||
| string $parkingManagementCode, | |||
| string $adjusterTerminalCode, | |||
| Carbon $publishingDate, | |||
| int $seqNo | |||
| ): CertificationTicket { | |||
| $qr = CertificationTicket::whereParkingManagementCode($parkingManagementCode) | |||
| ->wherePublishingTerminalCode($adjusterTerminalCode) | |||
| ->wherePublishingDate($publishingDate) | |||
| ->wherePublishingNo($seqNo) | |||
| ->first(); | |||
| if ($qr instanceof CertificationTicket === false) { | |||
| throw new AppCommonException("QR 認証なし"); | |||
| } | |||
| if ($qr->iseExpired()) { | |||
| throw new AppCommonException("QR 期限切れ"); | |||
| } | |||
| if ($qr->isUsed()) { | |||
| throw new AppCommonException("QR 使用済み"); | |||
| } | |||
| return $qr; | |||
| } | |||
| public static function use( | |||
| string $parkingManagementCode, | |||
| string $adjusterTerminalCode, | |||
| Carbon $publishingDate, | |||
| int $seqNo, | |||
| int $discountTicketCode, | |||
| Carbon $adjustDatetime, | |||
| int $discountAmount, | |||
| ) { | |||
| // QRコード情報の取得 | |||
| $qr = self::getUsable($parkingManagementCode, $adjusterTerminalCode, $publishingDate, $seqNo); | |||
| // デポジット処理 | |||
| self::useDeposit($qr->shop_id, $discountAmount); | |||
| $qr->used_at = $adjustDatetime; | |||
| $qr->discount_amount = $discountAmount; | |||
| $qr->discount_ticket_code = $discountTicketCode; | |||
| $qr->save(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,179 @@ | |||
| <?php | |||
| namespace App\Logics\QRService; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HtpmsCustomer\QRService\AcquisitionAvailableSetting; | |||
| use App\Models\HtpmsCustomer\QRService\AcquisitionTicket; | |||
| use App\Models\HtpmsCustomer\QRService\AcquisitionTicketToken; | |||
| use App\Models\HtpmsCustomer\QRService\ServiceParkingGroupRelation; | |||
| use App\Util\DateUtil; | |||
| use Illuminate\Support\Carbon; | |||
| use Str; | |||
| class CreateLogic | |||
| { | |||
| use DepositCheck; | |||
| /** | |||
| * サービス券を作成する | |||
| */ | |||
| public static function create( | |||
| string $token, | |||
| ) { | |||
| // トークンから店舗ID検索 | |||
| $t = AcquisitionTicketToken::whereToken($token)->first(); | |||
| if ($t instanceof AcquisitionTicketToken === false) { | |||
| throw new AppCommonException(sprintf("トークン不正 %s", $token)); | |||
| } | |||
| $shopId = $t->shop_id; | |||
| // 割引設定有無の確認 | |||
| $check = AcquisitionAvailableSetting::whereShopId($shopId) | |||
| ->first(); | |||
| if ($check instanceof AcquisitionAvailableSetting === false) { | |||
| throw new AppCommonException("取得できない割引"); | |||
| } | |||
| // デポジットチェック | |||
| if (!self::canCreate($shopId)) { | |||
| throw new AppCommonException("認証不可 デポジット"); | |||
| } | |||
| [$shop] = self::getData($shopId); | |||
| // 設定値の検索 | |||
| $setting = AcquisitionAvailableSetting::whereShopId($shopId) | |||
| ->first(); | |||
| if ($setting instanceof AcquisitionAvailableSetting === false) { | |||
| throw new AppCommonException("QRサービス券取得可能設定 不正"); | |||
| } | |||
| $qr = new AcquisitionTicket(); | |||
| $qr->qr_service_parking_group_id = $check->qr_service_parking_group_id; | |||
| $qr->publishing_no = self::getNextSeqNo($setting->shop_no); | |||
| $qr->shop_id = $shopId; | |||
| $qr->shop_no = $setting->shop_no; | |||
| $qr->discount_ticket_code = $setting->discount_ticket_code; | |||
| $qr->qr_service_parking_group_id = $setting->qr_service_parking_group_id; | |||
| // 有効期限設定 | |||
| $qr->expires_at = DateUtil::now()->addMinutes($shop->qr_service_expire_min); | |||
| $qr->publishing_date = DateUtil::now(); | |||
| $qr->save(); | |||
| return $qr; | |||
| } | |||
| public static function getUsable(int $shopNo, Carbon $publishingDate, int $seqNo): AcquisitionTicket | |||
| { | |||
| $qr = AcquisitionTicket::query() | |||
| ->whereShopNo($shopNo) | |||
| ->wherePublishingDate($publishingDate) | |||
| ->wherePublishingNo($seqNo) | |||
| ->lockForUpdate() | |||
| ->first(); | |||
| if ($qr instanceof AcquisitionTicket === false) { | |||
| throw new AppCommonException("QR 認証なし"); | |||
| } | |||
| if ($qr->iseExpired()) { | |||
| throw new AppCommonException("QR 期限切れ"); | |||
| } | |||
| if ($qr->isUsed()) { | |||
| throw new AppCommonException("QR 使用済み"); | |||
| } | |||
| return $qr; | |||
| } | |||
| /** | |||
| * サービス券を使用する | |||
| * | |||
| * @param integer $shopNo | |||
| * @param string $parkingManagementCode | |||
| * @param Carbon $publishingDate | |||
| * @param integer $seqNo | |||
| * @param integer $discountTicketCode | |||
| * @param Carbon $adjustDatetime | |||
| * @param integer $discountAmount | |||
| * @return void | |||
| */ | |||
| public static function use( | |||
| int $shopNo, | |||
| string $parkingManagementCode, | |||
| Carbon $publishingDate, | |||
| int $seqNo, | |||
| int $discountTicketCode, | |||
| Carbon $adjustDatetime, | |||
| int $discountAmount, | |||
| ) { | |||
| // QRコード情報の取得 | |||
| $qr = self::getUsable($shopNo, $publishingDate, $seqNo); | |||
| // QRコードサービス券駐車場グループに含まれているかチェック | |||
| self::checkQrParkingGroup($qr->qr_service_parking_group_id, $parkingManagementCode); | |||
| // デポジット処理 | |||
| self::useDeposit($qr->shop_id, $discountAmount); | |||
| $qr->discount_amount = $discountAmount; | |||
| // 利用情報 | |||
| $qr->used_at = $adjustDatetime; | |||
| $qr->discount_ticket_code = $discountTicketCode; | |||
| $qr->parking_management_code = $parkingManagementCode; | |||
| $qr->save(); | |||
| } | |||
| public static function getToken(string $shopId, bool $refresh = false): AcquisitionTicketToken | |||
| { | |||
| $token = AcquisitionTicketToken::whereShopId($shopId) | |||
| ->first(); | |||
| if ($refresh) { | |||
| if ($token instanceof AcquisitionTicketToken === true) { | |||
| $token->delete(); | |||
| $token = null; | |||
| } | |||
| } | |||
| if ($token instanceof AcquisitionTicketToken) { | |||
| return $token; | |||
| } | |||
| // トークン新規作成 | |||
| $token = new AcquisitionTicketToken(); | |||
| $token->token = Str::uuid()->toString(); | |||
| $token->shop_id = $shopId; | |||
| $token->save(); | |||
| return $token; | |||
| } | |||
| private static function getNextSeqNo(int $shopNo) | |||
| { | |||
| $max = AcquisitionTicket::query() | |||
| ->whereShopNo($shopNo) | |||
| ->wherePublishingDate(DateUtil::now()) | |||
| ->max(ColumnName::PUBLISHING_NO); | |||
| if ($max === null) return 1; | |||
| return $max + 1; | |||
| } | |||
| private static function checkQrParkingGroup(string $groupId, string $parkingManagementCode) | |||
| { | |||
| $query = ServiceParkingGroupRelation::query() | |||
| ->whereQrServiceParkingGroupId($groupId) | |||
| ->whereParkingManagementCode($parkingManagementCode); | |||
| if (!$query->exists()) { | |||
| throw new AppCommonException("利用できない駐車場"); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| <?php | |||
| namespace App\Logics\QRService; | |||
| use App\Contexts\Model\Deposit as ContextDeposit; | |||
| use App\Contexts\Model\Shop as ContextShop; | |||
| use App\Exceptions\AppCommonException; | |||
| use App\Models\HtpmsCustomer\Deposit\Deposit; | |||
| use App\Models\HtpmsCustomer\Deposit\DepositTransfer; | |||
| use App\Models\HtpmsCustomer\Mst\Shop; | |||
| use App\Util\DateUtil; | |||
| use LogicException; | |||
| trait DepositCheck | |||
| { | |||
| protected static function canCreate(string $shopId): bool | |||
| { | |||
| [$shop, $deposit] = self::getData($shopId); | |||
| return self::checkDepositWhenCertificate($shop, $deposit); | |||
| } | |||
| protected static function canCertificate(string $shopId): bool | |||
| { | |||
| [$shop, $deposit] = self::getData($shopId); | |||
| return self::checkDepositWhenCertificate($shop, $deposit); | |||
| } | |||
| protected static function canUseDeposit(string $shopId, $amount): bool | |||
| { | |||
| [$shop, $deposit] = self::getData($shopId); | |||
| return self::checkDepositWhenUse($shop, $deposit, $amount); | |||
| } | |||
| protected static function useDeposit( | |||
| string $shopId, | |||
| int $amount, | |||
| ) { | |||
| // データ取得 | |||
| [$shop, $deposit] = self::getData($shopId); | |||
| if (!self::checkDepositWhenUse($shop, $deposit, $amount)) { | |||
| throw new AppCommonException("利用時残高不足"); | |||
| } | |||
| // 異動履歴作成 | |||
| $history = self::makeTransferHistory($shopId, -1 * $amount); | |||
| // デポジット減算 | |||
| $deposit->deposit -= $amount; | |||
| // 保存 | |||
| $deposit->save(); | |||
| $history->save(); | |||
| } | |||
| /** | |||
| * デポジット異動履歴の作成 amountはdepositを使う場合は負の整数、チャージする場合は正の整数 | |||
| */ | |||
| protected static function makeTransferHistory(string $shopId, int $amount): DepositTransfer | |||
| { | |||
| [$shop, $deposit] = self::getData($shopId); | |||
| $transfer = new DepositTransfer(); | |||
| $transfer->shop_id = $shopId; | |||
| $transfer->transfer_datetime = DateUtil::now(); | |||
| $transfer->transfer_amount = -1 * $amount; | |||
| $transfer->before_amount = $deposit->deposit; | |||
| $transfer->after_amount = $deposit->deposit + $amount; | |||
| return $transfer; | |||
| } | |||
| /** | |||
| * @param string $shopId | |||
| * @return array{Shop, Deposit} | |||
| */ | |||
| protected static function getData(string $shopId) | |||
| { | |||
| // データ取得 コンテキストから取得する | |||
| $shop = ContextShop::instance()->get(); | |||
| $deposit = ContextDeposit::instance()->get(); | |||
| if ($shop === null) { | |||
| $shop = Shop::whereId($shopId)->firstOrFail(); | |||
| ContextShop::instance()->set($shop); | |||
| } else { | |||
| // 確認 | |||
| if ($shopId !== $shop->id) { | |||
| throw new LogicException("コンテキスト不正"); | |||
| } | |||
| } | |||
| if ($deposit === null) { | |||
| $deposit = Deposit::whereShopId($shopId)->firstOrFail(); | |||
| ContextDeposit::instance()->set($deposit); | |||
| } else { | |||
| // 確認 | |||
| if ($shopId !== $deposit->shop_id) { | |||
| throw new LogicException("コンテキスト不正"); | |||
| } | |||
| } | |||
| return [$shop, $deposit]; | |||
| } | |||
| private static function checkDepositWhenUse(Shop $shop, Deposit $deposit, int $amount) | |||
| { | |||
| return $shop->under_amount_when_use < $deposit->deposit - $amount; | |||
| } | |||
| private static function checkDepositWhenCertificate(Shop $shop, Deposit $deposit) | |||
| { | |||
| return $shop->under_amount_when_auth < $deposit->deposit; | |||
| } | |||
| private static function checkDepositWhenCreate(Shop $shop, Deposit $deposit) | |||
| { | |||
| return $shop->under_amount_when_create < $deposit->deposit; | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| <?php | |||
| namespace App\Logics\QRService; | |||
| class QRCryptoLogic | |||
| { | |||
| const CIPHER = "aes-256-ecb"; | |||
| const KEY = "axT59AhYNaxyK/X1fpQhEQ=="; | |||
| static public function encrypt(string $source) | |||
| { | |||
| $key = self::KEY; | |||
| $binary = openssl_encrypt($source, self::CIPHER, $key, OPENSSL_RAW_DATA); | |||
| return base64_encode($binary); | |||
| } | |||
| static public function decrypt(string $source) | |||
| { | |||
| $key = self::KEY; | |||
| $binary = base64_decode($source); | |||
| return openssl_decrypt($binary, self::CIPHER, $key, OPENSSL_RAW_DATA); | |||
| } | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| <?php | |||
| namespace App\Models; | |||
| use Exception; | |||
| use Illuminate\Database\Eloquent\Concerns\HasUuids; | |||
| use Illuminate\Database\Eloquent\SoftDeletes; | |||
| use Illuminate\Support\Facades\Auth; | |||
| use Illuminate\Support\Str; | |||
| abstract class AppModel extends BaseModel | |||
| { | |||
| use SoftDeletes, HasUuids; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| $historyName = static::class . 'History'; | |||
| $history = new $historyName; | |||
| if ($history instanceof HistoryModel) { | |||
| if (Auth::check()) { | |||
| $id = Auth::id(); | |||
| $history->updated_by = $id; | |||
| $history->created_by = $id; | |||
| } | |||
| return $history; | |||
| } else { | |||
| throw new Exception("履歴モデル不正"); | |||
| } | |||
| } | |||
| public function getChangeLogMessage($before, $after): ?string | |||
| { | |||
| return null; | |||
| } | |||
| public function setId(?string $uuid = null) | |||
| { | |||
| if ($this->id !== null) return; | |||
| if ($uuid) { | |||
| $this->id = $uuid; | |||
| } else { | |||
| $this->id = Str::uuid(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,95 @@ | |||
| <?php | |||
| namespace App\Models; | |||
| use App\Events\Model\CreatedEvent; | |||
| use App\Events\Model\CreatingEvent; | |||
| use App\Events\Model\DeletedEvent; | |||
| use App\Events\Model\UpdatingEvent; | |||
| use App\Models\Feature\IModelFeature; | |||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | |||
| use Illuminate\Database\Eloquent\Model; | |||
| use Illuminate\Database\Query\Builder; | |||
| use Illuminate\Support\Facades\DB; | |||
| use Illuminate\Support\Str; | |||
| /** | |||
| * @mixin \Eloquent | |||
| */ | |||
| abstract class BaseModel extends Model implements IModelFeature | |||
| { | |||
| use HasFactory; | |||
| const COL_NAME_ID = ColumnName::ID; | |||
| const COL_NAME_CREATED_BY = ColumnName::CREATED_BY; | |||
| const COL_NAME_UPDATED_BY = ColumnName::UPDATED_BY; | |||
| const COL_NAME_CREATED_AT = ColumnName::CREATED_AT; | |||
| const COL_NAME_UPDATED_AT = ColumnName::UPDATED_AT; | |||
| const COL_NAME_DELETED_AT = ColumnName::DELETED_AT; | |||
| protected $guarded = [ | |||
| self::COL_NAME_ID, | |||
| self::COL_NAME_CREATED_BY, | |||
| self::COL_NAME_UPDATED_BY, | |||
| self::COL_NAME_CREATED_AT, | |||
| self::COL_NAME_UPDATED_AT, | |||
| self::COL_NAME_DELETED_AT, | |||
| ]; | |||
| public static function getBuilder(string $name = 'main'): Builder | |||
| { | |||
| return DB::table(static::getTableName(), $name) | |||
| ->whereNull($name . "." . static::COL_NAME_DELETED_AT); | |||
| } | |||
| public static function getTableName(): string | |||
| { | |||
| return (new static)->getTable(); | |||
| } | |||
| public static function hasColumn(string $columnName): bool | |||
| { | |||
| $target = sprintf("%s::COL_NAME_%s", static::class, Str::upper($columnName)); | |||
| $ret = defined($target); | |||
| return $ret; | |||
| } | |||
| public function copy(IModelFeature $from): static | |||
| { | |||
| $data = $from->getAttributeKeys(); | |||
| foreach ($data as $key) { | |||
| $this->$key = $from->$key; | |||
| } | |||
| return $this; | |||
| } | |||
| public function getAttributeKeys(): array | |||
| { | |||
| return array_values(array_unique(array_merge(array_keys($this->attributesToArray()), $this->hidden))); | |||
| } | |||
| public function isNotSavedModel(): bool | |||
| { | |||
| return data_get($this, ColumnName::ID) === null; | |||
| } | |||
| protected $dispatchesEvents = [ | |||
| 'creating' => CreatingEvent::class, | |||
| 'created' => CreatedEvent::class, | |||
| 'updating' => UpdatingEvent::class, | |||
| 'deleted' => DeletedEvent::class, | |||
| ]; | |||
| // カラムが存在する項目のみfillするようオーバーライド | |||
| public function fill(array $atr) | |||
| { | |||
| $filterd = array_filter($atr, function ($value, $key) { | |||
| return static::hasColumn($key); | |||
| }, ARRAY_FILTER_USE_BOTH); | |||
| return parent::fill($filterd); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace App\Models; | |||
| abstract class Cast | |||
| { | |||
| const DATE = "date"; | |||
| const DATETIME = "datetime"; | |||
| const HASHED = "hashed"; | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| <?php | |||
| namespace App\Models; | |||
| abstract class ColumnName | |||
| { | |||
| // 共通 | |||
| const ID = 'id'; | |||
| const UPDATED_AT = 'updated_at'; | |||
| const UPDATED_BY = 'updated_by'; | |||
| const CREATED_AT = 'created_at'; | |||
| const CREATED_BY = 'created_by'; | |||
| const DELETED_AT = 'deleted_at'; | |||
| const HISTORY_ID = 'history_id'; | |||
| // 業務 | |||
| const USER_ID = 'user_id'; | |||
| const EMAIL_ID = "email_id"; | |||
| const SHOP_ID = "shop_id"; | |||
| const SHOP_NO = "shop_no"; | |||
| const PARKING_MANAGEMENT_CODE = "parking_management_code"; // 駐車場管理コード | |||
| const DISCOUNT_TICKET_PARKING_CODE = "discount_ticket_parking_code"; // サービス券駐車場コード | |||
| const DISCOUNT_TICKET_CODE = "discount_ticket_code"; // サービス券コード | |||
| const QR_SERVICE_PARKING_GROUP_ID = "qr_service_parking_group_id"; // QRサービス券駐車場グループID | |||
| const PUBLISHING_TERMINAL_CODE = "publishing_terminal_code"; // 発行端末番号 | |||
| const PUBLISHING_DATE = "publishing_date"; // QRサービス券発行日 | |||
| const PUBLISHING_NO = "publishing_no"; // QRサービス券発行日 | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| <?php | |||
| namespace App\Models\Feature; | |||
| use App\Models\HistoryModel; | |||
| use Illuminate\Database\Query\Builder; | |||
| use Illuminate\Support\Carbon; | |||
| /** | |||
| * @property ?Carbon $updated_at | |||
| * @property ?Carbon $created_at | |||
| * @property ?string $updated_by | |||
| * @property ?string $created_by | |||
| */ | |||
| interface IModelFeature | |||
| { | |||
| public static function getBuilder(string $name = 'main'): Builder; | |||
| public static function getTableName(): string; | |||
| public function copy(IModelFeature $from): static; | |||
| public function getAttributeKeys(): array; | |||
| public function isNotSavedModel(): bool; | |||
| public function getHistory(): HistoryModel|null; | |||
| /** | |||
| * モデルの和名を取得する | |||
| * | |||
| * @return string | |||
| */ | |||
| public function getModelName(): string; | |||
| public function getChangeLogMessage($before, $after): string|null; | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| <?php | |||
| namespace App\Models\Feature; | |||
| use App\Models\User; | |||
| trait UserId | |||
| { | |||
| public function user() | |||
| { | |||
| return $this->belongsTo(User::class); | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| <?php | |||
| namespace App\Models; | |||
| use App\Models\Feature\IModelFeature; | |||
| use Illuminate\Database\Eloquent\Collection; | |||
| abstract class HistoryModel extends BaseModel | |||
| { | |||
| const COL_NAME_HISTORY_ID = ColumnName::HISTORY_ID; | |||
| protected $primaryKey = ColumnName::HISTORY_ID; | |||
| /** | |||
| * @param string $id | |||
| * @return Collection<static> | |||
| */ | |||
| public static function findById(string $id) | |||
| { | |||
| return static::query()->where(ColumnName::ID, $id) | |||
| ->orderBy(ColumnName::CREATED_AT) | |||
| ->get(); | |||
| } | |||
| public function fillFromOrigin(IModelFeature $originModel) | |||
| { | |||
| return $this->copy($originModel); | |||
| } | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getChangeLogMessage($before, $after): ?string | |||
| { | |||
| return null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| <?php | |||
| namespace App\Models\Htpms; | |||
| use Illuminate\Database\Eloquent\Model; | |||
| class MstCustomer extends Model | |||
| { | |||
| const COL_NAME_ID = 'id'; | |||
| const COL_NAME_CUSTOMER_ID = 'customer_id'; | |||
| const COL_NAME_CUSTOMER_NAME = 'customer_name'; | |||
| protected $connection = 'htpms'; | |||
| protected $table = 'mst_customer'; | |||
| protected $fillable = []; // 参照専用 | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Deposit; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HistoryModel; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * デポジット | |||
| */ | |||
| class Deposit extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; // 店舗ID | |||
| const COL_NAME_DEPOSIT = "deposit"; // デポジット残高 | |||
| protected $table = "tbl3_dep_deposits"; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getModelName(): string | |||
| { | |||
| return "デポジット"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Deposit; | |||
| use App\Models\Cast; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HistoryModel; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * デポジット異動履歴 | |||
| */ | |||
| class DepositTransfer extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; // 店舗ID | |||
| const COL_NAME_TRANSFER_DATETIME = "transfer_datetime"; // 異動日時 | |||
| const COL_NAME_TRANSFER_AMOUNT = "transfer_amount"; // デポジット残高 | |||
| const COL_NAME_BEFORE_AMOUNT = "before_amount"; // 異動前デポジット | |||
| const COL_NAME_AFTER_AMOUNT = "after_amount"; // 異動後デポジット | |||
| protected $table = "tbl3_dep_deposit_transfers"; | |||
| protected $casts = [ | |||
| self::COL_NAME_TRANSFER_DATETIME => Cast::DATETIME, | |||
| ]; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getModelName(): string | |||
| { | |||
| return "デポジット異動履歴"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Existing; | |||
| use Illuminate\Database\Eloquent\Model; | |||
| class Parking extends Model | |||
| { | |||
| const COL_NAME_PARKING_MANAGEMENT_CODE = 'park_code'; | |||
| const COL_NAME_PARKING_NAME = 'park_name'; | |||
| protected $connection = 'htpms_customer'; | |||
| protected $table = 'tbl_park'; | |||
| protected $fillable = []; // 参照専用 | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer; | |||
| use App\Models\AppModel; | |||
| abstract class HtpmsCustomerAppModel extends AppModel | |||
| { | |||
| protected $connection = 'htpms_customer'; | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer; | |||
| use Illuminate\Support\Facades\DB; | |||
| class HtpmsCustomerConnectionSwitch | |||
| { | |||
| public static function switch(int $customerId): void | |||
| { | |||
| $connectionsRoot = "database.connections"; | |||
| $connectionHtmsCustomer = "htpms_customer"; | |||
| $databaseNameKey = "{$connectionsRoot}.{$connectionHtmsCustomer}.database"; | |||
| $currentDatabaseName = config($databaseNameKey); | |||
| $afterDatabaseName = "htpms_{$customerId}"; | |||
| if ($currentDatabaseName !== $afterDatabaseName) { | |||
| $conf = [$databaseNameKey => $afterDatabaseName]; | |||
| config($conf); | |||
| DB::reconnect($connectionHtmsCustomer); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer; | |||
| use App\Models\HistoryModel; | |||
| abstract class HtpmsCustomerHistoryModel extends HistoryModel | |||
| { | |||
| protected $connection = "htpms_customer"; | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Mst; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * 店舗マスタ | |||
| */ | |||
| class Shop extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_NAME = "name"; // 名称 | |||
| const COL_NAME_MEMO = "memo"; // メモ | |||
| const COL_NAME_QR_SERVICE_EXPIRE_MIN = "qr_service_expire_min"; // QRサービス券有効期限 | |||
| const COL_NAME_UNDER_AMOUNT_WHEN_CREATE = "under_amount_when_create"; //発行時デポジット下限値 | |||
| const COL_NAME_UNDER_AMOUNT_WHEN_AUTH = "under_amount_when_auth"; //認証時デポジット下限値 | |||
| const COL_NAME_UNDER_AMOUNT_WHEN_USE = "under_amount_when_use"; // 利用時デポジット下限値 | |||
| protected $table = "tbl3_mst_shops"; | |||
| public function getModelName(): string | |||
| { | |||
| return "店舗マスタ"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Mst; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerHistoryModel; | |||
| /** | |||
| * 店舗マスタ履歴 | |||
| */ | |||
| class ShopHistory extends HtpmsCustomerHistoryModel | |||
| { | |||
| protected $table = "tbl3_mst_shop_histories"; | |||
| public function getModelName(): string | |||
| { | |||
| return "店舗マスタ履歴"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Mst; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * 店舗番号紐づけ | |||
| */ | |||
| class ShopNoRelation extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; | |||
| const COL_NAME_PARKING_MANAGEMENT_CODE = ColumnName::PARKING_MANAGEMENT_CODE; | |||
| const COL_NAME_SHOP_NO = ColumnName::SHOP_NO; | |||
| protected $table = "tbl3_mst_shop_no_relations"; | |||
| public function getModelName(): string | |||
| { | |||
| return "店舗番号紐づけ"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\Mst; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerHistoryModel; | |||
| /** | |||
| * 店舗番号紐づけ履歴 | |||
| */ | |||
| class ShopNoRelationHistory extends HtpmsCustomerHistoryModel | |||
| { | |||
| protected $table = "tbl3_mst_shop_no_relation_histories"; | |||
| public function getModelName(): string | |||
| { | |||
| return "店舗マスタ履歴"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * QRサービス券取得可能設定 | |||
| */ | |||
| class AcquisitionAvailableSetting extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; // 店舗ID | |||
| const COL_NAME_QR_SERVICE_PARKING_GROUP_ID = ColumnName::QR_SERVICE_PARKING_GROUP_ID; // 駐車場管理コード | |||
| const COL_NAME_SHOP_NO = ColumnName::SHOP_NO; // 店舗番号 | |||
| const COL_NAME_DISCOUNT_TICKET_CODE = ColumnName::DISCOUNT_TICKET_CODE; // サービス券コード | |||
| protected $table = "tbl3_qrs_acquisition_available_settings"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券取得可能設定"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerHistoryModel; | |||
| /** | |||
| * QRサービス券取得可能設定履歴 | |||
| */ | |||
| class AcquisitionAvailableSettingHistory extends HtpmsCustomerHistoryModel | |||
| { | |||
| protected $table = "tbl3_qrs_acquisition_available_setting_histories"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券取得可能設定履歴"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\Cast; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HistoryModel; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| use App\Util\DateUtil; | |||
| use Illuminate\Support\Carbon; | |||
| /** | |||
| * 取得済みQRサービス券 | |||
| */ | |||
| class AcquisitionTicket extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; // 店舗ID | |||
| const COL_NAME_PUBLISHING_DATE = ColumnName::PUBLISHING_DATE; | |||
| const COL_NAME_PUBLISHING_NO = ColumnName::PUBLISHING_NO; | |||
| const COL_NAME_QR_SERVICE_PARKING_GROUP_ID = ColumnName::QR_SERVICE_PARKING_GROUP_ID; | |||
| const COL_NAME_DISCOUNT_TICKET_CODE = ColumnName::DISCOUNT_TICKET_CODE; // サービス券コード | |||
| const COL_NAME_SHOP_NO = ColumnName::SHOP_NO; // 店舗番号 | |||
| const COL_NAME_EXPIRES_AT = "expires_at"; // 有効期限 | |||
| const COL_NAME_PARKING_MANAGEMENT_CODE = ColumnName::PARKING_MANAGEMENT_CODE; // 駐車場管理コード | |||
| const COL_NAME_USED_AT = "used_at"; // 利用日時 | |||
| const COL_NAME_DISCOUNT_AMOUNT = "discount_amount"; // 割引金額 | |||
| protected $table = "tbl3_qrs_acquisition_tickets"; | |||
| protected $casts = [ | |||
| self::COL_NAME_PUBLISHING_DATE => Cast::DATE, | |||
| self::COL_NAME_EXPIRES_AT => Cast::DATETIME, | |||
| self::COL_NAME_USED_AT => Cast::DATETIME, | |||
| ]; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getModelName(): string | |||
| { | |||
| return "取得済みQRサービス券"; | |||
| } | |||
| /** | |||
| * 期限切れ判定 切れている場合trueを返却 | |||
| */ | |||
| public function iseExpired(?Carbon $基準時刻 = null): bool | |||
| { | |||
| if ($this->expires_at === null) return false; | |||
| if ($基準時刻 === null) { | |||
| $基準時刻 = DateUtil::now(); | |||
| } | |||
| if ($this->expires_at < $基準時刻) { | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| /** | |||
| * 使用済み判定 使用済みの場合trueを返却 | |||
| * | |||
| * @return boolean | |||
| */ | |||
| public function isUsed(): bool | |||
| { | |||
| return $this->used_at !== null; | |||
| } | |||
| public function canUse(): bool | |||
| { | |||
| return $this->isUsed() === false && $this->iseExpired() === false; | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HistoryModel; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * QRサービス券取得用トークン | |||
| */ | |||
| class AcquisitionTicketToken extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; | |||
| const COL_NAME_TOKEN = "token"; | |||
| protected $table = "tbl3_qrs_acquisition_ticket_tokens"; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券取得用トークン"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HistoryModel; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * QRサービス券認証可能設定 | |||
| */ | |||
| class CertificationAvailableSetting extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; // 店舗ID | |||
| const COL_NAME_PARKING_MANAGEMENT_CODE = ColumnName::PARKING_MANAGEMENT_CODE; // 駐車場管理コード | |||
| const COL_NAME_DISCOUNT_TICKET_CODE = ColumnName::DISCOUNT_TICKET_CODE; // サービス券コード | |||
| protected $table = "tbl3_qrs_certification_available_settings"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券認証可能設定"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerHistoryModel; | |||
| /** | |||
| * QRサービス券認証可能設定履歴 | |||
| */ | |||
| class CertificationAvailableSettingHistory extends HtpmsCustomerHistoryModel | |||
| { | |||
| protected $table = "tbl3_qrs_certification_available_setting_histories"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券認証可能設定履歴"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\Cast; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HistoryModel; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| use App\Util\DateUtil; | |||
| use Illuminate\Support\Carbon; | |||
| /** | |||
| * 認証済みQRサービス券 | |||
| */ | |||
| class CertificationTicket extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_PARKING_MANAGEMENT_CODE = ColumnName::PARKING_MANAGEMENT_CODE; // 駐車場管理コード | |||
| const COL_NAME_DISCOUNT_TICKET_CODE = ColumnName::DISCOUNT_TICKET_CODE; // サービス券コード | |||
| const COL_NAME_PUBLISHING_TERMINAL_CODE = ColumnName::PUBLISHING_TERMINAL_CODE; | |||
| const COL_NAME_PUBLISHING_DATE = ColumnName::PUBLISHING_DATE; | |||
| const COL_NAME_PUBLISHING_NO = ColumnName::PUBLISHING_NO; | |||
| const COL_NAME_SHOP_ID = ColumnName::SHOP_ID; // 店舗ID | |||
| const COL_NAME_SHOP_NO = ColumnName::SHOP_NO; // 店舗番号 | |||
| const COL_NAME_EXPIRES_AT = "expires_at"; // 有効期限 | |||
| const COL_NAME_USED_AT = "used_at"; // 利用日時 | |||
| const COL_NAME_DISCOUNT_AMOUNT = "discount_amount"; // 割引金額 | |||
| protected $table = "tbl3_qrs_certification_tickets"; | |||
| protected $casts = [ | |||
| self::COL_NAME_PUBLISHING_DATE => Cast::DATE, | |||
| self::COL_NAME_EXPIRES_AT => Cast::DATETIME, | |||
| self::COL_NAME_USED_AT => Cast::DATETIME, | |||
| ]; | |||
| public function getHistory(): ?HistoryModel | |||
| { | |||
| return null; | |||
| } | |||
| public function getModelName(): string | |||
| { | |||
| return "認証済みQRサービス券"; | |||
| } | |||
| /** | |||
| * 期限切れ判定 切れている場合trueを返却 | |||
| */ | |||
| public function iseExpired(?Carbon $基準時刻 = null): bool | |||
| { | |||
| if ($this->expires_at === null) return false; | |||
| if ($基準時刻 === null) { | |||
| $基準時刻 = DateUtil::now(); | |||
| } | |||
| if ($this->expires_at < $基準時刻) { | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| /** | |||
| * 使用済み判定 使用済みの場合trueを返却 | |||
| * | |||
| * @return boolean | |||
| */ | |||
| public function isUsed(): bool | |||
| { | |||
| return $this->used_at !== null; | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * QRサービス券駐車場グループ | |||
| */ | |||
| class ServiceParkingGroup extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_NAME = 'name'; // グループ名 | |||
| protected $table = "tbl3_qrs_service_parking_groups"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券駐車場グループ"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerHistoryModel; | |||
| /** | |||
| * QRサービス券駐車場グループ | |||
| */ | |||
| class ServiceParkingGroupHistory extends HtpmsCustomerHistoryModel | |||
| { | |||
| protected $table = "tbl3_qrs_service_parking_group_histories"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券駐車場グループ履歴"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| <?php | |||
| namespace App\Models\HtpmsCustomer\QRService; | |||
| use App\Models\ColumnName; | |||
| use App\Models\HtpmsCustomer\HtpmsCustomerAppModel; | |||
| /** | |||
| * QRサービス券駐車場グループ紐づけ | |||
| */ | |||
| class ServiceParkingGroupRelation extends HtpmsCustomerAppModel | |||
| { | |||
| const COL_NAME_QR_SERVICE_PARKING_GROUP_ID = ColumnName::QR_SERVICE_PARKING_GROUP_ID; | |||
| const COL_NAME_PARKING_MANAGEMENT_CODE = ColumnName::PARKING_MANAGEMENT_CODE; | |||
| protected $table = "tbl3_qrs_service_parking_group_relations"; | |||
| public function getModelName(): string | |||
| { | |||
| return "QRサービス券駐車場グループ紐づけ"; | |||
| } | |||
| } | |||