diff --git a/.env.example b/.env.example index 2794909..dee8d4a 100644 --- a/.env.example +++ b/.env.example @@ -56,3 +56,9 @@ VITE_PUSHER_HOST="${PUSHER_HOST}" VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +SAIL_XDEBUG_MODE=debug,develop + +SMS_PROVIDER=log +SMS_FOURS_MESSAGE_USER=null +SMS_FOURS_MESSAGE_PASSWORD=null \ No newline at end of file diff --git a/.env.testing b/.env.testing new file mode 100644 index 0000000..104241b --- /dev/null +++ b/.env.testing @@ -0,0 +1,64 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY=base64:7/KotwfYt4yVnc1Euu18lGSzo10TMPvW3JMv7LWdo8Y= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=web +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=pgsql +DB_HOST=pgsql +DB_PORT=5432 +DB_DATABASE=testing +DB_USERNAME=sail +DB_PASSWORD=password + +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_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}" + +SAIL_XDEBUG_MODE=debug,develop + +SMS_PROVIDER=log +SMS_FOURS_MESSAGE_USER=T8VWXuqlV2Z6 +SMS_FOURS_MESSAGE_PASSWORD=0Vjnak9FFPTH \ No newline at end of file diff --git a/app/Codes/FourSMessage/APIErrorCode.php b/app/Codes/FourSMessage/APIErrorCode.php new file mode 100644 index 0000000..cb04ad4 --- /dev/null +++ b/app/Codes/FourSMessage/APIErrorCode.php @@ -0,0 +1,40 @@ +boot(); + try { + $ret = $this->service(); + } catch (Exception $e) { + $message = sprintf("例外発生:%s:%s:%d", $e->getMessage(), $e->getFile(), $e->getLine()); + $this->outputError($message, $e->getTrace()); + $ret = self::RESULTCODE_FAILED; + } + + + if ($ret === self::RESULTCODE_SUCCESS) { + $this->outputInfoForBase("成功しました。"); + } else if ($ret === self::RESULTCODE_WARN) { + $this->outputWarn("一部失敗があります。"); + } else if ($ret === self::RESULTCODE_FAILED) { + $this->outputError("失敗しました"); + } else { + $this->outputError(sprintf("未定義のエラーコード:%d", $ret)); + } + + return $ret; + } + + private function boot() + { + Log::setDefaultDriver("batch"); + Log::withContext([ + '__scheduleId' => strval(Str::uuid()), + ...$this->arguments(), + ]); + $this->outputInfoForBase(sprintf("バッチ起動 %s", $this->getCommandName())); + } + + protected function outputInfo(string $message, array $context = []) + { + Log::info($message, $this->getContext($context)); + $this->info($message); + } + private function outputInfoForBase(string $message, array $context = []) + { + if ($this->outputInfoForBase) { + Log::info($message, $this->getContext($context)); + } + $this->info($message); + } + protected function outputWarn(string $message, array $context = []) + { + Log::warning($message, $this->getContext($context)); + $this->warn($message); + } + protected function outputError(string $message, array $context = []) + { + Log::error($message, $this->getContext($context)); + $this->error($message); + } + private function getContext(array $context = []) + { + return array_merge($context, ["context" => $this->arguments()]); + } + + protected function getCommandName(): string + { + return ""; + } +} diff --git a/app/Console/Commands/HeartBeat.php b/app/Console/Commands/HeartBeat.php new file mode 100644 index 0000000..b706414 --- /dev/null +++ b/app/Console/Commands/HeartBeat.php @@ -0,0 +1,60 @@ +option('maintenance')) { + $isMaintenanceMode = app()->isDownForMaintenance(); + if ($isMaintenanceMode) { + $this->outputWarn("down for maintenance"); + } + } else { + $this->outputInfo("heart beat"); + } + return self::RESULTCODE_SUCCESS; + } +} diff --git a/app/Console/Commands/PollSMSSendOrder.php b/app/Console/Commands/PollSMSSendOrder.php new file mode 100644 index 0000000..874bf5d --- /dev/null +++ b/app/Console/Commands/PollSMSSendOrder.php @@ -0,0 +1,97 @@ +getTargets(); + + $this->outputInfo(sprintf("対象:%d件", $targets->count())); + if ($targets->isEmpty()) { + return self::RESULTCODE_SUCCESS; + } else { + } + + foreach ($targets as $order) { + $this->handleOrder($order); + } + + return self::RESULTCODE_SUCCESS; + } + + public function getTargets() + { + return SMSSendOrder::whereDone(false) + ->get(); + } + + public function handleOrder(SMSSendOrder $order) + { + $ret = $this->manager->setOrder($order) + ->poll(); + + if ($ret) { + $order->save(); + + $this->outputInfo(sprintf( + "ID:%s 電話番号:%s %s", + $order->id, + $order->phone_number, + $order->done ? "完了" : "未" + )); + + // イベント発行 + if ($order->done) { + ConfirmEvent::dispatch($order); + } + } else { + $this->outputError(printf("失敗対象:%s", $order->toJson())); + throw new AppCommonException("POLL 失敗"); + } + } +} diff --git a/app/Console/Commands/RouteListCsv.php b/app/Console/Commands/RouteListCsv.php new file mode 100644 index 0000000..3a0fce8 --- /dev/null +++ b/app/Console/Commands/RouteListCsv.php @@ -0,0 +1,210 @@ + "csrf対策用", + ]; + + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'route:csv {--tsv}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'ルート一覧をcsv標準出力する'; + + private Context $context; + + private $fp = null; + + private string $separator; + + private int $rowIndex = 0; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + $this->outputInfoForBase = false; + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return int + */ + protected function service(): int + { + $routes = Route::getRoutes(); + $routes->getRoutes(); + + $this->separator = $this->getSeparator(); + + try { + $this->fp = fopen(base_path("routes.csv"), "w"); + $this->putHeader(); + + foreach ($routes->getRoutes() as $r) { + + if (Str::startsWith($r->uri(), ["_", "clockwork"])) { + continue; + } + + + $this->put($r); + } + } catch (Exception $e) { + + $this->outputError($e->getMessage()); + if ($this->fp !== null) { + fclose($this->fp); + $this->fp = null; + } + + return self::RESULTCODE_FAILED; + } + fclose($this->fp); + + return self::RESULTCODE_SUCCESS; + } + + private function putHeader() + { + fputcsv($this->fp, [ + "#", + "NAME", + "説明", + "メソッド", + "URI", + "S", + "C", + "A", + "L", + "N", + "コントローラー", + ], $this->separator); + } + + private function put(RoutingRoute $route) + { + if ($this->fp === null) { + throw new LogicException("FP null"); + } + + $controller = $route->getController(); + + + fputcsv($this->fp, [ + $this->rowIndex, + $this->getControllerName($controller), + $this->getControllerDescription($controller), + $this->getMethods($route), + $route->uri(), + $this->getRoleAuth($controller, UserRole::NORMAL_ADMIN), + $this->getRoleAuth($controller, UserRole::CONTRACT_ADMIN), + $this->getRoleAuth($controller, UserRole::SUPER_ADMIN), + $controller::class, + ], $this->separator); + + + + $this->rowIndex++; + } + private function getMethods(RoutingRoute $route) + { + return implode('|', $route->methods()); + } + + private function getRoleAuth($controller, UserRole $role): string + { + $ret = true; + if ($controller instanceof WebController) { + $ret = $controller->canAccess($role); + } + return $ret ? "〇" : "-"; + } + + private function getControllerName($controller): string + { + if ($controller instanceof WebController) { + return $controller->name(); + } else if ( + method_exists($controller, "name") && + is_callable([$controller, "name"]) + ) { + $ret = $controller->name(); + return is_string($ret) ? $ret : "-"; + } else { + + // コントローラ名から名前空間、メソッド名、等を除去 + return Str::of($controller::class) + ->afterLast("\\") + ->beforeLast("@") + ->replace("Controller", ""); + } + } + + private function getControllerDescription($controller): string + { + if ($controller instanceof WebController) { + return $controller->description(); + } else if ( + method_exists($controller, "description") && + is_callable([$controller, "description"]) + ) { + $ret = $controller->description(); + return is_string($ret) ? $ret : "-"; + } else if (Arr::has(self::CONTROLLER_DESCRIPTION, $controller::class)) { + return self::CONTROLLER_DESCRIPTION[$controller::class]; + } else { + return "-"; + } + } + + private function outputSjisFile() + { + + $contents = file_get_contents(base_path("routes.csv")); + + file_put_contents( + base_path("routes_sjis.csv"), + mb_convert_encoding( + $contents, + "SJIS", + "UTF8" + ) + ); + } + + private function getSeparator(): string + { + return $this->option("tsv") ? "\t" : ","; + } +} diff --git a/app/Console/Commands/TestMail.php b/app/Console/Commands/TestMail.php new file mode 100644 index 0000000..d3f7496 --- /dev/null +++ b/app/Console/Commands/TestMail.php @@ -0,0 +1,61 @@ +argument('email'); + + $mailer = new Test(); + $mailer->setEmail($email); + + $manager = new EmailManager($mailer); + $manager->confirm(); + + return self::RESULTCODE_SUCCESS; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index e6b9960..d060081 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -12,7 +12,9 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule): void { - // $schedule->command('inspire')->hourly(); + Schedules\HeartBeat::register($schedule); + Schedules\MailSend::register($schedule); + Schedules\SMSSend::register($schedule); } /** @@ -20,7 +22,7 @@ class Kernel extends ConsoleKernel */ protected function commands(): void { - $this->load(__DIR__.'/Commands'); + $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } diff --git a/app/Console/Schedules/BaseSchedule.php b/app/Console/Schedules/BaseSchedule.php new file mode 100644 index 0000000..dc0e0dd --- /dev/null +++ b/app/Console/Schedules/BaseSchedule.php @@ -0,0 +1,10 @@ +command(CommandsHeartBeat::class) + ->everyFiveMinutes() + ->evenInMaintenanceMode() + ->description("ハートビート"); + $schedule->command(CommandsHeartBeat::class, ['--maintenance']) + ->everyMinute() + ->evenInMaintenanceMode() + ->description("メンテナンスモード確認"); + } +} diff --git a/app/Console/Schedules/MailSend.php b/app/Console/Schedules/MailSend.php new file mode 100644 index 0000000..6b202d2 --- /dev/null +++ b/app/Console/Schedules/MailSend.php @@ -0,0 +1,19 @@ +command( + sprintf('queue:work --queue=%s --max-time=55 ', QueueName::EMAIL->value) + ) + ->everyMinute() + ->description("メールキュー処理"); + } +} diff --git a/app/Console/Schedules/SMSSend.php b/app/Console/Schedules/SMSSend.php new file mode 100644 index 0000000..09f6c24 --- /dev/null +++ b/app/Console/Schedules/SMSSend.php @@ -0,0 +1,19 @@ +command( + sprintf('queue:work --queue=%s --max-time=55 ', QueueName::SMS->value) + ) + ->everyMinute() + ->description("メールキュー処理"); + } +} diff --git a/app/Events/Mail/ConfirmEvent.php b/app/Events/Mail/ConfirmEvent.php new file mode 100644 index 0000000..715227b --- /dev/null +++ b/app/Events/Mail/ConfirmEvent.php @@ -0,0 +1,31 @@ +email = $email; + } else { + $this->email = $email->makeModel(); + $this->email->save(); + } + } +} diff --git a/app/Events/Model/CreatedEvent.php b/app/Events/Model/CreatedEvent.php new file mode 100644 index 0000000..dc37103 --- /dev/null +++ b/app/Events/Model/CreatedEvent.php @@ -0,0 +1,8 @@ +model = $model; + } + + /** + * Get the channels the event should broadcast on. + * + * @return array + */ + public function broadcastOn(): array + { + return [ + new PrivateChannel('channel-name'), + ]; + } +} diff --git a/app/Events/Model/UpdatingEvent.php b/app/Events/Model/UpdatingEvent.php new file mode 100644 index 0000000..cec6c8b --- /dev/null +++ b/app/Events/Model/UpdatingEvent.php @@ -0,0 +1,8 @@ +param = $param; + } + + public function param(): LoginParam + { + return $this->param; + } + + protected function run(Request $request): JsonResponse + { + // 取得したユーザ情報を登録しログインを行う + $param = $this->param(); + + + $user = User::whereEmail($param->email)->first(); + if ($user === null) { + return $this->failedResponse(); + } + + if (Auth::attempt([ + $this->username() => $param->email, + 'password' => $param->password, + ])) { + + $user = Auth::user(); + + return $this->successResponse(); + } else { + return $this->failedResponse([], '認証失敗'); + } + } +} diff --git a/app/Http/Controllers/Web/Auth/LoginParam.php b/app/Http/Controllers/Web/Auth/LoginParam.php new file mode 100644 index 0000000..47814c5 --- /dev/null +++ b/app/Http/Controllers/Web/Auth/LoginParam.php @@ -0,0 +1,20 @@ + $this->str(), + 'password' => $this->str(), + ]; + } +} diff --git a/app/Http/Controllers/Web/BaseParam.php b/app/Http/Controllers/Web/BaseParam.php index d4827b1..b978455 100644 --- a/app/Http/Controllers/Web/BaseParam.php +++ b/app/Http/Controllers/Web/BaseParam.php @@ -159,6 +159,15 @@ abstract class BaseParam implements IParam } 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, diff --git a/app/Http/Controllers/Web/ReceiptIssuingOrder/CreateController.php b/app/Http/Controllers/Web/ReceiptIssuingOrder/CreateController.php new file mode 100644 index 0000000..5d71f29 --- /dev/null +++ b/app/Http/Controllers/Web/ReceiptIssuingOrder/CreateController.php @@ -0,0 +1,48 @@ +middleware('auth:sanctum'); + $this->roleAllow(UserRole::NORMAL_ADMIN); + } + + protected function getParam(): IParam + { + return $this->param; + } + + protected function run(Request $request): JsonResponse + { + $param = $this->param; + + $this->manager->init() + ->fill($param->toArray()) + ->create(); + + + return $this->successResponse(); + } +} diff --git a/app/Http/Controllers/Web/ReceiptIssuingOrder/CreateParam.php b/app/Http/Controllers/Web/ReceiptIssuingOrder/CreateParam.php new file mode 100644 index 0000000..bf29d22 --- /dev/null +++ b/app/Http/Controllers/Web/ReceiptIssuingOrder/CreateParam.php @@ -0,0 +1,43 @@ + $this->str(true), + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY2 => $this->str(true), + ReceiptIssuingOrder::COL_NAME_SMS_PHONE_NUMBER => $this->str([new PhoneNumber()]), + ReceiptIssuingOrder::COL_NAME_RECEIPT_NO => $this->str(true), + ReceiptIssuingOrder::COL_NAME_RECEIPT_USE_DATETIME => $this->date(true), + ReceiptIssuingOrder::COL_NAME_RECEIPT_SHOP_NAME => $this->str(true), + ReceiptIssuingOrder::COL_NAME_RECEIPT_ISSUER => $this->str(true), + ReceiptIssuingOrder::COL_NAME_RECEIPT_PURPOSE => $this->str(true), + ReceiptIssuingOrder::COL_NAME_RECEIPT_AMOUNT => $this->numeric(['min:0', 'max:999999'], true), + ReceiptIssuingOrder::COL_NAME_MEMO => $this->text(true), + + ]; + } +} diff --git a/app/Http/Controllers/Web/WebController.php b/app/Http/Controllers/Web/WebController.php new file mode 100644 index 0000000..23e5e85 --- /dev/null +++ b/app/Http/Controllers/Web/WebController.php @@ -0,0 +1,363 @@ +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()]); + return $this->validateErrorResponse($e); + } + + try { + $this->validated = $validator->validated(); + $this->getParam()->setData($this->validated); + + $this->authorize(); + + return $this->run($request); + } catch (GeneralErrorMessageException $e) { + return $this->failedResponse([], $e->getMessage()); + } catch (AppCommonException $e) { + logs()->error(sprintf("Appエラー:%s", $e->getMessage())); + return $this->failedResponse(); + } catch (LogicException $e) { + 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) { + return $this->validateErrorResponse($e); + } catch (HttpException $e) { + if ($e->getStatusCode() === 401) { + return $this->unAuthorizedResponse(); + } + throw e; + } catch (Exception $e) { + 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(); + } + + private 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); + } + + return response() + ->json($ret) + ->withHeaders($this->makeHeader()); + } + + private function makeHeader(): array + { + $header = []; + $user = Auth::user(); + if ($user) { + $header["App-User-Auth"] = sprintf("%d,%d", $user->id, $user->role->value); + } else { + $header["App-User-Auth"] = 'none'; + } + return $header; + } + + // 以下 認可関係 + protected array|null $roleAllow = null; + protected array|null $roleDisallow = 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; + } + + $role = Auth::user()->role; + + if (!$this->canAccess($role)) { + abort(401); + } + } + + public function canAccess(UserRole $role) + { + if (is_array($this->roleAllow) && !in_array($role, $this->roleAllow)) { + return false; + } + + if (is_array($this->roleDisallow) && in_array($role, $this->roleDisallow)) { + return false; + } + return true; + } + + // 返却用データの登録 + 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(), + ]); + } +} diff --git a/app/Jobs/Mail/SimpleMail.php b/app/Jobs/Mail/SimpleMail.php new file mode 100644 index 0000000..e7fd3cd --- /dev/null +++ b/app/Jobs/Mail/SimpleMail.php @@ -0,0 +1,40 @@ +emailId = $email->id; + $this->onQueue(QueueName::EMAIL->value); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + Sender::send($this->emailId); + } +} diff --git a/app/Jobs/SMS/SendSMS.php b/app/Jobs/SMS/SendSMS.php new file mode 100644 index 0000000..fb0ea8f --- /dev/null +++ b/app/Jobs/SMS/SendSMS.php @@ -0,0 +1,49 @@ +onQueue(QueueName::SMS->value); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle(SMSManager $manager) + { + $ret = $manager->setOrder($this->order) + ->send(); + + if ($ret) { + info(sprintf( + "SMS送信依頼成功 address:%s order_id:%s", + $this->order->phone_number, + $this->order->id, + )); + } else { + $this->fail(sprintf("SMS送信依頼失敗 order_id:%s", $this->order->id)); + } + } +} diff --git a/app/Listeners/Mail/MailSendJobRegister.php b/app/Listeners/Mail/MailSendJobRegister.php new file mode 100644 index 0000000..0f8f10c --- /dev/null +++ b/app/Listeners/Mail/MailSendJobRegister.php @@ -0,0 +1,36 @@ +email; + if ($email->confirm_datetime === null && $email->send_datetime === null) { + + $email->confirm_datetime = DateUtil::now(); + $email->save(); + SimpleMail::dispatch($event->email); + } + } +} diff --git a/app/Listeners/Model/CreatedListener.php b/app/Listeners/Model/CreatedListener.php new file mode 100644 index 0000000..931bc11 --- /dev/null +++ b/app/Listeners/Model/CreatedListener.php @@ -0,0 +1,15 @@ +handleEvent($event->model); + } +} diff --git a/app/Listeners/Model/DeletedListener.php b/app/Listeners/Model/DeletedListener.php new file mode 100644 index 0000000..c2aadde --- /dev/null +++ b/app/Listeners/Model/DeletedListener.php @@ -0,0 +1,15 @@ +handleEvent($event->model); + } +} diff --git a/app/Listeners/Model/DeletingListener.php b/app/Listeners/Model/DeletingListener.php new file mode 100644 index 0000000..6c680d4 --- /dev/null +++ b/app/Listeners/Model/DeletingListener.php @@ -0,0 +1,15 @@ +handleEvent($event->model); + } +} diff --git a/app/Listeners/Model/ModelListener.php b/app/Listeners/Model/ModelListener.php new file mode 100644 index 0000000..2c3e4a1 --- /dev/null +++ b/app/Listeners/Model/ModelListener.php @@ -0,0 +1,59 @@ +createHistory($model); + + // 更新者作成者設定処理 + $this->setHandlerIds(); + } + + protected function createHistory(IModelFeature $model) + { + + $history = $model->getHistory(); + if ($history !== null) { + $history->fillFromOrigin($model); + $history->save(); + + $changeMessage = ""; + if ($model instanceof Model) { + $before = $model->getOriginal(); + $after = $model; + $message = $model->getChangeLogMessage($before, $after); + if ($message !== null) { + $changeMessage = sprintf("[%s]", $message); + } + } + Log::info(sprintf( + "モデル変更検知[%s][%s][ID:%s]%s", + $model->getModelName(), + static::ACTION, + data_get($model, ColumnName::ID), + $changeMessage + )); + } + } + + protected function setHandlerIds() + { + if (Auth::check()) { + $this->created_by = Auth::id(); + $this->updated_by = Auth::id(); + } + } +} diff --git a/app/Listeners/Model/UpdatingListener.php b/app/Listeners/Model/UpdatingListener.php new file mode 100644 index 0000000..c5d87d8 --- /dev/null +++ b/app/Listeners/Model/UpdatingListener.php @@ -0,0 +1,15 @@ +handleEvent($event->model); + } +} diff --git a/app/Listeners/SMS/ConfirmListener.php b/app/Listeners/SMS/ConfirmListener.php new file mode 100644 index 0000000..9e4bc9a --- /dev/null +++ b/app/Listeners/SMS/ConfirmListener.php @@ -0,0 +1,41 @@ +order; + + $receiptIssuingOrder = $smsSendOrder->receiptIssuingOrder; + if ($receiptIssuingOrder === null) { + return; + } + + $success = $smsSendOrder->send_datetime !== null; + + if ($success) { + $receiptIssuingOrder->sms_send_success = true; + $receiptIssuingOrder->save(); + } + } +} diff --git a/app/Logic/EmailManager.php b/app/Logic/EmailManager.php new file mode 100644 index 0000000..e3b9329 --- /dev/null +++ b/app/Logic/EmailManager.php @@ -0,0 +1,101 @@ +model = Email::lockForUpdate()->findOrfail($param); + $this->canSend = $this->model->send_datetime === null; + if (!$this->checkAuth()) throw new AppCommonException("メール権限エラー"); + } else if ($param instanceof BaseMailer) { + $this->model = $param->makeModel(); + } + } + + public function checkAuth() + { + return true; + } + + public function getEmailId() + { + return $this->model->id; + } + + public function getTimestamp(): Carbon|null + { + return $this->model->updated_at; + } + + public function create() + { + $this->model->save(); + return []; + } + + public function setSubject(string $subject) + { + if ($this->canSend()) { + $this->model->subject = $subject; + } else { + throw new AppCommonException("送信済みメール編集エラー"); + } + return $this; + } + + public function setContent(string $content) + { + + if ($this->canSend()) { + $this->model->content = $content; + } else { + throw new AppCommonException("送信済みメール編集エラー"); + } + return $this; + } + + public function update() + { + $this->model->save(); + return []; + } + + public function confirm() + { + + $validator = Validator::make(['email' => $this->model->email], [ + 'email' => ['required', 'email:strict,dns'] + ]); + + if ($validator->fails()) { + throw new Exception(sprintf("%s [%s]", $validator->errors()->first(), $this->model->email)); + } + + if ($this->canSend() !== null) { + ConfirmEvent::dispatch($this->model); + } else { + throw new AppCommonException("送信済みエラー"); + } + } + + public function canSend() + { + return $this->canSend; + } +} diff --git a/app/Logic/ReceiptIssuingOrder/CreateManager.php b/app/Logic/ReceiptIssuingOrder/CreateManager.php new file mode 100644 index 0000000..633e215 --- /dev/null +++ b/app/Logic/ReceiptIssuingOrder/CreateManager.php @@ -0,0 +1,79 @@ +order; + + + $this->initialized = true; + return $this; + } + + public function fill(array $attr) + { + $this->order->fill($attr); + return $this; + } + + public function create(): array + { + $order = $this->order; + + // パラメータチェック + $messages = $this->paramCheck(); + if (count($messages) !== 0) { + return $messages; + } + + // モデル更新 + $order->status = ReceiptIssuingOrderStatus::CREATED; + $this->refreshToken(); + + $contractId = $this->loginUser()->getContractId(); + if ($contractId === null) { + throw new LogicException("契約不良"); + } + $order->setContract($contractId) + ->save(); + + + // SMS配信 + $smsSendOrder = $this->smsManager::makeSMSSendOrder($order, SMSSendPurpose::SEND_RECEIPT_ISSUING_ORDER_FORM, $this->makeSMSContents()); + $smsSendOrder->send(); + + return []; + } + + private function paramCheck(): array + { + $ret = []; + $order = $this->order; + + return $ret; + } + + private function makeSMSContents(): string + { + return View::make('sms.announce_receipt_issusing_order_form', [ + 'url' => implode('/', [config('app.url'), 'receipt-issuing-order/create', $this->order->access_token]) + ])->render(); + } +} diff --git a/app/Logic/ReceiptIssuingOrder/PDFDownLoadManager.php b/app/Logic/ReceiptIssuingOrder/PDFDownLoadManager.php new file mode 100644 index 0000000..2324036 --- /dev/null +++ b/app/Logic/ReceiptIssuingOrder/PDFDownLoadManager.php @@ -0,0 +1,54 @@ +checkToken($token); + if (!$ret) { + throw new AppCommonException("トークン不正"); + } + + $this->initialized = true; + return $this; + } + + public function initById(string $id) + { + $this->fetch($id); + + $this->initialized = true; + return $this; + } + + protected function service() + { + $order = $this->order; + + $pdf = PDF::loadView('pdf', $order); + // はがきサイズを指定 + $ret = $pdf->setOption('page-height', 148) + ->setOption('page-width', 100) + ->setOption('encoding', 'utf-8') + ->inline(); + + $order->status = ReceiptIssuingOrderStatus::DOWNLOAD_DONE; + + return $ret; + } +} diff --git a/app/Logic/ReceiptIssuingOrder/ReceiptIssuingOrderManager.php b/app/Logic/ReceiptIssuingOrder/ReceiptIssuingOrderManager.php new file mode 100644 index 0000000..895af74 --- /dev/null +++ b/app/Logic/ReceiptIssuingOrder/ReceiptIssuingOrderManager.php @@ -0,0 +1,73 @@ +loginUser()->checkAuthorization($order)) { + throw new AppCommonException("認可不良"); + } + $this->order = $order; + } + + protected function refreshToken() + { + $order = $this->order; + + $order->access_token = base64_encode(Str::uuid()); + $order->access_token_expires_at = DateUtil::now()->adddays(7); + } + + protected function checkToken(string $token): bool + { + + if ($this->order->isNotSavedModel()) { + throw new LogicException("初期化不良"); + } + + $order = ReceiptIssuingOrder::whereAccessToken($token) + ->first(); + + if (!($order instanceof ReceiptIssuingOrder)) { + return false; + } + + if ($order->access_token_expires_at === null) { + return false; + } + + if ($order->access_token !== $token) { + return false; + } + + $now = DateUtil::now(); + + $ret = $now->lt($this->order->access_token_expires_at); + + if (!$ret) { + return false; + } + + $this->order = $order; + + return true; + } +} diff --git a/app/Logic/ReceiptIssuingOrder/UpdateManager.php b/app/Logic/ReceiptIssuingOrder/UpdateManager.php new file mode 100644 index 0000000..3b9fdda --- /dev/null +++ b/app/Logic/ReceiptIssuingOrder/UpdateManager.php @@ -0,0 +1,66 @@ +checkToken($token); + if (!$ret) { + throw new AppCommonException("トークン不正"); + } + + $this->initialized = true; + return $this; + } + + public function initById(string $id) + { + $this->fetch($id); + + $this->initialized = true; + return $this; + } + + public function fill(array $attr) + { + $this->order->fill($attr); + return $this; + } + + protected function service() + { + $order = $this->order; + + // パラメータチェック + $messages = $this->paramCheck(); + if (count($messages) !== 0) { + return $messages; + } + + // モデル更新 + $order->save(); + + return []; + } + + private function paramCheck(): array + { + $ret = []; + $order = $this->order; + + return $ret; + } +} diff --git a/app/Logic/SMS/FourSMessageManager.php b/app/Logic/SMS/FourSMessageManager.php new file mode 100644 index 0000000..582bdc5 --- /dev/null +++ b/app/Logic/SMS/FourSMessageManager.php @@ -0,0 +1,329 @@ +receipt_issuing_order_id = $receiptIssuingOrder->id; + $order->contract_id = $receiptIssuingOrder->contract_id; + $order->phone_number = $receiptIssuingOrder->sms_phone_number; + $order->sumamry_key1 = $receiptIssuingOrder->sumamry_key1; + $order->sumamry_key2 = $receiptIssuingOrder->sumamry_key2; + + $order->purpose = $purpose; + $order->content = $contents; + + return $$order; + } + + + public function __construct( + private SMSSendOrder $order, + ) { + } + + public function loadOrder(string $id): static + { + $this->order = SMSSendOrder::findOrFail($id); + return $this; + } + + public function setOrder(SMSSendOrder $order): static + { + $this->order = $order; + return $this; + } + + + public function getOrder() + { + return $this->order; + } + + public function send(): bool + { + + $sendContents = [ + 'cp_userid' => static::getUserId(), + 'cp_password' => static::getPassword(), + 'carrier_id' => '99', + 'message' => $this->order->content, + 'address' => $this->order->phone_number, + 'urlshorterflg' => '1', + 'send_date' => '202305120000', + ]; + + $this->order->sms_provider_id = CommunicationData::getMst()->id; + $res = $this->communication(self::SEND_URL, $sendContents); + + + if ($res->isSuccess()) { + // 送信依頼を作成 + $this->order->save(); + + $comm = new SMSProviderFourSMessageCommunication(); + $comm->sms_send_order_id = $this->order->id; + $comm->contract_id = $this->order->contract_id; + $comm->request_id = $res->requestId; + $comm->request_date = $res->requestDate; + $comm->save(); + + return true; + } else { + return false; + } + } + + public function poll(): bool + { + + $requestId = SMSProviderFourSMessageCommunication::whereSmsSendOrderId($this->order->id) + ->firstOrFail() + ->request_id; + + $sendContents = [ + 'request_id' => $requestId, + 'user_id' => static::getUserId(), + 'password' => base64_encode(static::getPassword()), + ]; + + $res = $this->communication(self::RESULT_URL, $sendContents); + + if ($res->isSuccess()) { + $res->save(); + } + + return $res->isSuccess(); + } + + + + public function cancel(): bool + { + + $requestId = SMSProviderFourSMessageCommunication::whereSmsSendOrderId($this->order->id) + ->firstOrFail() + ->request_id; + + $sendContents = [ + 'request_id' => $requestId, + 'cp_userid' => static::getUserId(), + 'cp_password' => static::getPassword(), + ]; + + $res = $this->communication(self::CANCEL_URL, $sendContents); + + return $res->isSuccess(); + } + + private function communication(array $urlDef, array $sendData): CommunicationData + { + + $method = $urlDef[0]; + + $form = Http::withHeaders([ + 'Content-Type' => 'application/x-www-form-urlencoded', + ]) + ->asForm(); + if ($method === 'GET') { + + $query = ""; + foreach ($sendData as $key => $value) { + if ($query !== "") { + $query .= '&'; + } + + $query .= $key . "=" . $value; + } + $url = $urlDef[1] . '?' . $query; + $res = $form->get($url); + } else { + $url = $urlDef[1]; + $res = $form->post($url, $sendData); + } + + logger(['SMS-SendContet' => $sendData, 'URL' => $url]); + + if (!$res->ok()) { + $res->throw(); + } + + logger(['SMS-ReveiveContet' => $res->json()]); + + $ret = new CommunicationData($res, $this->order); + + return $ret; + } +} + +class CommunicationData +{ + + public string|null $result; + public string|null $requestId; + public string|null $requestDate; + public string|null $errorCode; + public string|null $errorMessage; + + /** + * @var Collection + */ + public Collection $data; + + private SMSSendOrder $order; + + private const RESULT_SUCCESS = 'SUCCESS'; + + private const DATE_FORMAT = 'YmdHi'; + + public function __construct(Response $res, SMSSendOrder $order) + { + + $this->order = $order; + + $data = $res->json(); + $response = data_get($data, 'response'); + + if ($response !== null) { + $data = $response; + } + + $this->result = data_get($data, 'result'); + $this->requestId = data_get($data, 'request_id'); + $this->requestDate = data_get($data, 'request_date'); + $this->errorCode = data_get($data, 'error_code'); + $this->errorMessage = data_get($data, 'error_message'); + + $this->data = collect(); + + $records = data_get($data, 'records'); + if (is_array($records)) { + + foreach ($records as $record) { + $requestId = data_get($record, 'request_id'); + + // 空文字の場合があるので、数値にキャストする + $record['success_count'] = intVal($record['success_count']); + + $ele = SMSProviderFourSMessageCommunication::whereRequestId($requestId) + ->firstOrFail(); + + + $ele->fill($record); + $ele->sms_send_order_id = $order->id; + + $this->data->push($ele); + } + } + } + + public function isSuccess(): bool + { + return $this->result === self::RESULT_SUCCESS; + } + + public function save() + { + foreach ($this->data as $data) { + $data->save(); + + + if ($this->isFixRecord($data)) { + + $this->order->done = true; + + if ($data->deliv_rcpt_date !== null) { + $this->order->send_datetime = DateUtil::parse($data->deliv_rcpt_date, self::DATE_FORMAT); + } + + if ($data->success_count > 0) { + $this->order->cost = $this->calcCost($data); + } + } + } + } + + public function isFixRecord(SMSProviderFourSMessageCommunication $comm): bool + { + // 以下のコードになれば「状態確定」のタイミングであると判断できます。 + //  ・受付状態 : 90受付取消、または99受付失敗 + //  ・送信エラー: 31~35送信エラー、または40送達済 + + if (in_array($comm->request_status, ['90', '99'])) { + return true; + } + + if (in_array($comm->sending_status, [ + '31', + '32', + '33', + '34', + '35', + '40', + ])) { + return true; + } + + return false; + } + + private function calcCost(SMSProviderFourSMessageCommunication $comm): int + { + $provider = self::getMst(); + + return $comm->success_count * $provider->cost; + } + + private static function getMst(): SMSProvider + { + static $provider = null; + + if ($provider === null) { + $provider = SMSProvider::whereProviderName(SMSProviderName::FOUR_S_MESSAGE->value) + ->firstOrFail(); + } + + return $provider; + } +} diff --git a/app/Logic/SMS/LogManager.php b/app/Logic/SMS/LogManager.php new file mode 100644 index 0000000..5572741 --- /dev/null +++ b/app/Logic/SMS/LogManager.php @@ -0,0 +1,91 @@ +receipt_issuing_order_id = $receiptIssuingOrder->id; + $order->contract_id = $receiptIssuingOrder->contract_id; + $order->phone_number = $receiptIssuingOrder->sms_phone_number; + $order->sumamry_key1 = $receiptIssuingOrder->sumamry_key1; + $order->sumamry_key2 = $receiptIssuingOrder->sumamry_key2; + + $order->purpose = $purpose; + $order->content = $contents; + + $order->sms_provider_id = static::getMst()->id; + + return $order; + } + + + public function __construct( + private SMSSendOrder $order, + ) { + } + + public function loadOrder(string $id): static + { + $this->order = SMSSendOrder::findOrFail($id); + return $this; + } + + public function setOrder(SMSSendOrder $order): static + { + $this->order = $order; + return $this; + } + + + public function getOrder() + { + return $this->order; + } + + public function send(): bool + { + $this->order->send_datetime = DateUtil::now(); + $this->order->done = true; + $this->order->save(); + + info(sprintf("SMS送信ダミー:<<%s>>", $this->order->content)); + return true; + } + + public function poll(): bool + { + return true; + } + + public function cancel(): bool + { + + return true; + } + + private static function getMst(): SMSProvider + { + static $provider = null; + + if ($provider === null) { + $provider = SMSProvider::whereProviderName(SMSProviderName::LOG->value) + ->firstOrFail(); + } + + return $provider; + } +} diff --git a/app/Logic/SMS/SMSManager.php b/app/Logic/SMS/SMSManager.php new file mode 100644 index 0000000..f48cdc1 --- /dev/null +++ b/app/Logic/SMS/SMSManager.php @@ -0,0 +1,21 @@ +getAdminParams(), []); + } + + abstract public function getAdminParams(): array; +} diff --git a/app/Mail/Admins/Ask.php b/app/Mail/Admins/Ask.php new file mode 100644 index 0000000..ceb15f1 --- /dev/null +++ b/app/Mail/Admins/Ask.php @@ -0,0 +1,92 @@ +setValues($data); + if ($seasonTicketContract) { + $this->seasonTicketSeqNo = $seasonTicketContract->season_ticket_seq_no; + } + if ($subscription) { + $this->subscribeDatetime = $subscription->subscribe_datetime; + } + if ($user) { + $this->userEmail = $user->email; + } + if ($userDetail) { + $this->userName = sprintf( + "%s %s", + $userDetail->first_name, + $userDetail->last_name + ); + } + if ($parking) { + $this->parkName = $parking->park_name; + } + } + + public function getTemplateName(): string + { + return 'mails.admins.ask'; + } + + public function getSubject(): string + { + return sprintf( + '【スマートパーキングパス】【問い合わせ】【%s】%s', + $this->userName, + $this->askSubject + ); + } + + private function memberRegistration(): string + { + if ($this->seasonTicketSeqNo !== null || $this->subscribeDatetime !== null) { + return "あり"; + } + return "なし"; + } + + public function getAdminParams(): array + { + return [ + 'askSubject' => $this->askSubject, + 'askContents' => $this->askContents, + + 'parkName' => $this->parkName, + 'userName' => $this->userName, + 'userEmail' => $this->userEmail, + + 'seasonTicketSeqNo' => $this->seasonTicketSeqNo, + 'subscribeDatetime' => $this->subscribeDatetime ? $this->subscribeDatetime->format('Y/m/d H:i:s') : null, + + 'memberRegistration' => $this->memberRegistration(), + ]; + } +} diff --git a/app/Mail/BaseMailer.php b/app/Mail/BaseMailer.php new file mode 100644 index 0000000..3ca9856 --- /dev/null +++ b/app/Mail/BaseMailer.php @@ -0,0 +1,177 @@ +__email = $email; + Mail::to($email) + ->send($this); + } + + public function setUser(User $user) + { + $this->__userId = $user->id; + $this->__email = $user->email; + return $this; + } + + public function setEmail(string $target) + { + $this->__email = $target; + return $this; + } + + public function setUserId(string $userId) + { + $this->__userId = $userId; + return $this; + } + + public function setContractId(string $contractId) + { + $this->__contractId = $contractId; + return $this; + } + + public function setReceiptIssuingOrderId(string $receiptIssuingOrderId) + { + $this->__receiptIssuingOrderId = $receiptIssuingOrderId; + return $this; + } + + public function build() + { + return $this->text($this->getTemplateName()) + ->subject($this->getSubject()) + ->with($this->getParams()); + } + + public function setConfigDefault() + { + $root = $this->configRoot; + config([ + $root . "transport" => "smtp", + $root . "host" => env('MAIL_HOST', 'smtp.mailgun.org'), + $root . 'port' => env('MAIL_PORT', 587), + $root . 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + $root . 'username' => env('MAIL_USERNAME'), + $root . 'password' => env('MAIL_PASSWORD'), + $root . 'timeout' => null, + $root . 'auth_mode' => null, + $root . 'verify_peer' => false, + ]); + } + + public function setConfig(array $config) + { + $root = $this->configRoot; + $ret = []; + foreach ($config as $key => $val) { + $ret[$root . $key] = $val; + } + config($ret); + } + + protected function setValues(\stdClass|array $data) + { + foreach ($data as $key => $val) { + $this->setValue($key, $val); + } + } + + protected function setValue(string $key, $value) + { + $val = $value; + $camel = Str::camel($key); + $snake = Str::snake($key); + + $property = ""; + + if (property_exists($this, $camel)) { + $property = $camel; + } else if (property_exists($this, $snake)) { + $property = $snake; + } else { + return; + } + + if (data_get($this->casts, $property) === 'datetime') { + $this->$property = DateUtil::parse($val); + } else { + $this->$property = $val; + } + } + + public function makeModel(): Email + { + if ($this->__email === "") { + throw new AppCommonException("Email宛先不明"); + } + + $model = new Email(); + $model->subject = $this->getSubject(); + $model->content = $this->render(); + $model->type = get_class($this); + $model->email = $this->__email; + + $model->contract_id = $this->__contractId; + $model->user_id = $this->__userId; + $model->receipt_issuing_order_id = $this->__receiptIssuingOrderId; + + return $model; + } + + abstract public function getTemplateName(): string; + + abstract public function getSubject(): string; + + abstract public function getParams(): array; + + + /** + * 画面のURLを生成する + * + * @param array|string $path + * @return string + */ + protected function getAppUrl(array|string $path): string + { + $elements = [config("app.url")]; + if (is_array($path)) { + $elements = array_merge($elements, $path); + } else { + $elements[] = $path; + } + + return implode( + "/", + $elements, + ); + } +} diff --git a/app/Mail/Common/AskConfirmation.php b/app/Mail/Common/AskConfirmation.php new file mode 100644 index 0000000..bd7cb55 --- /dev/null +++ b/app/Mail/Common/AskConfirmation.php @@ -0,0 +1,70 @@ +setValues($data); + if ($user) { + $this->userEmail = $user->email; + $this->forMember = true; + } + if ($userDetail) { + $this->userName = sprintf( + "%s %s", + $userDetail->first_name, + $userDetail->last_name + ); + $this->forMember = true; + } + if ($parking) { + $this->parkName = $parking->park_name; + } + } + + public function getTemplateName(): string + { + return $this->forMember ? 'mails.members.ask_confirmation' : 'mails.guests.ask_confirmation'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】お問い合わせ内容の確認'; + } + + protected function getAskConfirmationParams(): array + { + return [ + 'askSubject' => $this->askSubject, + 'askContents' => $this->askContents, + + 'parkName' => $this->parkName, + 'userName' => $this->userName, + 'userEmail' => $this->userEmail, + ]; + } +} diff --git a/app/Mail/Guests/AskConfirmation.php b/app/Mail/Guests/AskConfirmation.php new file mode 100644 index 0000000..af804be --- /dev/null +++ b/app/Mail/Guests/AskConfirmation.php @@ -0,0 +1,27 @@ +setAskConfirmationData($data, $user, $userDetail, $parking); + } + + public function getGuestParams(): array + { + return $this->getAskConfirmationParams(); + } +} diff --git a/app/Mail/Guests/ChangeEmailStart.php b/app/Mail/Guests/ChangeEmailStart.php new file mode 100644 index 0000000..c562030 --- /dev/null +++ b/app/Mail/Guests/ChangeEmailStart.php @@ -0,0 +1,63 @@ + 'datetime', + ]; + + /** + * Create a new message instance. + * + * @return void + */ + public function __construct(ChangeEmail $model) + { + logger($model->toArray()); + $this->setValues($model->toArray()); + } + + public function getTemplateName(): string + { + return 'mails.guests.change_email_start'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】Email変更手続きのご案内'; + } + + public function getGuestParams(): array + { + return [ + 'url' => $this->getUrl(), + 'expiresAt' => $this->getExpiresAt(), + ]; + } + + private function getUrl() + { + return implode( + "/", + [ + config("app.url"), + 'change-email', + $this->token + ] + ); + } + private function getExpiresAt() + { + return $this->expiresAt->format('Y/m/d H:i'); + } +} diff --git a/app/Mail/Guests/EmailVerify.php b/app/Mail/Guests/EmailVerify.php new file mode 100644 index 0000000..4b64333 --- /dev/null +++ b/app/Mail/Guests/EmailVerify.php @@ -0,0 +1,65 @@ + 'datetime', + ]; + + /** + * Create a new message instance. + * + * @return void + */ + public function __construct(ModelsEmailVerify $model) + { + $this->setValues($model->toArray()); + } + + public function getTemplateName(): string + { + return 'mails.guests.email_verify'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】会員登録のご案内'; + } + + public function getGuestParams(): array + { + return [ + 'url' => $this->getUrl(), + 'expiresAt' => $this->getExpiresAt(), + ]; + } + + private function getUrl() + { + return implode( + "/", + [ + config("app.url"), + 'register/user', + $this->token + ] + ); + } + private function getExpiresAt() + { + $date = $this->expiresAt; + if ($date !== null) { + return $date->format('Y/m/d H:i'); + } + throw new Exception("有効期限日付不正"); + } +} diff --git a/app/Mail/Guests/Guest.php b/app/Mail/Guests/Guest.php new file mode 100644 index 0000000..104aecf --- /dev/null +++ b/app/Mail/Guests/Guest.php @@ -0,0 +1,15 @@ +getGuestParams(), []); + } + + abstract public function getGuestParams(): array; +} diff --git a/app/Mail/Members/AskConfirmation.php b/app/Mail/Members/AskConfirmation.php new file mode 100644 index 0000000..f16ccce --- /dev/null +++ b/app/Mail/Members/AskConfirmation.php @@ -0,0 +1,27 @@ +setAskConfirmationData($data, $user, $userDetail, $parking); + } + + public function getMemberParams(): array + { + return $this->getAskConfirmationParams(); + } +} diff --git a/app/Mail/Members/AutoCancelSeasonTicketContract.php b/app/Mail/Members/AutoCancelSeasonTicketContract.php new file mode 100644 index 0000000..5c3e504 --- /dev/null +++ b/app/Mail/Members/AutoCancelSeasonTicketContract.php @@ -0,0 +1,42 @@ +setValues($parking->toArray()); + $this->setValues($seasonTicketContract->toArray()); + } + + public function getTemplateName(): string + { + return 'mails.members.auto_cancel_season_ticket_contract'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】駐車場利用停止のお知らせ'; + } + + public function getMemberParams(): array + { + return [ + 'parkName' => $this->parkName, + 'seasonTicketSeqNo' => $this->seasonTicketSeqNo, + ]; + } +} diff --git a/app/Mail/Members/Bulk.php b/app/Mail/Members/Bulk.php new file mode 100644 index 0000000..e777f0e --- /dev/null +++ b/app/Mail/Members/Bulk.php @@ -0,0 +1,37 @@ +__subject = $subject; + $this->__contents = $contents; + } + + public function getTemplateName(): string + { + return 'mails.members.bulk'; + } + + public function getSubject(): string + { + return $this->__subject; + } + + public function getMemberParams(): array + { + return [ + 'contents' => $this->__contents, + ]; + } +} diff --git a/app/Mail/Members/Member.php b/app/Mail/Members/Member.php new file mode 100644 index 0000000..0499d13 --- /dev/null +++ b/app/Mail/Members/Member.php @@ -0,0 +1,17 @@ +getMemberParams(), [ + 'email' => $this->__email + ]); + } + + abstract public function getMemberParams(): array; +} diff --git a/app/Mail/Members/ResetIDm.php b/app/Mail/Members/ResetIDm.php new file mode 100644 index 0000000..acdc6c1 --- /dev/null +++ b/app/Mail/Members/ResetIDm.php @@ -0,0 +1,42 @@ +context(); + + $seasonTicketContract = $context->getSeasonTicketContract(); + if ($seasonTicketContract === null) { + throw new AppCommonException("コンテキスト不正 定期契約情報"); + } + + $this->confirmationCode = $seasonTicketContract->confirmation_code; + $this->seasonTicketSeqNo = $seasonTicketContract->season_ticket_seq_no; + } + + public function getTemplateName(): string + { + return 'mails.members.reset_idm'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】ICカードリセットのお知らせ'; + } + + public function getMemberParams(): array + { + return [ + 'confirmationCode' => $this->confirmationCode, + 'seasonTicketSeqNo' => $this->seasonTicketSeqNo, + ]; + } +} diff --git a/app/Mail/Members/ResetPasswordStart.php b/app/Mail/Members/ResetPasswordStart.php new file mode 100644 index 0000000..094e1be --- /dev/null +++ b/app/Mail/Members/ResetPasswordStart.php @@ -0,0 +1,61 @@ + 'datetime', + ]; + + /** + * Create a new message instance. + * + * @return void + */ + public function __construct(ResetPassword $model) + { + $this->setValues($model->toArray()); + } + + public function getTemplateName(): string + { + return 'mails.members.reset_password_start'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】パスワードリセット手続きのご案内'; + } + + public function getMemberParams(): array + { + return [ + 'url' => $this->getUrl(), + 'expiresAt' => $this->getExpiresAt(), + ]; + } + + private function getUrl() + { + return implode( + "/", + [ + config("app.url"), + 'password-reset', + $this->token + ] + ); + } + private function getExpiresAt() + { + return $this->expiresAt->format('Y/m/d H:i'); + } +} diff --git a/app/Mail/Members/SeasonTicketContractExpireRemind.php b/app/Mail/Members/SeasonTicketContractExpireRemind.php new file mode 100644 index 0000000..14de7f2 --- /dev/null +++ b/app/Mail/Members/SeasonTicketContractExpireRemind.php @@ -0,0 +1,45 @@ + 'datetime', + ]; + + /** + * Create a new message instance. + * + * @return void + */ + public function __construct(\stdClass $data) + { + $this->setValues($data); + } + + public function getTemplateName(): string + { + return 'mails.members.season_ticket_contract_expires_remind'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】定期期限切れ予告通知'; + } + + public function getMemberParams(): array + { + return [ + 'parkName' => $this->parkName, + 'seasonTicketSeqNo' => $this->seasonTicketSeqNo, + 'expirationEndDate' => $this->expirationEndDate, + ]; + } +} diff --git a/app/Mail/Members/Subscription/Approve.php b/app/Mail/Members/Subscription/Approve.php new file mode 100644 index 0000000..e4a4c7b --- /dev/null +++ b/app/Mail/Members/Subscription/Approve.php @@ -0,0 +1,111 @@ +email = $user->email; + $this->parkName = $parking->park_name; + $this->useStartRequestDate = $subscription ? $subscription->use_start_request_date : null; + $this->vehicleType = $seasonTicketContract->vehicle_type; + $this->contractorTypeName = $contractorTypeName; + $this->seasonTicketSeqNo = $seasonTicketContract->season_ticket_seq_no; + $this->confirmationCode = $seasonTicketContract->confirmation_code; + $this->parkingUseType = $seasonTicketContract->parking_use_type; + + $this->customerCode = $seasonTicketContract->customer_code; + $this->parkingManagementCode = $seasonTicketContract->parking_management_code; + } + + public function getTemplateName(): string + { + return 'mails.members.subscription.approve'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】定期申し込み完了'; + } + + public function getMemberParams(): array + { + return [ + 'email' => $this->email, + 'park_name' => $this->parkName, + 'use_start_request_date' => $this->getUseStartRequestDate(), + 'vehicle_type' => $this->getVehicleType(), + 'contractor_type_name' => $this->contractorTypeName, + 'how_to_use' => $this->getHowToUse(), + 'season_ticket_seq_no' => $this->seasonTicketSeqNo, + 'confirmation_code' => $this->confirmationCode, + 'ic_card_rental_request' => $this->parkingUseType === ParkingUseTypeCode::IC_RENTAL, + 'ic_self_card_request' => $this->parkingUseType === ParkingUseTypeCode::IC_SELF, + 'ic_qr_code_request' => $this->parkingUseType === ParkingUseTypeCode::QR, + 'my_qr_code_url' => $this->getMyQrCodeUrl(), + ]; + } + + private function getUseStartRequestDate() + { + if ($this->useStartRequestDate === null) return "-"; + return $this->useStartRequestDate->format("Y年m月d日"); + } + + private function getVehicleType() + { + return VehicleTypeCode::getName($this->vehicleType); + } + + private function getHowToUse() + { + switch ($this->parkingUseType) { + case ParkingUseTypeCode::IC_RENTAL: + return "貸与ICカード"; + case ParkingUseTypeCode::IC_SELF: + return "個人ICカード"; + case ParkingUseTypeCode::QR: + return "QRコード"; + } + throw new LogicException(sprintf("不適切な駐車場利用方法 %d", $this->parkingUseType->value)); + } + + private function getMyQrCodeUrl() + { + return $this->getAppUrl(['app', 'qrcode', 'detail', $this->customerCode, $this->parkingManagementCode]); + } +} diff --git a/app/Mail/Members/Subscription/Cancel.php b/app/Mail/Members/Subscription/Cancel.php new file mode 100644 index 0000000..eb033d7 --- /dev/null +++ b/app/Mail/Members/Subscription/Cancel.php @@ -0,0 +1,45 @@ +context(); + $parking = $context->getParking(); + if ($parking === null) { + throw new AppCommonException("コンテキスト不正 駐車場情報"); + } + + $this->parkName = $parking->park_name; + } + + public function getTemplateName(): string + { + return 'mails.members.subscription.cancel'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】申込キャンセルのお知らせ'; + } + + public function getMemberParams(): array + { + return [ + 'parkName' => $this->parkName, + ]; + } +} diff --git a/app/Mail/Members/Subscription/Entry.php b/app/Mail/Members/Subscription/Entry.php new file mode 100644 index 0000000..c078344 --- /dev/null +++ b/app/Mail/Members/Subscription/Entry.php @@ -0,0 +1,70 @@ +context(); + + $user = $context->getUser(); + $subscription = $context->getSeasonTicketContractSubscription(); + $parking = $context->getParking(); + + if ($user === null) { + throw new AppCommonException("コンテキスト不正 利用者情報"); + } + if ($subscription === null) { + throw new AppCommonException("コンテキスト不正 申込情報"); + } + if ($parking === null) { + throw new AppCommonException("コンテキスト不正 駐車場情報"); + } + + $this->email = $user->email; + $this->parkName = $parking->park_name; + $this->useStartRequestDate = $subscription->use_start_request_date; + $this->memo = $subscription->memo ?? ""; + } + + public function getTemplateName(): string + { + return 'mails.members.subscription.entry'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】定期申し込み内容'; + } + + public function getMemberParams(): array + { + return [ + 'email' => $this->email, + 'park_name' => $this->parkName, + 'use_start_request_date' => $this->getUseStartRequestDate(), + 'memo' => $this->memo + ]; + } + + public function getUseStartRequestDate() + { + if ($this->useStartRequestDate === null) return "-"; + return $this->useStartRequestDate->format("Y年m月d日"); + } +} diff --git a/app/Mail/Members/Subscription/Hold.php b/app/Mail/Members/Subscription/Hold.php new file mode 100644 index 0000000..747e1c6 --- /dev/null +++ b/app/Mail/Members/Subscription/Hold.php @@ -0,0 +1,47 @@ +context(); + $parking = $context->getParking(); + if ($parking === null) { + throw new AppCommonException("コンテキスト不正 駐車場情報"); + } + + $this->parkName = $parking->park_name; + $this->message = $message->message; + } + + public function getTemplateName(): string + { + return 'mails.members.subscription.hold'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】申込確認のお知らせ'; + } + + public function getMemberParams(): array + { + return [ + 'parkName' => $this->parkName, + 'memo' => $this->message, + ]; + } +} diff --git a/app/Mail/Members/Subscription/Reject.php b/app/Mail/Members/Subscription/Reject.php new file mode 100644 index 0000000..53e7e1d --- /dev/null +++ b/app/Mail/Members/Subscription/Reject.php @@ -0,0 +1,47 @@ +context(); + $parking = $context->getParking(); + if ($parking === null) { + throw new AppCommonException("コンテキスト不正 駐車場情報"); + } + + $this->parkName = $parking->park_name; + $this->message = $message->message ?? ""; + } + + public function getTemplateName(): string + { + return 'mails.members.subscription.reject'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】申込却下のお知らせ'; + } + + public function getMemberParams(): array + { + return [ + 'parkName' => $this->parkName, + 'memo' => $this->message, + ]; + } +} diff --git a/app/Mail/Members/Subscription/Returned.php b/app/Mail/Members/Subscription/Returned.php new file mode 100644 index 0000000..b0af117 --- /dev/null +++ b/app/Mail/Members/Subscription/Returned.php @@ -0,0 +1,48 @@ +context(); + $parking = $context->getParking(); + if ($parking === null) { + throw new AppCommonException("コンテキスト不正 駐車場情報"); + } + + $this->parkName = $parking->park_name; + $this->message = $message->message ?? ""; + } + + public function getTemplateName(): string + { + return 'mails.members.subscription.returned'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】申込差し戻しのお知らせ'; + } + + public function getMemberParams(): array + { + return [ + 'parkName' => $this->parkName, + 'memo' => $this->message, + ]; + } +} diff --git a/app/Mail/Members/UserRegisterComplete.php b/app/Mail/Members/UserRegisterComplete.php new file mode 100644 index 0000000..fe53a28 --- /dev/null +++ b/app/Mail/Members/UserRegisterComplete.php @@ -0,0 +1,76 @@ +email = $model->email; + + if ($password !== null) { + $this->includePasswordReset = true; + $this->expiresAt = $password->expires_at; + $this->token = $password->token; + } + } + + public function getTemplateName(): string + { + return 'mails.members.user_register_complete'; + } + + public function getSubject(): string + { + return '【スマートパーキングパス】会員登録完了'; + } + + public function getMemberParams(): array + { + return [ + 'email' => $this->email, + 'includePasswordReset' => $this->includePasswordReset, + 'url' => $this->getUrl(), + 'expiresAt' => $this->getExpiresAt(), + ]; + } + + private function getUrl() + { + return implode( + "/", + [ + config("app.url"), + 'password-reset', + $this->token + ] + ); + } + + private function getExpiresAt() + { + if ($this->expiresAt === null) { + return ""; + } else { + return $this->expiresAt->format('Y/m/d H:i'); + } + } +} diff --git a/app/Mail/Sender.php b/app/Mail/Sender.php new file mode 100644 index 0000000..8ed84eb --- /dev/null +++ b/app/Mail/Sender.php @@ -0,0 +1,43 @@ + $email->id, + 'email' => $email->email, + 'mailer' => $email->type, + ]); + + try { + Mail::to($email->email) + ->send(new TextMail($email->subject, $email->content)); + } catch (Exception $e) { + Log::error("メール送信失敗", [ + 'id' => $email->id, + 'email' => $email->email, + 'mailer' => $email->type, + ]); + $email->is_failed = true; + $email->save(); + throw $e; + } + + + $email->send_datetime = DateUtil::now(); + $email->save(); + } +} diff --git a/app/Mail/Test.php b/app/Mail/Test.php new file mode 100644 index 0000000..9f0c1e0 --- /dev/null +++ b/app/Mail/Test.php @@ -0,0 +1,21 @@ +__subject = $subject; + $this->__contents = $contents; + } + + public function getTemplateName(): string + { + return 'mails.free_text'; + } + + public function getSubject(): string + { + return $this->__subject; + } + + public function getParams(): array + { + return [ + 'contents' => $this->__contents, + ]; + } +} diff --git a/app/Models/AppModel.php b/app/Models/AppModel.php new file mode 100644 index 0000000..48a66ef --- /dev/null +++ b/app/Models/AppModel.php @@ -0,0 +1,22 @@ +getTable(); + } + + 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 = [ + 'created' => CreatedEvent::class, + 'updating' => UpdatingEvent::class, + 'deleted' => DeletedEvent::class, + ]; +} diff --git a/app/Models/ColumnName.php b/app/Models/ColumnName.php new file mode 100644 index 0000000..a086e01 --- /dev/null +++ b/app/Models/ColumnName.php @@ -0,0 +1,22 @@ +custom(), true); + } + + public function custom(): array + { + $custom = data_get($this, self::COL_NAME_CUSTOM); + if ($custom) { + return explode(',', $custom); + } else { + return []; + } + } +} diff --git a/app/Models/ContractHistory.php b/app/Models/ContractHistory.php new file mode 100644 index 0000000..d2e3343 --- /dev/null +++ b/app/Models/ContractHistory.php @@ -0,0 +1,12 @@ +contract->isNotSavedModel()) { + $this->contract = $this->user()->contract; + } + + return $this->contract; + } + + public function checkAuthorization(array|Model $target): bool + { + + if (!Auth::check()) { + return false; + } + + if ($this->user()->role === UserRole::SUPER_ADMIN) { + return true; + } + + $contractId = data_get($target, ColumnName::CONTRACT_ID); + if ($contractId === null) { + throw new LogicException("契約ID不正"); + } + + return $contractId === $this->user()->contract_id; + } + + public function getContractId(): ?string + { + if ($this->user()->role === UserRole::SUPER_ADMIN) { + + $session = request()->session(); + + if ($session->exists(self::SESSION_KEY_SADMIN_CONTRACT_ID)) { + return $session->get(self::SESSION_KEY_SADMIN_CONTRACT_ID); + } + return $this->contract()->id; + } + + return data_get($this->contract(), Contract::COL_NAME_ID); + } +} diff --git a/app/Models/Feature/ContractFeature.php b/app/Models/Feature/ContractFeature.php new file mode 100644 index 0000000..e60aa28 --- /dev/null +++ b/app/Models/Feature/ContractFeature.php @@ -0,0 +1,30 @@ +id; + data_set($this, self::COL_NAME_CONTRACT_ID, $id); + return $this; + } + + public function contract(): BelongsTo + { + if ($this instanceof Model) { + return $this->belongsTo(Contract::class); + } else { + throw new LogicException("不正"); + } + } +} diff --git a/app/Models/Feature/IModelFeature.php b/app/Models/Feature/IModelFeature.php new file mode 100644 index 0000000..866bac6 --- /dev/null +++ b/app/Models/Feature/IModelFeature.php @@ -0,0 +1,31 @@ +id; + data_set($this, self::COL_NAME_RECEIPT_ISSUING_ORDER_ID, $id); + return $this; + } + + public function receiptIssuingOrder(): BelongsTo + { + if ($this instanceof Model) { + return $this->belongsTo(ReceiptIssuingOrder::class); + } else { + throw new LogicException("不正"); + } + } +} diff --git a/app/Models/Feature/SMSProviderFeature.php b/app/Models/Feature/SMSProviderFeature.php new file mode 100644 index 0000000..b625a77 --- /dev/null +++ b/app/Models/Feature/SMSProviderFeature.php @@ -0,0 +1,30 @@ +id; + data_set($this, self::COL_NAME_SMS_PROVIDER_ID, $id); + return $this; + } + + public function smsProvider(): BelongsTo + { + if ($this instanceof Model) { + return $this->belongsTo(SMSProvider::class); + } else { + throw new LogicException("不正"); + } + } +} diff --git a/app/Models/Feature/SMSSendOrderFeature.php b/app/Models/Feature/SMSSendOrderFeature.php new file mode 100644 index 0000000..3598342 --- /dev/null +++ b/app/Models/Feature/SMSSendOrderFeature.php @@ -0,0 +1,29 @@ +id; + data_set($this, self::COL_NAME_SMS_SEND_ORDER_ID, $id); + } + + public function smsSendOrder(): BelongsTo + { + if ($this instanceof Model) { + return $this->belongsTo(SMSSendOrder::class); + } else { + throw new LogicException("不正"); + } + } +} diff --git a/app/Models/HistoryModel.php b/app/Models/HistoryModel.php new file mode 100644 index 0000000..dda7918 --- /dev/null +++ b/app/Models/HistoryModel.php @@ -0,0 +1,39 @@ + + */ + 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; + } +} diff --git a/app/Models/ReceiptIssuingHTParkingCustomOrder.php b/app/Models/ReceiptIssuingHTParkingCustomOrder.php new file mode 100644 index 0000000..9b4c6f0 --- /dev/null +++ b/app/Models/ReceiptIssuingHTParkingCustomOrder.php @@ -0,0 +1,15 @@ + ReceiptIssuingOrderStatus::class, + ]; + + public function getModelName(): string + { + return "領収証発行依頼"; + } +} diff --git a/app/Models/ReceiptIssuingOrderHistory.php b/app/Models/ReceiptIssuingOrderHistory.php new file mode 100644 index 0000000..581d5a6 --- /dev/null +++ b/app/Models/ReceiptIssuingOrderHistory.php @@ -0,0 +1,12 @@ + SMSSendPurpose::class, + ]; + + public function getModelName(): string + { + return "SMS送信依頼"; + } + + public function send() + { + $this->save(); + SendSMS::dispatch($this); + } +} diff --git a/app/Models/SMSSendOrderHistory.php b/app/Models/SMSSendOrderHistory.php new file mode 100644 index 0000000..7cfd914 --- /dev/null +++ b/app/Models/SMSSendOrderHistory.php @@ -0,0 +1,12 @@ + - */ - protected $fillable = [ - 'name', - 'email', - 'password', - ]; + const COL_NAME_ROLE = 'role'; + + // public function __construct(...$params) + // { + // $this->dispatchesEvents = [ + // 'created' => CreatedListener::class, + // 'updating' => UpdatingListener::class, + // 'deleting' => DeletingListener::class, + // ]; + // protected $dispatchesEvents = [ + // 'created' => CreatedEvent::class, + // 'updating' => UpdatingEvent::class, + // 'deleted' => DeletedEvent::class, + // ]; + // parent::__construct(...$params); + // } /** * The attributes that should be hidden for serialization. @@ -30,15 +43,60 @@ class User extends Authenticatable */ protected $hidden = [ 'password', - 'remember_token', ]; - /** - * The attributes that should be cast. - * - * @var array - */ protected $casts = [ - 'email_verified_at' => 'datetime', + self::COL_NAME_ROLE => UserRole::class, ]; + + protected $dispatchesEvents = [ + 'created' => CreatedEvent::class, + 'updating' => UpdatingEvent::class, + 'deleted' => DeletedEvent::class, + ]; + + public static function getBuilder(string $name = 'main'): Builder + { + return DB::table(static::getTableName(), $name); + } + + public static function getTableName(): string + { + return (new static)->getTable(); + } + + 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; + } + + public function getHistory(): ?HistoryModel + { + return new UserHistory(); + } + + public function getModelName(): string + { + return "ユーザー情報"; + } + + public function getChangeLogMessage($before, $after): ?string + { + return null; + } } diff --git a/app/Models/UserHistory.php b/app/Models/UserHistory.php new file mode 100644 index 0000000..79ac54c --- /dev/null +++ b/app/Models/UserHistory.php @@ -0,0 +1,12 @@ +app->isLocal()) { - + if ($this->app->environment('local')) { // IDEヘルパー登録 $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class); } @@ -31,7 +32,28 @@ class AppServiceProvider extends ServiceProvider //Queue関連 Queue::before(function (JobProcessing $event) { // Logのドライバー設定 - Log::setDefaultDriver('queue'); + $queueName = $event->job->getQueue(); + if ($queueName === QueueName::EMAIL->value) { + Log::setDefaultDriver('queue-email'); + } else if ($queueName === QueueName::SMS->value) { + Log::setDefaultDriver('queue-sms'); + } }); + + + // DB関連 + $this->app->singleton(\App\Util\DBUtil::class); + + + // SMS関連 + $smsProvider = config('logic.sms_provider'); + if ($smsProvider === SMSProviderName::FOUR_S_MESSAGE) { + $this->app->bind(\App\Logic\SMS\SMSManager::class, \App\Logic\SMS\FourSMessageManager::class); + } else { + $this->app->bind(\App\Logic\SMS\SMSManager::class, \App\Logic\SMS\LogManager::class); + } + + // User関連 + $this->app->singleton(\App\Models\Ex\LoginUser::class); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 2d65aac..87a48e4 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -33,6 +33,6 @@ class EventServiceProvider extends ServiceProvider */ public function shouldDiscoverEvents(): bool { - return false; + return true; } } diff --git a/app/Rules/BaseRule.php b/app/Rules/BaseRule.php new file mode 100644 index 0000000..101229d --- /dev/null +++ b/app/Rules/BaseRule.php @@ -0,0 +1,32 @@ +check($value); + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return '正しくありません。'; + } +} diff --git a/app/Rules/CustomerCode.php b/app/Rules/CustomerCode.php new file mode 100644 index 0000000..c1d38c6 --- /dev/null +++ b/app/Rules/CustomerCode.php @@ -0,0 +1,34 @@ +exists(); + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return trans('validation.exists'); + } +} diff --git a/app/Rules/Kana.php b/app/Rules/Kana.php new file mode 100644 index 0000000..98f6085 --- /dev/null +++ b/app/Rules/Kana.php @@ -0,0 +1,34 @@ +length($value)) return false; + if (!$this->charactor($value)) return false; + return true; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return $this->message; + } + + private function length(string $value) + { + $len = strlen($value); + if (8 <= $len && $len <= 20) { + return true; + } else { + $this->message = "{$this->min}文字以上 {$this->max}以下で設定してください"; + return false; + } + } + + private function charactor(string $value) + { + if (preg_match("/^[a-zA-Z0-9@.\-]+$/", $value) === 0) { + $this->message = "半角英数字と記号(@.-)のみ設定可能です。"; + return false; + } + return true; + } +} diff --git a/app/Rules/NotIncludeCode.php b/app/Rules/NotIncludeCode.php new file mode 100644 index 0000000..786755c --- /dev/null +++ b/app/Rules/NotIncludeCode.php @@ -0,0 +1,33 @@ +customerCode = $customerCode; + } + + + public function check($value) : bool + { + if (!is_string($value)) return false; + if (strlen($value) !== 5) return false; + + if(strlen($this->customerCode)){ + try { + KeysConverter::getTblPark($this->customerCode, $value); + }catch(Exception){ + return false; + } + } + return true; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return '正しくありません'; + } +} diff --git a/app/Rules/PhoneNumber.php b/app/Rules/PhoneNumber.php new file mode 100644 index 0000000..a2e3f4d --- /dev/null +++ b/app/Rules/PhoneNumber.php @@ -0,0 +1,32 @@ +isValid()) { + + if ($format) { + $date = Carbon::createFromFormat($format, $source); + } else { + $date = Carbon::parse($source); + } + + if ($date && $date->isValid()) { return $date->timezone(config('app.timezone')); } return null; diff --git a/app/Util/MigrationHelper.php b/app/Util/MigrationHelper.php new file mode 100644 index 0000000..80ac21a --- /dev/null +++ b/app/Util/MigrationHelper.php @@ -0,0 +1,143 @@ +comment($model->getModelName()); + } + + $forHistory = false; + if (Str::endsWith($tableName, "histories")) { + $forHistory = true; + } + $helper = new MigrationHelper($table, $forHistory); + $schema($table, $helper); + }); + } + + public static function alterTable(string $tableName, Closure $schema) + { + Schema::table($tableName, function (Blueprint $table) use ($tableName, $schema) { + $forHistory = false; + if (Str::endsWith($tableName, "histories")) { + $forHistory = true; + } + $helper = new MigrationHelper($table, $forHistory); + $schema($table, $helper); + }); + } + + + + private Blueprint $table; + private bool $forHistory = false; + + public function __construct(Blueprint $table, bool $forHistory = false) + { + $this->table = $table; + $this->forHistory = $forHistory; + } + + + public function baseColumn() + { + if ($this->forHistory) { + $this->table->id('history_id')->comment("履歴ID"); + $this->table->uuid('id')->comment("ID"); + } else { + $this->table->uuid('id')->primary()->comment("ID"); + } + $this->table->unsignedBigInteger(ColumnName::CREATED_BY)->nullable()->comment("作成者ID"); + $this->table->unsignedBigInteger(ColumnName::UPDATED_BY)->nullable()->comment("更新者ID"); + $this->table->timestamp(ColumnName::CREATED_AT)->nullable()->comment("作成日時"); + $this->table->timestamp(ColumnName::UPDATED_AT)->nullable()->comment("更新日時"); + $this->table->timestamp(ColumnName::DELETED_AT)->nullable()->comment("論理削除日時"); + + return $this; + } + + public function index(int $number, array $columns) + { + $indexName = $this->getIndexName($number); + if ($this->forHistory) { + $this->table->index($columns, $indexName); + } else { + $this->table->index([ColumnName::DELETED_AT, ...$columns], $indexName); + } + return $this; + } + + public function dropIndex(int $number) + { + $indexName = $this->getIndexName($number); + $this->table->dropIndex($indexName); + return $this; + } + + private function getIndexName(int $number) + { + return sprintf("%s_idx_%02d", $this->table->getTable(), $number); + } + + + public function contractId(bool $nullable = false) + { + $this->table->uuid(ColumnName::CONTRACT_ID)->comment("契約ID")->nullable($nullable); + $this->table->foreign(ColumnName::CONTRACT_ID)->references(ColumnName::ID)->on(Contract::getTableName()); + + return $this; + } + + public function receiptIssuingOrderId(bool $nullable = false) + { + $this->table->uuid(ColumnName::RECEIPT_ISSUING_ORDER_ID)->comment("領収証発行依頼ID")->nullable($nullable); + $this->table->foreign(ColumnName::RECEIPT_ISSUING_ORDER_ID)->references(ColumnName::ID)->on(ReceiptIssuingOrder::getTableName()); + return $this; + } + + public function smsSendOrderId(bool $nullable = false) + { + $this->table->uuid(ColumnName::SMS_SEND_ORDER_ID)->comment("SMS送信依頼ID")->nullable($nullable); + $this->table->foreign(ColumnName::SMS_SEND_ORDER_ID)->references(ColumnName::ID)->on(SMSSendOrder::getTableName()); + return $this; + } + + public function userId(bool $nullable = false) + { + $this->table->uuid(ColumnName::USER_ID)->comment("ユーザーID")->nullable($nullable); + $this->table->foreign(ColumnName::USER_ID)->references(ColumnName::ID)->on(User::getTableName()); + return $this; + } + + public function smsProviderId(bool $nullable = false) + { + $this->table->uuid(ColumnName::SMS_PROVIDER_ID)->comment("SMSプロバイダーID")->nullable($nullable); + $this->table->foreign(ColumnName::SMS_PROVIDER_ID)->references(ColumnName::ID)->on(SMSProvider::getTableName()); + return $this; + } +} diff --git a/config/cors.php b/config/cors.php index 8a39e6d..cc63f4f 100644 --- a/config/cors.php +++ b/config/cors.php @@ -19,7 +19,7 @@ return [ 'allowed_methods' => ['*'], - 'allowed_origins' => ['*'], + 'allowed_origins' => ['http://localhost:*', config('app.url')], 'allowed_origins_patterns' => [], @@ -29,6 +29,6 @@ return [ 'max_age' => 0, - 'supports_credentials' => false, + 'supports_credentials' => true, ]; diff --git a/config/logging.php b/config/logging.php index c013c81..4718020 100644 --- a/config/logging.php +++ b/config/logging.php @@ -144,9 +144,17 @@ return [ // 'replace_placeholders' => true, 'permission' => 0666, ], - 'queue' => [ + 'queue-email' => [ 'driver' => 'daily', - 'path' => storage_path('logs/queue.log'), + 'path' => storage_path('logs/email.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 14, + // 'replace_placeholders' => true, + 'permission' => 0666, + ], + 'queue-sms' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/sms.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, // 'replace_placeholders' => true, diff --git a/config/logic.php b/config/logic.php new file mode 100644 index 0000000..361a4c4 --- /dev/null +++ b/config/logic.php @@ -0,0 +1,22 @@ + SMSProviderName::from(env('SMS_PROVIDER', SMSProviderName::LOG->value)), + + 'sms' => [ + + SMSProviderName::FOUR_S_MESSAGE->value => [ + 'userId' => env("SMS_FOURS_MESSAGE_USER"), + 'password' => env("SMS_FOURS_MESSAGE_PASSWORD"), + ], + + SMSProviderName::LOG->value => [ + 'userId' => env("SMS_FOURS_MESSAGE_USER"), + 'password' => env("SMS_FOURS_MESSAGE_PASSWORD"), + ] + ] +]; diff --git a/database/factories/ContractFactory.php b/database/factories/ContractFactory.php new file mode 100644 index 0000000..b0eb6da --- /dev/null +++ b/database/factories/ContractFactory.php @@ -0,0 +1,24 @@ + + */ +class ContractFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + Contract::COL_NAME_NAME => fake()->name(), + ]; + } +} diff --git a/database/factories/SMSProviderFactory.php b/database/factories/SMSProviderFactory.php new file mode 100644 index 0000000..b33257a --- /dev/null +++ b/database/factories/SMSProviderFactory.php @@ -0,0 +1,25 @@ + + */ +class SMSProviderFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + SMSProvider::COL_NAME_PROVIDER_NAME => fake()->name(), + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index a6ecc0a..474c2ce 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -2,8 +2,10 @@ namespace Database\Factories; +use App\Codes\UserRole; +use App\Models\Contract; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Support\Str; +use Illuminate\Support\Facades\Hash; /** * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> @@ -18,21 +20,10 @@ class UserFactory extends Factory public function definition(): array { return [ - 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password - 'remember_token' => Str::random(10), + 'password' => Hash::make("testuser"), + 'role' => UserRole::NORMAL_ADMIN, + 'contract_id' => Contract::factory(), ]; } - - /** - * Indicate that the model's email address should be unverified. - */ - public function unverified(): static - { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); - } } diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php deleted file mode 100644 index 444fafb..0000000 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ /dev/null @@ -1,32 +0,0 @@ -id(); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->rememberToken(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('users'); - } -}; diff --git a/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php b/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php deleted file mode 100644 index 81a7229..0000000 --- a/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php +++ /dev/null @@ -1,28 +0,0 @@ -string('email')->primary(); - $table->string('token'); - $table->timestamp('created_at')->nullable(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('password_reset_tokens'); - } -}; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php deleted file mode 100644 index e828ad8..0000000 --- a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php +++ /dev/null @@ -1,33 +0,0 @@ -id(); - $table->morphs('tokenable'); - $table->string('name'); - $table->string('token', 64)->unique(); - $table->text('abilities')->nullable(); - $table->timestamp('last_used_at')->nullable(); - $table->timestamp('expires_at')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('personal_access_tokens'); - } -}; diff --git a/database/migrations/2023_04_13_100116_create_jobs_table.php b/database/migrations/2023_04_13_100116_create_jobs_table.php new file mode 100644 index 0000000..6098d9b --- /dev/null +++ b/database/migrations/2023_04_13_100116_create_jobs_table.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + } +}; diff --git a/database/migrations/2023_04_13_150300_create_contracts_table.php b/database/migrations/2023_04_13_150300_create_contracts_table.php new file mode 100644 index 0000000..28a75d1 --- /dev/null +++ b/database/migrations/2023_04_13_150300_create_contracts_table.php @@ -0,0 +1,38 @@ +schema()); + MigrationHelper::createTable('contract_histories', $this->schema()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('contracts'); + Schema::dropIfExists('contract_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn(); + + $table->string('name')->comment("契約者名")->nullable(); + $table->text('custom')->comment("カスタム")->nullable(); + }; + } +}; diff --git a/database/migrations/2023_04_15_000000_create_sms_providers_table.php b/database/migrations/2023_04_15_000000_create_sms_providers_table.php new file mode 100644 index 0000000..b597028 --- /dev/null +++ b/database/migrations/2023_04_15_000000_create_sms_providers_table.php @@ -0,0 +1,45 @@ +schema()); + MigrationHelper::createTable('s_m_s_provider_histories', $this->schema()); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('s_m_s_providers'); + Schema::dropIfExists('s_m_s_provider_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn(); + + $table->string('provider_name')->comment("SMSプロバイダー")->nullable(); + $table->unsignedInteger('cost')->comment("送信単価")->default(0); + + $helper->index(1, ['provider_name']); + }; + } +}; diff --git a/database/migrations/2023_04_15_150500_create_users_table.php b/database/migrations/2023_04_15_150500_create_users_table.php new file mode 100644 index 0000000..d492c5b --- /dev/null +++ b/database/migrations/2023_04_15_150500_create_users_table.php @@ -0,0 +1,46 @@ +schema()); + MigrationHelper::createTable('user_histories', $this->schema()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('user_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn() + ->contractId(); + + $table->string('email')->comment("Email")->nullable(); + $table->string('password')->comment("ログインパスワード")->nullable(); + $table->unsignedTinyInteger("role")->comment("認可")->nullable(); + + + $helper->index(1, [ColumnName::CONTRACT_ID]); + $helper->index(2, ['email']); + }; + } +}; diff --git a/database/migrations/2023_04_15_152400_create_receipt_issuing_orders_table.php b/database/migrations/2023_04_15_152400_create_receipt_issuing_orders_table.php new file mode 100644 index 0000000..b95c3e2 --- /dev/null +++ b/database/migrations/2023_04_15_152400_create_receipt_issuing_orders_table.php @@ -0,0 +1,74 @@ +schema()); + MigrationHelper::createTable('receipt_issuing_order_histories', $this->schema()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('receipt_issuing_orders'); + Schema::dropIfExists('receipt_issuing_order_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn() + ->contractId(); + + $table->string("handler_id")->comment("担当者ID")->nullable(); + $table->datetime("order_datetime")->comment("依頼日時")->nullable(); + $table->string("status")->comment("ステータス")->nullable(); + $table->string("sumamry_key1")->comment("集計キー1")->nullable(); + $table->string("sumamry_key2")->comment("集計キー2")->nullable(); + $table->string("access_token")->comment("アクセストークン")->nullable(); + $table->datetime("access_token_expires_at")->comment("アクセストークン有効期限日時")->nullable(); + + // 以下、SMS送信関連 + $table->string("sms_phone_number")->comment("SMS_電話番号")->nullable(); + $table->boolean("sms_send_success")->comment("SMS_送信成功")->default(false); + + + // 以下、領収証関連 + $table->string("receipt_no")->comment("領収証発行番号")->nullable(); + $table->string("receipt_use_datetime")->comment("領収証_利用日時")->nullable(); + $table->string("receipt_shop_name")->comment("領収証_発行店名")->nullable(); + $table->string("receipt_issuer")->comment("領収証_発行者名")->nullable(); + $table->string("receipt_purpose")->comment("領収証_但書き")->nullable(); + $table->string("receipt_invoice_no")->comment("領収証_インボイス登録番号")->nullable(); + $table->string("receipt_amount")->comment("領収証_金額")->nullable(); + $table->string("receipt_how_to_receive")->comment("領収証_取得希望方法")->nullable(); + $table->string("email")->comment("Email")->nullable(); + + // 以下、郵送関連 + $table->string("mail_pref_code")->comment("郵送_都道府県")->nullable(); + $table->string("mail_zip_code")->comment("郵送_郵便番号")->nullable(); + $table->string("mail_address1")->comment("郵送_住所1")->nullable(); + $table->string("mail_address2")->comment("郵送_住所2")->nullable(); + $table->string("mail_address3")->comment("郵送_住所3")->nullable(); + $table->date("mail_post_date")->comment("郵送_投函日")->nullable(); + + $table->text('memo')->comment("備考")->nullable(); + + $helper->index(1, [ColumnName::CONTRACT_ID, 'order_datetime']); + }; + } +}; diff --git a/database/migrations/2023_04_18_131700_create_sms_send_orders_table.php b/database/migrations/2023_04_18_131700_create_sms_send_orders_table.php new file mode 100644 index 0000000..5f61ecc --- /dev/null +++ b/database/migrations/2023_04_18_131700_create_sms_send_orders_table.php @@ -0,0 +1,61 @@ +schema()); + MigrationHelper::createTable('s_m_s_send_order_histories', $this->schema()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('s_m_s_send_orders'); + Schema::dropIfExists('s_m_s_send_order_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn() + ->contractId() + ->receiptIssuingOrderId(true) + ->smsProviderId(true); + + $table->string("purpose")->comment("送信目的")->nullable(); + $table->datetime("send_datetime")->comment("送信日時")->nullable(); + $table->boolean("done")->comment("完了")->default(false); + + + + $table->unsignedInteger("cost")->comment("コスト")->default(0); + + $table->text('content')->comment("本文")->nullable(); + $table->text('phone_number')->comment("送信先電話番号")->nullable(); + + $table->string("sumamry_key1")->comment("集計キー1")->nullable(); + $table->string("sumamry_key2")->comment("集計キー2")->nullable(); + + + $helper->index(1, [ColumnName::CONTRACT_ID, 'send_datetime']); + $helper->index(2, [ColumnName::CONTRACT_ID, 'done']); + $helper->index(3, [ColumnName::CONTRACT_ID, 'sumamry_key1', 'sumamry_key2']); + + $helper->index(4, [ColumnName::CONTRACT_ID, 'done']); + }; + } +}; diff --git a/database/migrations/2023_04_18_131800_create_sms_provider_foursmessage_communications_table.php b/database/migrations/2023_04_18_131800_create_sms_provider_foursmessage_communications_table.php new file mode 100644 index 0000000..d443502 --- /dev/null +++ b/database/migrations/2023_04_18_131800_create_sms_provider_foursmessage_communications_table.php @@ -0,0 +1,64 @@ +schema()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('s_m_s_provider_four_s_message_communications'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn() + ->contractId() + ->smsSendOrderId(); + + $table->string("request_id")->comment("配信要求送信リクエストID")->nullable(); + $table->string("request_date")->comment("リクエスト受付時刻")->nullable(); + $table->string("request_status")->comment("リクエスト受付状態")->nullable(); + $table->string("cp_id")->comment("コンテンツパートナーID")->nullable(); + $table->string("address")->comment("宛先電話番号")->nullable(); + $table->string("requested_carrier_code")->comment("リクエスト時に指定したキャリア")->nullable(); + $table->string("requested_send_date")->comment("リクエスト時に指定した送信予定時刻")->nullable(); + $table->text("requested_message")->comment("リクエスト時に指定したメッセージ本文 (base64)")->nullable(); + $table->string("requested_option")->comment("リクエスト時に指定した送信オプション")->nullable(); + $table->unsignedSmallInteger("message_count")->comment("本文を短文換算した通数カウント")->nullable(); + $table->unsignedSmallInteger("success_count")->comment("成功通数カウント")->nullable(); + $table->string("sent_carrier_code")->comment("実際に配信された携帯キャリア")->nullable(); + $table->string("sent_date")->comment("システムからキャリアにむけて送信した日時")->nullable(); + $table->string("deliv_rcpt_date")->comment("ユーザに送信された(送達)日時")->nullable(); + $table->string("sending_status")->comment("送信結果コード")->nullable(); + $table->string("result_status")->comment("キャリアから受信した送達結果コード")->nullable(); + $table->string("extime_from")->comment("送信除外時間指定がある場合の開始時間")->nullable(); + $table->string("extime_to")->comment("送信除外時間指定がある場合の終了時間")->nullable(); + + + + $helper->index(1, [ColumnName::CONTRACT_ID, ColumnName::CREATED_AT]); + + $helper->index(2, [ColumnName::SMS_SEND_ORDER_ID]); + $helper->index(3, ['request_date']); + $helper->index(4, ['address']); + }; + } +}; diff --git a/database/migrations/2023_04_19_104800_create_emails_table.php b/database/migrations/2023_04_19_104800_create_emails_table.php new file mode 100644 index 0000000..3c884b9 --- /dev/null +++ b/database/migrations/2023_04_19_104800_create_emails_table.php @@ -0,0 +1,56 @@ +schema()); + MigrationHelper::createTable('email_histories', $this->schema()); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('emails'); + Schema::dropIfExists('email_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn() + ->contractId(true) + ->userId(true) + ->receiptIssuingOrderId(true); + + $table->dateTime("confirm_datetime")->comment("確定時刻")->nullable(); + $table->dateTime('send_datetime')->comment("送信時刻")->nullable(); + $table->string('email')->comment("Email"); + $table->string('subject')->comment("件名"); + $table->text('content')->comment("本文"); + $table->string('type')->comment("Emailタイプ"); + $table->boolean('is_failed')->comment("失敗")->nullable(); + + + $helper->index(1, [ColumnName::CONTRACT_ID, ColumnName::USER_ID]); + $helper->index(2, ['email']); + $helper->index(3, ['is_failed']); + }; + } +}; diff --git a/database/migrations/2023_04_24_160000_create_receipt_issuing_ht_custom_orders_table.php b/database/migrations/2023_04_24_160000_create_receipt_issuing_ht_custom_orders_table.php new file mode 100644 index 0000000..9a815f1 --- /dev/null +++ b/database/migrations/2023_04_24_160000_create_receipt_issuing_ht_custom_orders_table.php @@ -0,0 +1,44 @@ +schema()); + MigrationHelper::createTable('receipt_issuing_h_t_parking_custom_order_histories', $this->schema()); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('receipt_issuing_h_t_parking_custom_orders'); + Schema::dropIfExists('receipt_issuing_h_t_parking_custom_order_histories'); + } + + private function schema() + { + + return function (Blueprint $table, MigrationHelper $helper) { + $helper->baseColumn() + ->receiptIssuingOrderId(); + + $table->string("customer_code")->comment("顧客コード")->nullable(); + $table->string("parking_management_code")->comment("駐車場管理コード")->nullable(); + $table->unsignedInteger("adjust_seq_no")->comment("精算連番")->nullable(); + + $helper->index(1, [ColumnName::RECEIPT_ISSUING_ORDER_ID]); + }; + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index a9f4519..fc7eb4c 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -12,11 +12,8 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // \App\Models\User::factory(10)->create(); - - // \App\Models\User::factory()->create([ - // 'name' => 'Test User', - // 'email' => 'test@example.com', - // ]); + $this->call([ + SMSProviderSeeder::class, + ]); } } diff --git a/database/seeders/SMSProviderSeeder.php b/database/seeders/SMSProviderSeeder.php new file mode 100644 index 0000000..5154d6d --- /dev/null +++ b/database/seeders/SMSProviderSeeder.php @@ -0,0 +1,29 @@ +provider_name = SMSProviderName::FOUR_S_MESSAGE; + $model->cost = 12; + + $model->save(); + + $model = new SMSProvider(); + $model->provider_name = SMSProviderName::LOG; + $model->cost = 100; + + $model->save(); + } +} diff --git a/cache_create.sh b/deploy.sh similarity index 82% rename from cache_create.sh rename to deploy.sh index 6fbcb3f..bc91294 100644 --- a/cache_create.sh +++ b/deploy.sh @@ -3,17 +3,19 @@ cd $(dirname $0) SAIL="" +COMMANDS=() if [ $# -eq 0 ]; then SAIL="" + COMMANDS+=("composer install --optimize-autoloader --no-dev") elif [ $# -eq 1 ] && [ $1 == "sail" ]; then SAIL="./sail" + NO_DEV="--no-dev" else echo "引数不正" exit 1 fi -COMMANDS=() COMMANDS+=("${SAIL} php artisan config:cache") COMMANDS+=("${SAIL} php artisan route:cache") COMMANDS+=("${SAIL} php artisan view:cache") diff --git a/ide_helper_generate.sh b/ide_helper_generate.sh new file mode 100644 index 0000000..8f2a221 --- /dev/null +++ b/ide_helper_generate.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +cd $(dirname $0) + +SAIL="" + +if [ $# -eq 0 ]; then + SAIL="" +elif [ $# -eq 1 ] && [ $1 == "sail" ]; then + SAIL="./sail" +else + echo "引数不正" + exit 1 +fi + +COMMANDS=() +# COMMANDS+=("${SAIL} php artisan ide-helper:generate") +COMMANDS+=("${SAIL} php artisan ide-helper:models -N") + +for COMMAND in "${COMMANDS[@]}"; do + echo ${COMMAND} + ${COMMAND} +done \ No newline at end of file diff --git a/lang/en/auth.php b/lang/en/auth.php new file mode 100644 index 0000000..6598e2c --- /dev/null +++ b/lang/en/auth.php @@ -0,0 +1,20 @@ + 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/lang/en/pagination.php b/lang/en/pagination.php new file mode 100644 index 0000000..d481411 --- /dev/null +++ b/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 0000000..f1223bd --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset.', + 'sent' => 'We have emailed your password reset link.', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/lang/en/validation.php b/lang/en/validation.php new file mode 100644 index 0000000..99f7c61 --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,184 @@ + 'The :attribute field must be accepted.', + 'accepted_if' => 'The :attribute field must be accepted when :other is :value.', + 'active_url' => 'The :attribute field must be a valid URL.', + 'after' => 'The :attribute field must be a date after :date.', + 'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', + 'alpha' => 'The :attribute field must only contain letters.', + 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', + 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'array' => 'The :attribute field must be an array.', + 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', + 'before' => 'The :attribute field must be a date before :date.', + 'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute field must have between :min and :max items.', + 'file' => 'The :attribute field must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute field must be between :min and :max.', + 'string' => 'The :attribute field must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute field confirmation does not match.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute field must be a valid date.', + 'date_equals' => 'The :attribute field must be a date equal to :date.', + 'date_format' => 'The :attribute field must match the format :format.', + 'decimal' => 'The :attribute field must have :decimal decimal places.', + 'declined' => 'The :attribute field must be declined.', + 'declined_if' => 'The :attribute field must be declined when :other is :value.', + 'different' => 'The :attribute field and :other must be different.', + 'digits' => 'The :attribute field must be :digits digits.', + 'digits_between' => 'The :attribute field must be between :min and :max digits.', + 'dimensions' => 'The :attribute field has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', + 'email' => 'The :attribute field must be a valid email address.', + 'ends_with' => 'The :attribute field must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute field must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute field must have more than :value items.', + 'file' => 'The :attribute field must be greater than :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than :value.', + 'string' => 'The :attribute field must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute field must have :value items or more.', + 'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than or equal to :value.', + 'string' => 'The :attribute field must be greater than or equal to :value characters.', + ], + 'image' => 'The :attribute field must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field must exist in :other.', + 'integer' => 'The :attribute field must be an integer.', + 'ip' => 'The :attribute field must be a valid IP address.', + 'ipv4' => 'The :attribute field must be a valid IPv4 address.', + 'ipv6' => 'The :attribute field must be a valid IPv6 address.', + 'json' => 'The :attribute field must be a valid JSON string.', + 'lowercase' => 'The :attribute field must be lowercase.', + 'lt' => [ + 'array' => 'The :attribute field must have less than :value items.', + 'file' => 'The :attribute field must be less than :value kilobytes.', + 'numeric' => 'The :attribute field must be less than :value.', + 'string' => 'The :attribute field must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute field must not have more than :value items.', + 'file' => 'The :attribute field must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be less than or equal to :value.', + 'string' => 'The :attribute field must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute field must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute field must not have more than :max items.', + 'file' => 'The :attribute field must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute field must not be greater than :max.', + 'string' => 'The :attribute field must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute field must not have more than :max digits.', + 'mimes' => 'The :attribute field must be a file of type: :values.', + 'mimetypes' => 'The :attribute field must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute field must have at least :min items.', + 'file' => 'The :attribute field must be at least :min kilobytes.', + 'numeric' => 'The :attribute field must be at least :min.', + 'string' => 'The :attribute field must be at least :min characters.', + ], + 'min_digits' => 'The :attribute field must have at least :min digits.', + 'missing' => 'The :attribute field must be missing.', + 'missing_if' => 'The :attribute field must be missing when :other is :value.', + 'missing_unless' => 'The :attribute field must be missing unless :other is :value.', + 'missing_with' => 'The :attribute field must be missing when :values is present.', + 'missing_with_all' => 'The :attribute field must be missing when :values are present.', + 'multiple_of' => 'The :attribute field must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute field format is invalid.', + 'numeric' => 'The :attribute field must be a number.', + 'password' => [ + 'letters' => 'The :attribute field must contain at least one letter.', + 'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute field must contain at least one number.', + 'symbols' => 'The :attribute field must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], + 'present' => 'The :attribute field must be present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute field format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute field must match :other.', + 'size' => [ + 'array' => 'The :attribute field must contain :size items.', + 'file' => 'The :attribute field must be :size kilobytes.', + 'numeric' => 'The :attribute field must be :size.', + 'string' => 'The :attribute field must be :size characters.', + ], + 'starts_with' => 'The :attribute field must start with one of the following: :values.', + 'string' => 'The :attribute field must be a string.', + 'timezone' => 'The :attribute field must be a valid timezone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'uppercase' => 'The :attribute field must be uppercase.', + 'url' => 'The :attribute field must be a valid URL.', + 'ulid' => 'The :attribute field must be a valid ULID.', + 'uuid' => 'The :attribute field must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/lang/ja/auth.php b/lang/ja/auth.php new file mode 100644 index 0000000..6598e2c --- /dev/null +++ b/lang/ja/auth.php @@ -0,0 +1,20 @@ + 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/lang/ja/pagination.php b/lang/ja/pagination.php new file mode 100644 index 0000000..d481411 --- /dev/null +++ b/lang/ja/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/lang/ja/passwords.php b/lang/ja/passwords.php new file mode 100644 index 0000000..f1223bd --- /dev/null +++ b/lang/ja/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset.', + 'sent' => 'We have emailed your password reset link.', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/lang/ja/validation.php b/lang/ja/validation.php new file mode 100644 index 0000000..9ba9f01 --- /dev/null +++ b/lang/ja/validation.php @@ -0,0 +1,205 @@ + 'The :attribute field must be accepted.', + 'accepted_if' => 'The :attribute field must be accepted when :other is :value.', + 'active_url' => 'The :attribute field must be a valid URL.', + 'after' => 'The :attribute field must be a date after :date.', + 'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', + 'alpha' => 'The :attribute field must only contain letters.', + 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', + 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'array' => 'The :attribute field must be an array.', + 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', + 'before' => 'The :attribute field must be a date before :date.', + 'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute field must have between :min and :max items.', + 'file' => 'The :attribute field must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute field must be between :min and :max.', + 'string' => 'The :attribute field must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute field confirmation does not match.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute field must be a valid date.', + 'date_equals' => 'The :attribute field must be a date equal to :date.', + 'date_format' => 'The :attribute field must match the format :format.', + 'decimal' => 'The :attribute field must have :decimal decimal places.', + 'declined' => 'The :attribute field must be declined.', + 'declined_if' => 'The :attribute field must be declined when :other is :value.', + 'different' => 'The :attribute field and :other must be different.', + 'digits' => 'The :attribute field must be :digits digits.', + 'digits_between' => 'The :attribute field must be between :min and :max digits.', + 'dimensions' => 'The :attribute field has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', + 'email' => 'The :attribute field must be a valid email address.', + 'ends_with' => 'The :attribute field must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute field must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute field must have more than :value items.', + 'file' => 'The :attribute field must be greater than :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than :value.', + 'string' => 'The :attribute field must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute field must have :value items or more.', + 'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than or equal to :value.', + 'string' => 'The :attribute field must be greater than or equal to :value characters.', + ], + 'image' => 'The :attribute field must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field must exist in :other.', + 'integer' => 'The :attribute field must be an integer.', + 'ip' => 'The :attribute field must be a valid IP address.', + 'ipv4' => 'The :attribute field must be a valid IPv4 address.', + 'ipv6' => 'The :attribute field must be a valid IPv6 address.', + 'json' => 'The :attribute field must be a valid JSON string.', + 'lowercase' => 'The :attribute field must be lowercase.', + 'lt' => [ + 'array' => 'The :attribute field must have less than :value items.', + 'file' => 'The :attribute field must be less than :value kilobytes.', + 'numeric' => 'The :attribute field must be less than :value.', + 'string' => 'The :attribute field must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute field must not have more than :value items.', + 'file' => 'The :attribute field must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be less than or equal to :value.', + 'string' => 'The :attribute field must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute field must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute field must not have more than :max items.', + 'file' => 'The :attribute field must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute field must not be greater than :max.', + 'string' => 'The :attribute field must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute field must not have more than :max digits.', + 'mimes' => 'The :attribute field must be a file of type: :values.', + 'mimetypes' => 'The :attribute field must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute field must have at least :min items.', + 'file' => 'The :attribute field must be at least :min kilobytes.', + 'numeric' => 'The :attribute field must be at least :min.', + 'string' => 'The :attribute field must be at least :min characters.', + ], + 'min_digits' => 'The :attribute field must have at least :min digits.', + 'missing' => 'The :attribute field must be missing.', + 'missing_if' => 'The :attribute field must be missing when :other is :value.', + 'missing_unless' => 'The :attribute field must be missing unless :other is :value.', + 'missing_with' => 'The :attribute field must be missing when :values is present.', + 'missing_with_all' => 'The :attribute field must be missing when :values are present.', + 'multiple_of' => 'The :attribute field must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute field format is invalid.', + 'numeric' => 'The :attribute field must be a number.', + 'password' => [ + 'letters' => 'The :attribute field must contain at least one letter.', + 'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute field must contain at least one number.', + 'symbols' => 'The :attribute field must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], + 'present' => 'The :attribute field must be present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute field format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute field must match :other.', + 'size' => [ + 'array' => 'The :attribute field must contain :size items.', + 'file' => 'The :attribute field must be :size kilobytes.', + 'numeric' => 'The :attribute field must be :size.', + 'string' => 'The :attribute field must be :size characters.', + ], + 'starts_with' => 'The :attribute field must start with one of the following: :values.', + 'string' => 'The :attribute field must be a string.', + 'timezone' => 'The :attribute field must be a valid timezone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'uppercase' => 'The :attribute field must be uppercase.', + 'url' => 'The :attribute field must be a valid URL.', + 'ulid' => 'The :attribute field must be a valid ULID.', + 'uuid' => 'The :attribute field must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +], [ + 'after' => '日付の前後関係が正しくありません', + 'after_or_equal' => '日付の前後関係が正しくありません', + 'before_or_equal' => '日付の前後関係が正しくありません', + 'between' => [ + 'string' => ':min から :max 文字入力してください', + ], + 'date' => '日付を入力してください', + 'email' => 'Emailの形式が正しくありません', + 'enum' => '正しい項目を選択してください', + 'exists' => '存在しません', + 'max' => [ + 'string' => ':max 文字まで入力できます', + ], + 'numeric' => '数値を入力してください', + 'required' => '必須項目です', + 'size' => [ + 'string' => ':size 桁で入力してください.', + ], + 'string' => '文字を入力してください', + 'unique' => 'すでに使われています', +]); diff --git a/resources/views/mails/components/ask_confirmation.blade.php b/resources/views/mails/components/ask_confirmation.blade.php new file mode 100644 index 0000000..eb2e4ab --- /dev/null +++ b/resources/views/mails/components/ask_confirmation.blade.php @@ -0,0 +1,14 @@ +下記内容にてお問合せを受付ました。 +返信のメールがあるまでしばらくお待ちください。 + +件名: {{ $askSubject }} +@if($parkName) +駐車場名 : {{ $parkName }} +@endif + +■問い合わせ内容 +{{ $askContents }} + +■問い合わせ者情報 +氏名: {{ $userName }} +email: {{ $userEmail }} \ No newline at end of file diff --git a/resources/views/mails/components/season_ticket_contract/how_to_register_ic_card.blade.php b/resources/views/mails/components/season_ticket_contract/how_to_register_ic_card.blade.php new file mode 100644 index 0000000..08a14a3 --- /dev/null +++ b/resources/views/mails/components/season_ticket_contract/how_to_register_ic_card.blade.php @@ -0,0 +1,7 @@ + ①駐車場に設置された定期更新機で利用したい交通系ICカード(ICOCA,SUICAなど)をタッチします。 + ②画面が切り替わりますので、上記定期券番号と確認コードを入力して確定ボタンを押してください。 +  または、承認画面で認証用QRコードをかざしても定期券登録が完了します。 +@isset($my_qr_code_url) + 認証用QRコードは以下(初回アクセスの場合はパスワードを設定してログインしてください) + {{ $my_qr_code_url }} +@endisset \ No newline at end of file diff --git a/resources/views/mails/components/send_only.blade.php b/resources/views/mails/components/send_only.blade.php new file mode 100644 index 0000000..46c35fc --- /dev/null +++ b/resources/views/mails/components/send_only.blade.php @@ -0,0 +1 @@ +※このメールをお送りしているアドレスは、送信専用となっており、返信いただいてもご回答いたしかねます。 \ No newline at end of file diff --git a/resources/views/mails/free_text.blade.php b/resources/views/mails/free_text.blade.php new file mode 100644 index 0000000..3b153ec --- /dev/null +++ b/resources/views/mails/free_text.blade.php @@ -0,0 +1 @@ +{{ $contents }} \ No newline at end of file diff --git a/resources/views/mails/test.blade.php b/resources/views/mails/test.blade.php new file mode 100644 index 0000000..5b697b9 --- /dev/null +++ b/resources/views/mails/test.blade.php @@ -0,0 +1 @@ +テストメールです \ No newline at end of file diff --git a/resources/views/sms/announce_receipt_issusing_order_form.blade.php b/resources/views/sms/announce_receipt_issusing_order_form.blade.php new file mode 100644 index 0000000..54be027 --- /dev/null +++ b/resources/views/sms/announce_receipt_issusing_order_form.blade.php @@ -0,0 +1 @@ +領収証発行はこちらで申込ください。 {{ $url }} \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 837f1ab..cc31243 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,7 +14,11 @@ use App\Util\RouteHelper; */ -// ルーティングで適合しない場合はフロント側のRoutingにゆだねる Route::get('pdf', 'App\Http\Controllers\PDFController@index'); +RouteHelper::post('/receiptIssuingOrder/create', App\Http\Controllers\Web\ReceiptIssuingOrder\CreateController::class); + + + +// ルーティングで適合しない場合はフロント側のRoutingにゆだねる RouteHelper::get('/{any?}', App\Http\Controllers\Web\IndexController::class)->where('any', '.*'); diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php deleted file mode 100644 index 8364a84..0000000 --- a/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/tests/Feature/HttpTest.php b/tests/Feature/HttpTest.php new file mode 100644 index 0000000..14178ca --- /dev/null +++ b/tests/Feature/HttpTest.php @@ -0,0 +1,59 @@ +seed(SMSProviderSeeder::class); + + /** + * @var Authenticatable + */ + $user = User::factory()->create(); + + $response = $this->actingAs($user) + ->postJson('/receiptIssuingOrder/create', [ + ReceiptIssuingOrder::COL_NAME_RECEIPT_AMOUNT => 100, + ReceiptIssuingOrder::COL_NAME_SMS_PHONE_NUMBER => "00011112222", + ]); + + + // $response->dd(); + $this->assertHttpResponseSuccess($response); + + $smsOrder = SMSSendOrder::first(); + $this->assertNotNull($smsOrder); + $this->assertTrue($smsOrder->done); + + $order = ReceiptIssuingOrder::first(); + + $this->assertNotNull($order); + $this->assertEquals(100, $order->receipt_amount); + $this->assertEquals("00011112222", $order->sms_phone_number); + } +} diff --git a/tests/Feature/ModelTest.php b/tests/Feature/ModelTest.php new file mode 100644 index 0000000..139a3d2 --- /dev/null +++ b/tests/Feature/ModelTest.php @@ -0,0 +1,68 @@ +create(); + + $receipt = new ReceiptIssuingOrder(); + $receipt->setContract($contract)->save(); + + $provider = SMSProvider::factory()->create(); + + $order = SMSSendOrder::newModelInstance()->setContract($contract) + ->setReceiptIssuingOrder($receipt) + ->setSMSProvider($provider); + + $order->purpose = SMSSendPurpose::SEND_RECEIPT_ISSUING_ORDER_FORM; + + $order->save(); + + + $ret = SMSSendOrder::first(); + + $this->assertNotNull($ret); + + $this->assertEquals(SMSSendPurpose::SEND_RECEIPT_ISSUING_ORDER_FORM, $ret->purpose); + } + + public function test_factory() + { + + $contract = Contract::factory()->create(); + $user = User::factory()->for($contract)->create(); + + + if ($user instanceof User && $contract instanceof Contract) { + $this->assertNotNull($user->contract_id); + + $this->assertEquals($contract->id, $user->contract_id); + } else { + $this->assertInstanceOf(Contract::class, $contract); + $this->assertInstanceOf(User::class, $user); + } + } +} diff --git a/tests/Feature/SMSTest.php b/tests/Feature/SMSTest.php new file mode 100644 index 0000000..d1f8526 --- /dev/null +++ b/tests/Feature/SMSTest.php @@ -0,0 +1,81 @@ +save(); + + $receiptIssuingOurder = new ReceiptIssuingOrder(); + $receiptIssuingOurder->contract_id = $contract->id; + $receiptIssuingOurder->status = "NEW"; + + $receiptIssuingOurder->sumamry_key1 = "京都"; + $receiptIssuingOurder->sumamry_key2 = "駅前駐車場"; + $receiptIssuingOurder->sms_phone_number = "09093604589"; + $receiptIssuingOurder->save(); + + $manager = app(SMSManager::class); + $this->assertInstanceOf(FourSMessageManager::class, $manager); + + if ($manager instanceof FourSMessageManager) { + + try { + + + $ret = $manager->setReceiptIssuingOrder($receiptIssuingOurder) + ->setMessage("こん\r\nにちは\r\nhttps://www.yahoo.co.jp/") + ->send(); + + $this->assertTrue($ret); + + return; + + $smsOrder = $manager->getOrder(); + + sleep(10); + + + $ret = $manager::cancel($smsOrder); + $this->assertTrue($ret); + + sleep(10); + + $ret = $manager::poll($smsOrder); + $this->assertTrue($ret); + + + $comm = SMSProviderFourSMessageCommunication::whereSmsSendOrderId($smsOrder->id) + ->first(); + $this->assertNotNull($comm); + } catch (Exception $e) { + + logger($e->getMessage()); + throw $e; + } + } + } +} diff --git a/tests/Feature/TestCaseEx.php b/tests/Feature/TestCaseEx.php new file mode 100644 index 0000000..d30d95e --- /dev/null +++ b/tests/Feature/TestCaseEx.php @@ -0,0 +1,16 @@ +assertEquals($response['result'], HTTPResultCode::SECCESS->value); + } +} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 5773b0c..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,16 +0,0 @@ -assertTrue(true); - } -}