Browse Source

全体的に整備

develop
sosuke.iwabuchi 2 years ago
parent
commit
4194261236
100 changed files with 4561 additions and 2 deletions
  1. +6
    -0
      .env.example
  2. +64
    -0
      .env.testing
  3. +40
    -0
      app/Codes/FourSMessage/APIErrorCode.php
  4. +14
    -0
      app/Codes/FourSMessage/RequestStatus.php
  5. +31
    -0
      app/Codes/FourSMessage/ResultStatus.php
  6. +13
    -0
      app/Codes/FourSMessage/SendingStatus.php
  7. +11
    -0
      app/Codes/HTTPResultCode.php
  8. +9
    -0
      app/Codes/QueueName.php
  9. +27
    -0
      app/Codes/ReceiptIssuingOrderStatus.php
  10. +9
    -0
      app/Codes/SMSProviderName.php
  11. +8
    -0
      app/Codes/SMSSendPurpose.php
  12. +14
    -0
      app/Codes/UserRole.php
  13. +105
    -0
      app/Console/Commands/BaseCommand.php
  14. +60
    -0
      app/Console/Commands/HeartBeat.php
  15. +97
    -0
      app/Console/Commands/PollSMSSendOrder.php
  16. +210
    -0
      app/Console/Commands/RouteListCsv.php
  17. +61
    -0
      app/Console/Commands/TestMail.php
  18. +4
    -2
      app/Console/Kernel.php
  19. +10
    -0
      app/Console/Schedules/BaseSchedule.php
  20. +22
    -0
      app/Console/Schedules/HeartBeat.php
  21. +19
    -0
      app/Console/Schedules/MailSend.php
  22. +19
    -0
      app/Console/Schedules/SMSSend.php
  23. +31
    -0
      app/Events/Mail/ConfirmEvent.php
  24. +8
    -0
      app/Events/Model/CreatedEvent.php
  25. +8
    -0
      app/Events/Model/DeletedEvent.php
  26. +8
    -0
      app/Events/Model/DeletingEvent.php
  27. +36
    -0
      app/Events/Model/ModelChangeEvent.php
  28. +8
    -0
      app/Events/Model/UpdatingEvent.php
  29. +25
    -0
      app/Events/SMS/ConfirmEvent.php
  30. +9
    -0
      app/Exceptions/AppCommonException.php
  31. +9
    -0
      app/Exceptions/GeneralErrorMessageException.php
  32. +13
    -0
      app/Features/LoginUser.php
  33. +61
    -0
      app/Http/Controllers/Web/Auth/LoginController.php
  34. +20
    -0
      app/Http/Controllers/Web/Auth/LoginParam.php
  35. +9
    -0
      app/Http/Controllers/Web/BaseParam.php
  36. +48
    -0
      app/Http/Controllers/Web/ReceiptIssuingOrder/CreateController.php
  37. +43
    -0
      app/Http/Controllers/Web/ReceiptIssuingOrder/CreateParam.php
  38. +363
    -0
      app/Http/Controllers/Web/WebController.php
  39. +40
    -0
      app/Jobs/Mail/SimpleMail.php
  40. +49
    -0
      app/Jobs/SMS/SendSMS.php
  41. +36
    -0
      app/Listeners/Mail/MailSendJobRegister.php
  42. +15
    -0
      app/Listeners/Model/CreatedListener.php
  43. +15
    -0
      app/Listeners/Model/DeletedListener.php
  44. +15
    -0
      app/Listeners/Model/DeletingListener.php
  45. +59
    -0
      app/Listeners/Model/ModelListener.php
  46. +15
    -0
      app/Listeners/Model/UpdatingListener.php
  47. +41
    -0
      app/Listeners/SMS/ConfirmListener.php
  48. +101
    -0
      app/Logic/EmailManager.php
  49. +79
    -0
      app/Logic/ReceiptIssuingOrder/CreateManager.php
  50. +54
    -0
      app/Logic/ReceiptIssuingOrder/PDFDownLoadManager.php
  51. +73
    -0
      app/Logic/ReceiptIssuingOrder/ReceiptIssuingOrderManager.php
  52. +66
    -0
      app/Logic/ReceiptIssuingOrder/UpdateManager.php
  53. +329
    -0
      app/Logic/SMS/FourSMessageManager.php
  54. +91
    -0
      app/Logic/SMS/LogManager.php
  55. +21
    -0
      app/Logic/SMS/SMSManager.php
  56. +15
    -0
      app/Mail/Admins/Admin.php
  57. +92
    -0
      app/Mail/Admins/Ask.php
  58. +177
    -0
      app/Mail/BaseMailer.php
  59. +70
    -0
      app/Mail/Common/AskConfirmation.php
  60. +27
    -0
      app/Mail/Guests/AskConfirmation.php
  61. +63
    -0
      app/Mail/Guests/ChangeEmailStart.php
  62. +65
    -0
      app/Mail/Guests/EmailVerify.php
  63. +15
    -0
      app/Mail/Guests/Guest.php
  64. +27
    -0
      app/Mail/Members/AskConfirmation.php
  65. +42
    -0
      app/Mail/Members/AutoCancelSeasonTicketContract.php
  66. +37
    -0
      app/Mail/Members/Bulk.php
  67. +17
    -0
      app/Mail/Members/Member.php
  68. +42
    -0
      app/Mail/Members/ResetIDm.php
  69. +61
    -0
      app/Mail/Members/ResetPasswordStart.php
  70. +45
    -0
      app/Mail/Members/SeasonTicketContractExpireRemind.php
  71. +111
    -0
      app/Mail/Members/Subscription/Approve.php
  72. +45
    -0
      app/Mail/Members/Subscription/Cancel.php
  73. +70
    -0
      app/Mail/Members/Subscription/Entry.php
  74. +47
    -0
      app/Mail/Members/Subscription/Hold.php
  75. +47
    -0
      app/Mail/Members/Subscription/Reject.php
  76. +48
    -0
      app/Mail/Members/Subscription/Returned.php
  77. +76
    -0
      app/Mail/Members/UserRegisterComplete.php
  78. +43
    -0
      app/Mail/Sender.php
  79. +21
    -0
      app/Mail/Test.php
  80. +37
    -0
      app/Mail/TextMail.php
  81. +22
    -0
      app/Models/AppModel.php
  82. +69
    -0
      app/Models/BaseModel.php
  83. +22
    -0
      app/Models/ColumnName.php
  84. +32
    -0
      app/Models/Contract.php
  85. +12
    -0
      app/Models/ContractHistory.php
  86. +12
    -0
      app/Models/Email.php
  87. +12
    -0
      app/Models/EmailHistory.php
  88. +76
    -0
      app/Models/Ex/LoginUser.php
  89. +30
    -0
      app/Models/Feature/ContractFeature.php
  90. +31
    -0
      app/Models/Feature/IModelFeature.php
  91. +30
    -0
      app/Models/Feature/ReceiptIssuingOrderFeature.php
  92. +30
    -0
      app/Models/Feature/SMSProviderFeature.php
  93. +29
    -0
      app/Models/Feature/SMSSendOrderFeature.php
  94. +39
    -0
      app/Models/HistoryModel.php
  95. +15
    -0
      app/Models/ReceiptIssuingHTParkingCustomOrder.php
  96. +12
    -0
      app/Models/ReceiptIssuingHTParkingCustomOrderHistory.php
  97. +47
    -0
      app/Models/ReceiptIssuingOrder.php
  98. +12
    -0
      app/Models/ReceiptIssuingOrderHistory.php
  99. +15
    -0
      app/Models/SMSProvider.php
  100. +23
    -0
      app/Models/SMSProviderFourSMessageCommunication.php

+ 6
- 0
.env.example View File

@@ -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

+ 64
- 0
.env.testing View File

@@ -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

+ 40
- 0
app/Codes/FourSMessage/APIErrorCode.php View File

@@ -0,0 +1,40 @@
<?php

namespace App\Codes\FourSMessage;

enum APIErrorCode: string
{
case CODE_1001 = '1001'; // 認証失敗。
case CODE_1002 = '1002'; // 認証されたユーザーにはこの操作を行う権限が無い。
case CODE_2002 = '2002'; // 送信実行予定時刻範囲エラー。(現在〜31 日後の範囲外)
case CODE_2003 = '2003'; // 日付範囲が 1 ヶ月を超えている。
case CODE_2004 = '2004'; // 指定された SMS 送信要求はキャンセルできない。(送信済み)
case CODE_3002 = '3002'; // キャリアコードが指定されていない。
case CODE_3003 = '3003'; // キャリアコードの値が不正。
case CODE_3004 = '3004'; // 送信先電話番号が指定されていない。
case CODE_3005 = '3005'; // 送信先電話番号の形式が不正。
case CODE_3006 = '3006'; // メッセージが指定されていない。
case CODE_3007 = '3007'; // メッセージのデコードエラー。
case CODE_3011 = '3011'; // メッセージに「¦」文字が含まれている。
case CODE_3012 = '3012'; // 送信実行予定時刻の形式が不正。
case CODE_3013 = '3013'; // 範囲指定の日付時刻が指定されていない。(開始時刻)
case CODE_3014 = '3014'; // 範囲指定の日付時刻が指定されていない。(終了時刻)
case CODE_3015 = '3015'; // 範囲指定の日付形式が不正。(開始時刻)
case CODE_3016 = '3016'; // 範囲指定の日付形式が不正。(終了時刻)
case CODE_3017 = '3017'; // 日付範囲が不正。
case CODE_3018 = '3018'; // SMS 送信要求ID が指定されていない。
case CODE_3019 = '3019'; // 指定された SMS 送信要求ID が不正。
case CODE_3037 = '3037'; // パスワードが指定されていない。
case CODE_3038 = '3038'; // 指定されたパスワードが不正。(base64 エンコード)
case CODE_3039 = '3039'; // 指定されたパスワードが不正。(使用文字)
case CODE_3040 = '3040'; // 指定されたパスワードが不正。(セキュリティー要件)
case CODE_3055 = '3055'; // 説明文字列を base64 でデコードできない。
case CODE_3067 = '3067'; // 送信オプションの形式が不正
case CODE_3068 = '3068'; // コールバックフラグの形式が不正
case CODE_3069 = '3069'; // コールバック URL が指定されていない
case CODE_3070 = '3070'; // コールバック URL の形式が不正
case CODE_3073 = '3073'; // 送信除外時間extime_from の内容が正しくない
case CODE_3074 = '3074'; // 送信除外時間extime_to の内容が正しくない
case CODE_9001 = '9001'; // 各種エラー
case CODE_1000 = '1000'; //1 送信要求時の各種エラー (エラー内容はレスポンス参照)
}

+ 14
- 0
app/Codes/FourSMessage/RequestStatus.php View File

@@ -0,0 +1,14 @@
<?php

namespace App\Codes\FourSMessage;

/**
* 受付状態
*/
enum RequestStatus: string
{
case NONE = '';
case DONE = '00'; // 受付済
case CANCELED = '00'; // 受付取消
case FAILED = '00'; // 受付失敗
}

+ 31
- 0
app/Codes/FourSMessage/ResultStatus.php View File

@@ -0,0 +1,31 @@
<?php

namespace App\Codes\FourSMessage;

/**
* 送達状態
*/
enum ResultStatus: string
{
case NONE = '';
case SUCCESS_00 = '00'; // 正常
case OUT_OF_SERVIFCE_21 = '21'; // 圏外等
case NETWORK_ERROR_22 = '22'; // キャリア側NW 障害
case MOBILE_ERROR_23 = '23'; // 移動機起因エラー(SMS 拒否設定等)


case OUT_OF_SERVIFCE_31 = '31'; // 圏外・電源オフ
case MOBILE_ERROR_32 = '32'; // 移動機起因エラー
case OTHER_ERROR_33 = '33'; // その他エラー
case CARRIER_ERROR_33 = '33'; // キャリア側エラー
case NOT_CARRIERS_USER_34 = '34'; // 自社キャリアのユーザではない
case DONE_40 = '40'; // 送達済

case DELIVERD = 'DELIVERD'; // 正常
case FAILED = 'FAILED'; // 送信エラー
case EXPIRED = 'EXPIRED'; // 圏外等
case DELETED = 'DELETED'; // 圏外等
case UNDELIV = 'UNDELIV'; // キャリア側NW 障害
case REJECTD = 'REJECTD'; // 移動機起因エラー(SMS 拒否設定等)
case UNKNOWN = 'UNKNOWN'; // 想定外のエラー
}

+ 13
- 0
app/Codes/FourSMessage/SendingStatus.php View File

@@ -0,0 +1,13 @@
<?php

namespace App\Codes\FourSMessage;

/**
* 送信状態
*/
enum SendingStatus: string
{
case NONE = '';
case SENDING = '10'; // 送信中
case DONE = '20'; // 送信済
}

+ 11
- 0
app/Codes/HTTPResultCode.php View File

@@ -0,0 +1,11 @@
<?php

namespace App\Codes;

enum HTTPResultCode: int
{
case SECCESS = 0;
case FAILED = 1;
case UNAUTHORIZED = 2;
case EXCLUSIVE_ERROR = 3;
}

+ 9
- 0
app/Codes/QueueName.php View File

@@ -0,0 +1,9 @@
<?php

namespace App\Codes;

enum QueueName: string
{
case EMAIL = 'email';
case SMS = 'sms';
}

+ 27
- 0
app/Codes/ReceiptIssuingOrderStatus.php View File

@@ -0,0 +1,27 @@
<?php

namespace App\Codes;

/**
* 領収証発行依頼ステータス
*/
enum ReceiptIssuingOrderStatus: int
{
case NONE = 0;
case CREATED = 100; // 新規作成
case SMS_SENDING = 200; // SMS送信中
case SMS_RECEIVED = 300; // SMS送信完了
case SMS_OPENED = 400; // SMS開封

// 郵送関連
case MAIL_REQUEST = 500; // 郵送依頼中
case PREPARING_FOR_MAIL = 510; // 郵送準備中
case MAIL_DONE = 520; // 郵送完了

// Email送信関連
case EMAIL_SENDING = 600; // Email送信中
case EMAIL_DONE = 610; // Email送信完了

// PDFダウンロード
case DOWNLOAD_DONE = 700; // ダウンロード完了
}

+ 9
- 0
app/Codes/SMSProviderName.php View File

@@ -0,0 +1,9 @@
<?php

namespace App\Codes;

enum SMSProviderName: string
{
case FOUR_S_MESSAGE = 'FourSMessage';
case LOG = 'log';
}

+ 8
- 0
app/Codes/SMSSendPurpose.php View File

@@ -0,0 +1,8 @@
<?php

namespace App\Codes;

enum SMSSendPurpose: string
{
case SEND_RECEIPT_ISSUING_ORDER_FORM = 'SEND_RECEIPT_ISSUING_ORDER_FORM';
}

+ 14
- 0
app/Codes/UserRole.php View File

@@ -0,0 +1,14 @@
<?php

namespace App\Codes;

/**
* ユーザー権限
*/
enum UserRole: int
{
case NONE = 0;
case NORMAL_ADMIN = 100;
case CONTRACT_ADMIN = 200;
case SUPER_ADMIN = 900;
}

+ 105
- 0
app/Console/Commands/BaseCommand.php View File

@@ -0,0 +1,105 @@
<?php

namespace App\Console\Commands;

use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

abstract class BaseCommand extends Command
{

const RESULTCODE_SUCCESS = 0;
const RESULTCODE_WARN = 1;
const RESULTCODE_FAILED = 2;


/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}


abstract protected function service(): int;
protected bool $outputInfoForBase = true;

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$ret = 0;

$this->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 "";
}
}

+ 60
- 0
app/Console/Commands/HeartBeat.php View File

@@ -0,0 +1,60 @@
<?php

namespace App\Console\Commands;


class HeartBeat extends BaseCommand
{

const COMMAND = "heartbeat {--maintenance}";

protected bool $outputInfoForBase = false;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = self::COMMAND;

/**
* The console command description.
*
* @var string
*/
protected $description = 'ハートビート';

static public function getCommand()
{
return self::COMMAND;
}


/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function service(): int
{
if ($this->option('maintenance')) {
$isMaintenanceMode = app()->isDownForMaintenance();
if ($isMaintenanceMode) {
$this->outputWarn("down for maintenance");
}
} else {
$this->outputInfo("heart beat");
}
return self::RESULTCODE_SUCCESS;
}
}

+ 97
- 0
app/Console/Commands/PollSMSSendOrder.php View File

@@ -0,0 +1,97 @@
<?php

namespace App\Console\Commands;

use App\Events\SMS\ConfirmEvent;
use App\Exceptions\AppCommonException;
use App\Logic\SMS\SMSManager;
use App\Models\SMSSendOrder;

class TestMail extends BaseCommand
{

const COMMAND = "sms:poll";

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = self::COMMAND;

/**
* The console command description.
*
* @var string
*/
protected $description = 'SMS配信状況を取得/反映する';

static public function getCommand()
{
return self::COMMAND;
}


/**
* Create a new command instance.
*
* @return void
*/
public function __construct(private SMSManager $manager)
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function service(): int
{
$targets = $this->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 失敗");
}
}
}

+ 210
- 0
app/Console/Commands/RouteListCsv.php View File

@@ -0,0 +1,210 @@
<?php

namespace App\Console\Commands;

use App\Codes\UserRole;
use App\Http\Controllers\Web\WebController;
use Exception;
use Illuminate\Routing\Route as RoutingRoute;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use LogicException;

class RouteListCsv extends BaseCommand
{

/**
* コントローラー説明の個別定義
* 主にミドルウェア側のコントローラーに適用
*/
const CONTROLLER_DESCRIPTION = [
\Laravel\Sanctum\Http\Controllers\CsrfCookieController::class => "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" : ",";
}
}

+ 61
- 0
app/Console/Commands/TestMail.php View File

@@ -0,0 +1,61 @@
<?php

namespace App\Console\Commands;

use App\Events\Mail\ConfirmEvent;
use App\Logic\EmailManager;
use App\Mail\Test;

class TestMail extends BaseCommand
{

const COMMAND = "test:mail {email}";

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = self::COMMAND;

/**
* The console command description.
*
* @var string
*/
protected $description = 'テストメールを送信する(キュー登録)';

static public function getCommand()
{
return self::COMMAND;
}


/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function service(): int
{
$email = $this->argument('email');

$mailer = new Test();
$mailer->setEmail($email);

$manager = new EmailManager($mailer);
$manager->confirm();

return self::RESULTCODE_SUCCESS;
}
}

+ 4
- 2
app/Console/Kernel.php View File

@@ -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');
}


+ 10
- 0
app/Console/Schedules/BaseSchedule.php View File

@@ -0,0 +1,10 @@
<?php

namespace App\Console\Schedules;

use Illuminate\Console\Scheduling\Schedule;

abstract class BaseSchedule
{
abstract static public function register(Schedule $schedule);
}

+ 22
- 0
app/Console/Schedules/HeartBeat.php View File

@@ -0,0 +1,22 @@
<?php

namespace App\Console\Schedules;

use App\Console\Commands\HeartBeat as CommandsHeartBeat;
use Illuminate\Console\Scheduling\Schedule;

class HeartBeat extends BaseSchedule
{

static public function register(Schedule $schedule)
{
$schedule->command(CommandsHeartBeat::class)
->everyFiveMinutes()
->evenInMaintenanceMode()
->description("ハートビート");
$schedule->command(CommandsHeartBeat::class, ['--maintenance'])
->everyMinute()
->evenInMaintenanceMode()
->description("メンテナンスモード確認");
}
}

+ 19
- 0
app/Console/Schedules/MailSend.php View File

@@ -0,0 +1,19 @@
<?php

namespace App\Console\Schedules;

use App\Codes\QueueName;
use Illuminate\Console\Scheduling\Schedule;

class MailSend extends BaseSchedule
{

static public function register(Schedule $schedule)
{
$schedule->command(
sprintf('queue:work --queue=%s --max-time=55 ', QueueName::EMAIL->value)
)
->everyMinute()
->description("メールキュー処理");
}
}

+ 19
- 0
app/Console/Schedules/SMSSend.php View File

@@ -0,0 +1,19 @@
<?php

namespace App\Console\Schedules;

use App\Codes\QueueName;
use Illuminate\Console\Scheduling\Schedule;

class SMSSend extends BaseSchedule
{

static public function register(Schedule $schedule)
{
$schedule->command(
sprintf('queue:work --queue=%s --max-time=55 ', QueueName::SMS->value)
)
->everyMinute()
->description("メールキュー処理");
}
}

+ 31
- 0
app/Events/Mail/ConfirmEvent.php View File

@@ -0,0 +1,31 @@
<?php

namespace App\Events\Mail;

use App\Mail\BaseMailer;
use App\Models\Email;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ConfirmEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public Email $email;

/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Email|BaseMailer $email)
{
if ($email instanceof Email) {
$this->email = $email;
} else {
$this->email = $email->makeModel();
$this->email->save();
}
}
}

+ 8
- 0
app/Events/Model/CreatedEvent.php View File

@@ -0,0 +1,8 @@
<?php

namespace App\Events\Model;


class CreatedEvent extends ModelChangeEvent
{
}

+ 8
- 0
app/Events/Model/DeletedEvent.php View File

@@ -0,0 +1,8 @@
<?php

namespace App\Events\Model;


class DeletedEvent extends ModelChangeEvent
{
}

+ 8
- 0
app/Events/Model/DeletingEvent.php View File

@@ -0,0 +1,8 @@
<?php

namespace App\Events\Model;


class DeletingEvent extends ModelChangeEvent
{
}

+ 36
- 0
app/Events/Model/ModelChangeEvent.php View File

@@ -0,0 +1,36 @@
<?php

namespace App\Events\Model;

use App\Models\Feature\IModelFeature;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

abstract class ModelChangeEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public IModelFeature $model;

/**
* Create a new event instance.
*/
public function __construct(IModelFeature $model)
{
$this->model = $model;
}

/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

+ 8
- 0
app/Events/Model/UpdatingEvent.php View File

@@ -0,0 +1,8 @@
<?php

namespace App\Events\Model;


class UpdatingEvent extends ModelChangeEvent
{
}

+ 25
- 0
app/Events/SMS/ConfirmEvent.php View File

@@ -0,0 +1,25 @@
<?php

namespace App\Events\SMS;

use App\Models\SMSSendOrder;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

/**
* 送信状態の確定イベント
*/
class ConfirmEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
* Create a new event instance.
*
* @return void
*/
public function __construct(public SMSSendOrder $order)
{
}
}

+ 9
- 0
app/Exceptions/AppCommonException.php View File

@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class AppCommonException extends Exception
{
}

+ 9
- 0
app/Exceptions/GeneralErrorMessageException.php View File

@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class GeneralErrorMessageException extends Exception
{
}

+ 13
- 0
app/Features/LoginUser.php View File

@@ -0,0 +1,13 @@
<?php

namespace App\Features;

use App\Models\Ex\LoginUser as ExLoginUser;

trait LoginUser
{
protected function loginUser(): ExLoginUser
{
return app(ExLoginUser::class);
}
}

+ 61
- 0
app/Http/Controllers/Web/Auth/LoginController.php View File

@@ -0,0 +1,61 @@
<?php

namespace App\Http\Controllers\Web\Login;

use App\Codes\UserRole;
use App\Http\Controllers\Web\WebController;
use App\Models\User;
use App\Repositories\MeRepository;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends WebController
{
public function name(): string
{
return "ログイン";
}

public function description(): string
{
return "ログインを行う";
}

use AuthenticatesUsers;

public function __construct(LoginParam $param)
{
$this->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([], '認証失敗');
}
}
}

+ 20
- 0
app/Http/Controllers/Web/Auth/LoginParam.php View File

@@ -0,0 +1,20 @@
<?php

namespace App\Http\Controllers\Web\Login;

use App\Http\Controllers\Web\BaseParam;

/**
* @property string $email
* @property string $password
*/
class LoginParam extends BaseParam
{
public function rules(): array
{
return [
'email' => $this->str(),
'password' => $this->str(),
];
}
}

+ 9
- 0
app/Http/Controllers/Web/BaseParam.php View File

@@ -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,


+ 48
- 0
app/Http/Controllers/Web/ReceiptIssuingOrder/CreateController.php View File

@@ -0,0 +1,48 @@
<?php

namespace App\Http\Controllers\Web\ReceiptIssuingOrder;

use App\Codes\UserRole;
use App\Http\Controllers\Web\IParam;
use App\Http\Controllers\Web\WebController;
use App\Logic\ReceiptIssuingOrder\CreateManager;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class CreateController extends WebController
{
public function name(): string
{
return "領収証発行依頼新規作成";
}

public function description(): string
{
return "領収証発行依頼を新規作成する";
}

public function __construct(
protected CreateParam $param,
private CreateManager $manager
) {
$this->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();
}
}

+ 43
- 0
app/Http/Controllers/Web/ReceiptIssuingOrder/CreateParam.php View File

@@ -0,0 +1,43 @@
<?php

namespace App\Http\Controllers\Web\ReceiptIssuingOrder;

use App\Http\Controllers\Web\BaseParam;
use App\Models\ReceiptIssuingOrder;
use App\Rules\PhoneNumber;
use Carbon\Carbon;

/**
* @property ?string $handlerId
* @property ?string $orderDatetime
* @property ?string $sumamryKey1
* @property ?string $sumamryKey2
* @property ?string $smsPhoneNumber
* @property ?string $receiptNo
* @property ?Carbon $receiptUseDatetime
* @property ?string $receiptShopName
* @property ?string $receiptIssuer
* @property ?string $receiptPurpose
* @property string $receiptAmount
* @property ?string $memo
*/

class CreateParam extends BaseParam
{
public function rules(): array
{
return [
ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY1 => $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),

];
}
}

+ 363
- 0
app/Http/Controllers/Web/WebController.php View File

@@ -0,0 +1,363 @@
<?php

namespace App\Http\Controllers\Web;

use App\Codes\HTTPResultCode as ResultCode;
use App\Codes\UserRole;
use App\Exceptions\AppCommonException;
use App\Exceptions\GeneralErrorMessageException;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use LogicException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\Exception\HttpException;

abstract class WebController extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

const COL_NAME_CREATED_AT = 'created_at';
const COL_NAME_UPDATED_AT = 'updated_at';

const COL_NAME_RESULT_CODE = 'result';
const COL_NAME_DATA = 'data';
const COL_NAME_MESSAGES = 'messages';
const COL_NAME_GENERAL_MESSAGE = 'general';
const COL_NAME_EMAIL_ID = 'email_id';
const COL_NAME_ERRORS = 'errors';

/**
* バリデートした結果を格納
*
* @var array
*/
protected $validated = [];

/**
* 画面へ返却するメールID
*
* @var integer|null
*/
private int|null $emailId = null;

/**
* 返却するメッセージ
*
* @var array|null
*/
private array|null $messages = null;

/**
* 返却するメッセージ
*
* @var string|null
*/
private string|null $generalMessage = null;

/**
* 返却するデータ
*
* @var mixed|null
*/
private $data = null;

/**
* 返却する結果コード
*
* @var ResultCode|null
*/
private ResultCode|null $resultCode = ResultCode::SECCESS;


/**
* パラメータオブジェクト
*/
abstract protected function getParam(): IParam;


/**
* コントローラーの名前
* オーバーライドされることを想定
* 主に、Routeのドキュメント作成用
*
* @return string
*/
public function name(): string
{
return "---未設定---";
}

/**
* コントローラーの説明
* オーバーライドされることを想定
* 主に、Routeのドキュメント作成用
*
* @return string
*/
public function description(): string
{
return "---未設定---";
}

/**
* オーバーライド必要
* メインロジック
*
* @param Request $request
* @return Response|JsonResponse|string
*/
protected function run(Request $request): Response|JsonResponse|BinaryFileResponse|string
{
return $this->successResponse();
}

private function getRules()
{
return $this->getParam()->rules();
}

public function entry(Request $request)
{
$this->setLogContext($request);

try {
$validator = Validator::make($request->all(), $this->getRules());
$validator->validate();
} catch (ValidationException $e) {
logger("validate error", ['errors' => $e->errors(), 'request' => $request->all(), 'path' => $request->path()]);
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(),
]);
}
}

+ 40
- 0
app/Jobs/Mail/SimpleMail.php View File

@@ -0,0 +1,40 @@
<?php

namespace App\Jobs\Mail;

use App\Codes\QueueName;
use App\Mail\Sender;
use App\Models\Email;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SimpleMail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

private string $emailId;

/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Email $email)
{
$this->emailId = $email->id;
$this->onQueue(QueueName::EMAIL->value);
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Sender::send($this->emailId);
}
}

+ 49
- 0
app/Jobs/SMS/SendSMS.php View File

@@ -0,0 +1,49 @@
<?php

namespace App\Jobs\SMS;

use App\Codes\QueueName;
use App\Logic\SMS\SMSManager;
use App\Models\SMSSendOrder;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendSMS implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;


/**
* Create a new job instance.
*
* @return void
*/
public function __construct(private SMSSendOrder $order)
{
$this->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));
}
}
}

+ 36
- 0
app/Listeners/Mail/MailSendJobRegister.php View File

@@ -0,0 +1,36 @@
<?php

namespace App\Listeners\Mail;

use App\Events\Mail\ConfirmEvent;
use App\Jobs\Mail\SimpleMail;
use App\Util\DateUtil;

class MailSendJobRegister
{

/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
}
/**
* Handle the event.
*
* @param ConfirmEvent $event
* @return void
*/
public function handle(ConfirmEvent $event)
{
$email = $event->email;
if ($email->confirm_datetime === null && $email->send_datetime === null) {

$email->confirm_datetime = DateUtil::now();
$email->save();
SimpleMail::dispatch($event->email);
}
}
}

+ 15
- 0
app/Listeners/Model/CreatedListener.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Listeners\Model;

use App\Events\Model\CreatedEvent;

class CreatedListener extends ModelListener
{
protected const ACTION = '作成';

public function handle(CreatedEvent $event): void
{
$this->handleEvent($event->model);
}
}

+ 15
- 0
app/Listeners/Model/DeletedListener.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Listeners\Model;

use App\Events\Model\DeletedEvent;

class DeletedListener extends ModelListener
{
protected const ACTION = '削除';

public function handle(DeletedEvent $event): void
{
$this->handleEvent($event->model);
}
}

+ 15
- 0
app/Listeners/Model/DeletingListener.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Listeners\Model;

use App\Events\Model\DeletingEvent;

class DeletingListener extends ModelListener
{
protected const ACTION = '削除';

public function handle(DeletingEvent $event): void
{
$this->handleEvent($event->model);
}
}

+ 59
- 0
app/Listeners/Model/ModelListener.php View File

@@ -0,0 +1,59 @@
<?php

namespace App\Listeners\Model;

use App\Models\ColumnName;
use App\Models\Feature\IModelFeature;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;

abstract class ModelListener
{

protected const ACTION = '-';

protected function handleEvent(IModelFeature $model): void
{
// 履歴作成処理
$this->createHistory($model);

// 更新者作成者設定処理
$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();
}
}
}

+ 15
- 0
app/Listeners/Model/UpdatingListener.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Listeners\Model;

use App\Events\Model\UpdatingEvent;

class UpdatingListener extends ModelListener
{
protected const ACTION = '更新';

public function handle(UpdatingEvent $event): void
{
$this->handleEvent($event->model);
}
}

+ 41
- 0
app/Listeners/SMS/ConfirmListener.php View File

@@ -0,0 +1,41 @@
<?php

namespace App\Listeners\SMS;

use App\Events\SMS\ConfirmEvent;


/**
* SMS送信依頼の確定イベントをキャッチ
* 領収証発行依頼のSMS送信完了を設定する
*/
class ConfirmListener
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}

/**
* Handle the event.
*/
public function handle(ConfirmEvent $event): void
{
$smsSendOrder = $event->order;

$receiptIssuingOrder = $smsSendOrder->receiptIssuingOrder;
if ($receiptIssuingOrder === null) {
return;
}

$success = $smsSendOrder->send_datetime !== null;

if ($success) {
$receiptIssuingOrder->sms_send_success = true;
$receiptIssuingOrder->save();
}
}
}

+ 101
- 0
app/Logic/EmailManager.php View File

@@ -0,0 +1,101 @@
<?php

namespace App\Logic;

use App\Events\Mail\ConfirmEvent;
use App\Exceptions\AppCommonException;
use App\Mail\BaseMailer;
use App\Models\Email;
use Exception;
use Illuminate\Support\Carbon;
use Validator;

class EmailManager
{

private Email $model;

private bool $canSend = false;

public function __construct(int|BaseMailer $param)
{
if (is_numeric($param)) {
$this->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;
}
}

+ 79
- 0
app/Logic/ReceiptIssuingOrder/CreateManager.php View File

@@ -0,0 +1,79 @@
<?php

namespace App\Logic\ReceiptIssuingOrder;

use App\Codes\ReceiptIssuingOrderStatus;
use App\Codes\SMSSendPurpose;
use App\Logic\SMS\SMSManager;
use App\Models\ReceiptIssuingOrder;
use Illuminate\Support\Facades\View;
use LogicException;

class CreateManager extends ReceiptIssuingOrderManager
{

public function __construct(
protected ReceiptIssuingOrder $order,
protected SMSManager $smsManager
) {
}

public function init()
{
$order = $this->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();
}
}

+ 54
- 0
app/Logic/ReceiptIssuingOrder/PDFDownLoadManager.php View File

@@ -0,0 +1,54 @@
<?php

namespace App\Logic\ReceiptIssuingOrder;

use App\Codes\ReceiptIssuingOrderStatus;
use App\Exceptions\AppCommonException;
use App\Logic\SMS\SMSManager;
use App\Models\ReceiptIssuingOrder;
use PDF;

class UpdateManager extends ReceiptIssuingOrderManager
{

public function __construct(
protected ReceiptIssuingOrder $order,
protected SMSManager $smsManager
) {
}

public function initByToken(string $token)
{
$ret = $this->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;
}
}

+ 73
- 0
app/Logic/ReceiptIssuingOrder/ReceiptIssuingOrderManager.php View File

@@ -0,0 +1,73 @@
<?php

namespace App\Logic\ReceiptIssuingOrder;

use App\Exceptions\AppCommonException;
use App\Features\LoginUser;
use App\Models\ReceiptIssuingOrder;
use App\Util\DateUtil;
use Illuminate\Support\Str;
use LogicException;

abstract class ReceiptIssuingOrderManager
{
use LoginUser;

protected bool $initialized = false;

protected function __construct(protected ReceiptIssuingOrder $order)
{
}

protected function fetch(string $receiptIssuingOrderId)
{
$order = ReceiptIssuingOrder::findOrFail($receiptIssuingOrderId);
if (!$this->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;
}
}

+ 66
- 0
app/Logic/ReceiptIssuingOrder/UpdateManager.php View File

@@ -0,0 +1,66 @@
<?php

namespace App\Logic\ReceiptIssuingOrder;

use App\Exceptions\AppCommonException;
use App\Logic\SMS\SMSManager;
use App\Models\ReceiptIssuingOrder;

class UpdateManager extends ReceiptIssuingOrderManager
{

public function __construct(
protected ReceiptIssuingOrder $order,
protected SMSManager $smsManager
) {
}

public function initByToken(string $token)
{
$ret = $this->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;
}
}

+ 329
- 0
app/Logic/SMS/FourSMessageManager.php View File

@@ -0,0 +1,329 @@
<?php

namespace App\Logic\SMS;

use App\Codes\SMSProviderName;
use App\Codes\SMSSendPurpose;
use App\Models\ReceiptIssuingOrder;
use App\Models\SMSProvider;
use App\Models\SMSProviderFourSMessageCommunication;
use App\Models\SMSSendOrder;
use App\Util\DateUtil;
use Exception;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use LogicException;

class FourSMessageManager implements SMSManager
{


private const SEND_URL = ['POST', "https://4sm.jp/api/sms_send"];
private const CANCEL_URL = ['POST', "https://4sm.jp/api/sms_cancel"];
private const RESULT_URL = ['POST', "https://4sm.jp/api/get_send_result"];

private static function getUserId(): string
{
$ret = config("logic.sms.FourSMessage.userId");
if ($ret === null) {
throw new LogicException("SMS FourSMessage UserID 未定義");
}
return $ret;
}

private static function getPassword(): string
{
$ret = config("logic.sms.FourSMessage.password");
if ($ret === null) {
throw new LogicException("SMS FourSMessage Password 未定義");
}
return $ret;
}

public static function makeSMSSendOrder(ReceiptIssuingOrder $receiptIssuingOrder, SMSSendPurpose $purpose, string $contents): SMSSendOrder
{
$order = new SMSSendOrder();

$order->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<SMSProviderFourSMessageCommunication>
*/
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;
}
}

+ 91
- 0
app/Logic/SMS/LogManager.php View File

@@ -0,0 +1,91 @@
<?php

namespace App\Logic\SMS;

use App\Codes\SMSProviderName;
use App\Codes\SMSSendPurpose;
use App\Events\SMS\ConfirmEvent;
use App\Models\ReceiptIssuingOrder;
use App\Models\SMSProvider;
use App\Models\SMSSendOrder;
use App\Util\DateUtil;

class LogManager implements SMSManager
{


public static function makeSMSSendOrder(ReceiptIssuingOrder $receiptIssuingOrder, SMSSendPurpose $purpose, string $contents): SMSSendOrder
{
$order = new SMSSendOrder();

$order->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;
}
}

+ 21
- 0
app/Logic/SMS/SMSManager.php View File

@@ -0,0 +1,21 @@
<?php

namespace App\Logic\SMS;

use App\Codes\SMSSendPurpose;
use App\Models\ReceiptIssuingOrder;
use App\Models\SMSSendOrder;

interface SMSManager
{
public static function makeSMSSendOrder(ReceiptIssuingOrder $order, SMSSendPurpose $purpose, string $contents): SMSSendOrder;

public function loadOrder(string $id): static;
public function setOrder(SMSSendOrder $order): static;

public function getOrder();

public function send(): bool;
public function poll(): bool;
public function cancel(): bool;
}

+ 15
- 0
app/Mail/Admins/Admin.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Mail\Admins;

use App\Mail\BaseMailer;

abstract class Admin extends BaseMailer
{
public function getParams(): array
{
return array_merge($this->getAdminParams(), []);
}

abstract public function getAdminParams(): array;
}

+ 92
- 0
app/Mail/Admins/Ask.php View File

@@ -0,0 +1,92 @@
<?php

namespace App\Mail\Admins;

use App\Models\Parking;
use App\Models\SeasonTicketContract;
use App\Models\SeasonTicketContractSubscription;
use App\Models\User;
use App\Models\UserDetail;
use Illuminate\Support\Carbon;

class Ask extends Admin
{

protected string $askSubject;
protected string $askContents;
protected string|null $parkName = null;
protected string|null $userName;
protected string|null $userEmail;
protected int|null $seasonTicketSeqNo = null;
protected Carbon|null $subscribeDatetime = null;


public function __construct(
array $data,
User|null $user = null,
UserDetail|null $userDetail = null,
SeasonTicketContract|null $seasonTicketContract = null,
SeasonTicketContractSubscription|null $subscription = null,
Parking|null $parking = null,
) {
$this->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(),
];
}
}

+ 177
- 0
app/Mail/BaseMailer.php View File

@@ -0,0 +1,177 @@
<?php

namespace App\Mail;

use App\Exceptions\AppCommonException;
use App\Models\Email;
use App\Models\User;
use App\Util\DateUtil;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;

abstract class BaseMailer extends Mailable
{
use Queueable, SerializesModels;

protected $configRoot = 'mail.mailers.smtp_ht.';

protected $casts = [];

protected User|null $__user = null;
protected string $__email = "";
protected string|null $__userId = null;
protected string|null $__contractId = null;
protected string|null $__receiptIssuingOrderId = null;


public function sendEmail(string $email)
{
$this->__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,
);
}
}

+ 70
- 0
app/Mail/Common/AskConfirmation.php View File

@@ -0,0 +1,70 @@
<?php

namespace App\Mail\Common;

use App\Models\Parking;
use App\Models\User;
use App\Models\UserDetail;
use Illuminate\Support\Carbon;

trait AskConfirmation
{

protected string $askSubject;
protected string $askContents;
protected string|null $parkName = null;
protected string|null $userName;
protected string|null $userEmail;
protected int|null $seasonTicketSeqNo = null;
protected Carbon|null $subscribeDatetime = null;

private bool $forMember = false;

abstract protected function setValues(\stdClass|array $data);

protected function setAskConfirmationData(
array $data,
User|null $user = null,
UserDetail|null $userDetail = null,
Parking|null $parking = null,
) {
$this->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,
];
}
}

+ 27
- 0
app/Mail/Guests/AskConfirmation.php View File

@@ -0,0 +1,27 @@
<?php

namespace App\Mail\Guests;

use App\Mail\Common\AskConfirmation as CommonAskConfirmation;
use App\Models\Parking;
use App\Models\User;
use App\Models\UserDetail;

class AskConfirmation extends Guest
{
use CommonAskConfirmation;

public function __construct(
array $data,
User|null $user = null,
UserDetail|null $userDetail = null,
Parking|null $parking = null,
) {
$this->setAskConfirmationData($data, $user, $userDetail, $parking);
}

public function getGuestParams(): array
{
return $this->getAskConfirmationParams();
}
}

+ 63
- 0
app/Mail/Guests/ChangeEmailStart.php View File

@@ -0,0 +1,63 @@
<?php

namespace App\Mail\Guests;

use App\Models\ChangeEmail;
use Exception;
use Illuminate\Support\Carbon;

class ChangeEmailStart extends Guest
{

protected string $token;
protected Carbon $expiresAt;

protected $casts = [
'expiresAt' => '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');
}
}

+ 65
- 0
app/Mail/Guests/EmailVerify.php View File

@@ -0,0 +1,65 @@
<?php

namespace App\Mail\Guests;

use App\Models\EmailVerify as ModelsEmailVerify;
use Exception;
use Illuminate\Support\Carbon;

class EmailVerify extends Guest
{
protected string $token;
protected Carbon $expiresAt;

protected $casts = [
'expiresAt' => '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("有効期限日付不正");
}
}

+ 15
- 0
app/Mail/Guests/Guest.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Mail\Guests;

use App\Mail\BaseMailer;

abstract class Guest extends BaseMailer
{
public function getParams(): array
{
return array_merge($this->getGuestParams(), []);
}

abstract public function getGuestParams(): array;
}

+ 27
- 0
app/Mail/Members/AskConfirmation.php View File

@@ -0,0 +1,27 @@
<?php

namespace App\Mail\Members;

use App\Mail\Common\AskConfirmation as CommonAskConfirmation;
use App\Models\Parking;
use App\Models\User;
use App\Models\UserDetail;

class AskConfirmation extends Member
{
use CommonAskConfirmation;

public function __construct(
array $data,
User|null $user = null,
UserDetail|null $userDetail = null,
Parking|null $parking = null,
) {
$this->setAskConfirmationData($data, $user, $userDetail, $parking);
}

public function getMemberParams(): array
{
return $this->getAskConfirmationParams();
}
}

+ 42
- 0
app/Mail/Members/AutoCancelSeasonTicketContract.php View File

@@ -0,0 +1,42 @@
<?php

namespace App\Mail\Members;

use App\Models\SeasonTicketContract;
use App\Models\Parking;

class AutoCancelSeasonTicketContract extends Member
{

protected string $parkName;
protected int $seasonTicketSeqNo;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Parking $parking, SeasonTicketContract $seasonTicketContract)
{
$this->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,
];
}
}

+ 37
- 0
app/Mail/Members/Bulk.php View File

@@ -0,0 +1,37 @@
<?php

namespace App\Mail\Members;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;

class Bulk extends Member
{
use Queueable, SerializesModels;

private string $__subject;
private string $__contents;

public function __construct(string $subject, string $contents)
{
$this->__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,
];
}
}

+ 17
- 0
app/Mail/Members/Member.php View File

@@ -0,0 +1,17 @@
<?php

namespace App\Mail\Members;

use App\Mail\BaseMailer;

abstract class Member extends BaseMailer
{
public function getParams(): array
{
return array_merge($this->getMemberParams(), [
'email' => $this->__email
]);
}

abstract public function getMemberParams(): array;
}

+ 42
- 0
app/Mail/Members/ResetIDm.php View File

@@ -0,0 +1,42 @@
<?php

namespace App\Mail\Members;

use App\Exceptions\AppCommonException;

class ResetIDm extends Member
{
private string $confirmationCode;
private int $seasonTicketSeqNo;

public function __construct()
{
$context = $this->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,
];
}
}

+ 61
- 0
app/Mail/Members/ResetPasswordStart.php View File

@@ -0,0 +1,61 @@
<?php

namespace App\Mail\Members;

use App\Models\ResetPassword;
use Illuminate\Support\Carbon;

class ResetPasswordStart extends Member
{

protected string $token;
protected Carbon $expiresAt;

protected $casts = [
'expiresAt' => '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');
}
}

+ 45
- 0
app/Mail/Members/SeasonTicketContractExpireRemind.php View File

@@ -0,0 +1,45 @@
<?php

namespace App\Mail\Members;

use Illuminate\Support\Carbon;

class SeasonTicketContractExpireRemind extends Member
{
protected string|null $parkName;
protected int $seasonTicketSeqNo;
protected Carbon $expirationEndDate;

protected $casts = [
'expirationEndDate' => '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,
];
}
}

+ 111
- 0
app/Mail/Members/Subscription/Approve.php View File

@@ -0,0 +1,111 @@
<?php

namespace App\Mail\Members\Subscription;

use App\Codes\ParkingUseTypeCode;
use App\Codes\VehicleTypeCode;
use App\Mail\Members\Member;
use App\Models\Parking;
use App\Models\SeasonTicketContract;
use App\Models\SeasonTicketContractSubscription;
use App\Models\User;
use Illuminate\Support\Carbon;
use LogicException;

class Approve extends Member
{
private string $email;
private string $parkName;
private Carbon|null $useStartRequestDate;
private VehicleTypeCode $vehicleType;
private string $contractorTypeName;
private int $seasonTicketSeqNo;
private int|null $confirmationCode;
private ParkingUseTypeCode $parkingUseType;

private string $customerCode;
private string $parkingManagementCode;


/**
* Create a new message instance.
*
* @return void
*/
public function __construct(
User $user,
SeasonTicketContractSubscription|null $subscription,
Parking $parking,
SeasonTicketContract $seasonTicketContract,
string $contractorTypeName
) {
$this->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]);
}
}

+ 45
- 0
app/Mail/Members/Subscription/Cancel.php View File

@@ -0,0 +1,45 @@
<?php

namespace App\Mail\Members\Subscription;

use App\Exceptions\AppCommonException;
use App\Mail\Members\Member;

class Cancel extends Member
{

private string $parkName;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
$context = $this->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,
];
}
}

+ 70
- 0
app/Mail/Members/Subscription/Entry.php View File

@@ -0,0 +1,70 @@
<?php

namespace App\Mail\Members\Subscription;

use App\Exceptions\AppCommonException;
use App\Mail\Members\Member;
use Illuminate\Support\Carbon;

class Entry extends Member
{

private string $email;
private string $parkName;
private Carbon|null $useStartRequestDate;
private string $memo;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
$context = $this->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日");
}
}

+ 47
- 0
app/Mail/Members/Subscription/Hold.php View File

@@ -0,0 +1,47 @@
<?php

namespace App\Mail\Members\Subscription;

use App\Mail\Members\Member;
use App\Models\SeasonTicketContractSubscriptionMessage;

class Hold extends Member
{
private string $parkName;
private string|null $message;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct(SeasonTicketContractSubscriptionMessage $message)
{
$context = $this->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,
];
}
}

+ 47
- 0
app/Mail/Members/Subscription/Reject.php View File

@@ -0,0 +1,47 @@
<?php

namespace App\Mail\Members\Subscription;

use App\Mail\Members\Member;
use App\Models\SeasonTicketContractSubscriptionMessage;

class Reject extends Member
{
private string $parkName;
private string $message;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct(SeasonTicketContractSubscriptionMessage $message)
{
$context = $this->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,
];
}
}

+ 48
- 0
app/Mail/Members/Subscription/Returned.php View File

@@ -0,0 +1,48 @@
<?php

namespace App\Mail\Members\Subscription;

use App\Mail\Members\Member;
use App\Models\SeasonTicketContractSubscriptionMessage;

class Returned extends Member
{

private string $parkName;
private string $message;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct(SeasonTicketContractSubscriptionMessage $message)
{
$context = $this->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,
];
}
}

+ 76
- 0
app/Mail/Members/UserRegisterComplete.php View File

@@ -0,0 +1,76 @@
<?php

namespace App\Mail\Members;

use App\Models\ResetPassword;
use App\Models\User;
use Illuminate\Support\Carbon;

class UserRegisterComplete extends Member
{


private string $email;

private bool $includePasswordReset = false;
private Carbon|null $expiresAt = null;
private string $token = "";


/**
* Create a new message instance.
*
* @return void
*/
public function __construct(User $model, ResetPassword|null $password = null)
{
$this->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');
}
}
}

+ 43
- 0
app/Mail/Sender.php View File

@@ -0,0 +1,43 @@
<?php

namespace App\Mail;

use App\Middlewares\Now;
use App\Models\Email;
use App\Util\DateUtil;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

class Sender
{

public static function send(string $emailId)
{
$email = Email::findOrFail($emailId);

info("メール送信", [
'id' => $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();
}
}

+ 21
- 0
app/Mail/Test.php View File

@@ -0,0 +1,21 @@
<?php

namespace App\Mail;

class Test extends BaseMailer
{
public function getTemplateName(): string
{
return 'mails.test';
}

public function getSubject(): string
{
return 'メールテスト';
}

public function getParams(): array
{
return [];
}
}

+ 37
- 0
app/Mail/TextMail.php View File

@@ -0,0 +1,37 @@
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;

class TextMail extends BaseMailer
{
use Queueable, SerializesModels;

private string $__subject;
private string $__contents;

public function __construct(string $subject, string $contents)
{
$this->__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,
];
}
}

+ 22
- 0
app/Models/AppModel.php View File

@@ -0,0 +1,22 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\SoftDeletes;

abstract class AppModel extends BaseModel
{
use SoftDeletes, HasUuids;

public function getHistory(): ?HistoryModel
{
$historyName = static::class . 'History';
return new $historyName;
}

public function getChangeLogMessage($before, $after): ?string
{
return null;
}
}

+ 69
- 0
app/Models/BaseModel.php View File

@@ -0,0 +1,69 @@
<?php

namespace App\Models;

use App\Events\Model\CreatedEvent;
use App\Events\Model\DeletedEvent;
use App\Events\Model\UpdatingEvent;
use App\Models\Feature\IModelFeature;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;

abstract class BaseModel extends Model implements IModelFeature
{
use HasFactory;

const COL_NAME_ID = ColumnName::ID;
const COL_NAME_CREATED_BY = ColumnName::CREATED_BY;
const COL_NAME_UPDATED_BY = ColumnName::UPDATED_BY;
const COL_NAME_CREATED_AT = ColumnName::CREATED_AT;
const COL_NAME_UPDATED_AT = ColumnName::UPDATED_AT;
const COL_NAME_DELETED_AT = ColumnName::DELETED_AT;

protected $guarded = [
self::COL_NAME_ID,
self::COL_NAME_CREATED_BY,
self::COL_NAME_UPDATED_BY,
self::COL_NAME_CREATED_AT,
self::COL_NAME_UPDATED_AT,
self::COL_NAME_DELETED_AT,
];

public static function getBuilder(string $name = 'main'): Builder
{
return DB::table(static::getTableName(), $name);
}

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;
}

protected $dispatchesEvents = [
'created' => CreatedEvent::class,
'updating' => UpdatingEvent::class,
'deleted' => DeletedEvent::class,
];
}

+ 22
- 0
app/Models/ColumnName.php View File

@@ -0,0 +1,22 @@
<?php

namespace App\Models;

abstract class ColumnName
{
// 共通
const ID = 'id';
const UPDATED_AT = 'updated_at';
const UPDATED_BY = 'updated_by';
const CREATED_AT = 'created_at';
const CREATED_BY = 'created_by';
const DELETED_AT = 'deleted_at';
const HISTORY_ID = 'history_id';

// 業務
const CONTRACT_ID = 'contract_id';
const USER_ID = 'user_id';
const RECEIPT_ISSUING_ORDER_ID = "receipt_issuing_order_id";
const SMS_SEND_ORDER_ID = "sms_send_order_id";
const SMS_PROVIDER_ID = "sms_provider_id";
}

+ 32
- 0
app/Models/Contract.php View File

@@ -0,0 +1,32 @@
<?php

namespace App\Models;

use Illuminate\Support\Str;

class Contract extends AppModel
{

const COL_NAME_NAME = 'name';
const COL_NAME_CUSTOM = 'custom';

public function getModelName(): string
{
return "契約";
}

public function includeCustom(string $target): bool
{
return in_array($target, $this->custom(), true);
}

public function custom(): array
{
$custom = data_get($this, self::COL_NAME_CUSTOM);
if ($custom) {
return explode(',', $custom);
} else {
return [];
}
}
}

+ 12
- 0
app/Models/ContractHistory.php View File

@@ -0,0 +1,12 @@
<?php

namespace App\Models;


class ContractHistory extends HistoryModel
{
public function getModelName(): string
{
return "契約履歴";
}
}

+ 12
- 0
app/Models/Email.php View File

@@ -0,0 +1,12 @@
<?php

namespace App\Models;


class Email extends AppModel
{
public function getModelName(): string
{
return "Email";
}
}

+ 12
- 0
app/Models/EmailHistory.php View File

@@ -0,0 +1,12 @@
<?php

namespace App\Models;


class EmailHistory extends HistoryModel
{
public function getModelName(): string
{
return "Email履歴";
}
}

+ 76
- 0
app/Models/Ex/LoginUser.php View File

@@ -0,0 +1,76 @@
<?php

namespace App\Models\Ex;

use App\Codes\UserRole;
use App\Models\ColumnName;
use App\Models\Contract;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use LogicException;

class LoginUser
{
private const SESSION_KEY_SADMIN_CONTRACT_ID = 'SESSION_KEY_SADMIN_CONTRACT_ID';


public function __construct(
private User $user,
private Contract $contract
) {
}

public function user(): ?User
{
return Auth::user();
}

public function contract(): ?Contract
{

if (!Auth::check()) {
return null;
}

if ($this->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);
}
}

+ 30
- 0
app/Models/Feature/ContractFeature.php View File

@@ -0,0 +1,30 @@
<?php

namespace App\Models\Feature;

use App\Models\ColumnName;
use App\Models\Contract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use LogicException;

trait ContractFeature
{
const COL_NAME_CONTRACT_ID = ColumnName::CONTRACT_ID;

public function setContract(Contract|string $contract): static
{
$id = is_string($contract) ? $contract : $contract->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("不正");
}
}
}

+ 31
- 0
app/Models/Feature/IModelFeature.php View File

@@ -0,0 +1,31 @@
<?php

namespace App\Models\Feature;

use App\Models\HistoryModel;
use Illuminate\Database\Query\Builder;

interface IModelFeature
{

public static function getBuilder(string $name = 'main'): Builder;

public static function getTableName(): string;

public function copy(IModelFeature $from): static;

public function getAttributeKeys(): array;

public function isNotSavedModel(): bool;

public function getHistory(): HistoryModel|null;

/**
* モデルの和名を取得する
*
* @return string
*/
public function getModelName(): string;

public function getChangeLogMessage($before, $after): string|null;
}

+ 30
- 0
app/Models/Feature/ReceiptIssuingOrderFeature.php View File

@@ -0,0 +1,30 @@
<?php

namespace App\Models\Feature;

use App\Models\ColumnName;
use App\Models\ReceiptIssuingOrder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use LogicException;

trait ReceiptIssuingOrderFeature
{
const COL_NAME_RECEIPT_ISSUING_ORDER_ID = ColumnName::RECEIPT_ISSUING_ORDER_ID;

public function setReceiptIssuingOrder(ReceiptIssuingOrder|string $receiptIssuingOrder): static
{
$id = is_string($receiptIssuingOrder) ? $receiptIssuingOrder : $receiptIssuingOrder->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("不正");
}
}
}

+ 30
- 0
app/Models/Feature/SMSProviderFeature.php View File

@@ -0,0 +1,30 @@
<?php

namespace App\Models\Feature;

use App\Models\ColumnName;
use App\Models\SMSProvider;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use LogicException;

trait SMSProviderFeature
{
const COL_NAME_SMS_PROVIDER_ID = ColumnName::SMS_PROVIDER_ID;

public function setSMSProvider(SMSProvider|string $smsProvider): static
{
$id = is_string($smsProvider) ? $smsProvider : $smsProvider->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("不正");
}
}
}

+ 29
- 0
app/Models/Feature/SMSSendOrderFeature.php View File

@@ -0,0 +1,29 @@
<?php

namespace App\Models\Feature;

use App\Models\ColumnName;
use App\Models\SMSSendOrder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use LogicException;

trait SMSSendOrderFeature
{
const COL_NAME_SMS_SEND_ORDER_ID = ColumnName::SMS_SEND_ORDER_ID;

public function setSMSSendOrder(SMSSendOrder|string $smsSendOrder)
{
$id = is_string($smsSendOrder) ? $smsSendOrder : $smsSendOrder->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("不正");
}
}
}

+ 39
- 0
app/Models/HistoryModel.php View File

@@ -0,0 +1,39 @@
<?php

namespace App\Models;

use App\Models\Feature\IModelFeature;
use Illuminate\Database\Eloquent\Collection;

abstract class HistoryModel extends BaseModel
{
const COL_NAME_HISTORY_ID = ColumnName::HISTORY_ID;

protected $primaryKey = ColumnName::HISTORY_ID;

/**
* @param string $id
* @return Collection<static>
*/
public static function findById(string $id)
{
return static::query()->where(ColumnName::ID, $id)
->orderBy(ColumnName::CREATED_AT)
->get();
}

public function fillFromOrigin(IModelFeature $originModel)
{
return $this->copy($originModel);
}

public function getHistory(): ?HistoryModel
{
return null;
}

public function getChangeLogMessage($before, $after): ?string
{
return null;
}
}

+ 15
- 0
app/Models/ReceiptIssuingHTParkingCustomOrder.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Models;

use App\Models\Feature\ReceiptIssuingOrderFeature;

class ReceiptIssuingHTParkingCustomOrder extends AppModel
{
use ReceiptIssuingOrderFeature;

public function getModelName(): string
{
return "領収証発行依頼(HT駐車場カスタム)";
}
}

+ 12
- 0
app/Models/ReceiptIssuingHTParkingCustomOrderHistory.php View File

@@ -0,0 +1,12 @@
<?php

namespace App\Models;


class ReceiptIssuingHTParkingCustomOrderHistory extends HistoryModel
{
public function getModelName(): string
{
return "領収証発行依頼履歴(HT駐車場カスタム)";
}
}

+ 47
- 0
app/Models/ReceiptIssuingOrder.php View File

@@ -0,0 +1,47 @@
<?php

namespace App\Models;

use App\Codes\ReceiptIssuingOrderStatus;
use App\Models\Feature\ContractFeature;

class ReceiptIssuingOrder extends AppModel
{
use ContractFeature;

const COL_NAME_HANDLER_ID = "handler_id";
const COL_NAME_ORDER_DATETIME = "order_datetime";
const COL_NAME_STATUS = "status";
const COL_NAME_SUMMARY_KEY1 = "sumamry_key1";
const COL_NAME_SUMMARY_KEY2 = "sumamry_key2";
const COL_NAME_ACCESS_TOKEN = "access_token";
const COL_NAME_ACCESS_TOKEN_EXPIRES_AT = "access_token_expires_at";
const COL_NAME_SMS_PHONE_NUMBER = "sms_phone_number";
const COL_NAME_SMS_SEND_SUCCESS = "sms_send_success";
const COL_NAME_RECEIPT_NO = "receipt_no";
const COL_NAME_RECEIPT_USE_DATETIME = "receipt_use_datetime";
const COL_NAME_RECEIPT_SHOP_NAME = "receipt_shop_name";
const COL_NAME_RECEIPT_ISSUER = "receipt_issuer";
const COL_NAME_RECEIPT_PURPOSE = "receipt_purpose";
const COL_NAME_RECEIPT_INVOICE_NO = "receipt_invoice_no";
const COL_NAME_RECEIPT_AMOUNT = "receipt_amount";
const COL_NAME_RECEIPT_HOW_TO_RECEIVE = "receipt_how_to_receive";
const COL_NAME_EMAIL = "email";
const COL_NAME_MAIL_PREF_CODE = "mail_pref_code";
const COL_NAME_MAIL_ZIP_CODE = "mail_zip_code";
const COL_NAME_MAIL_ADDRESS1 = "mail_address1";
const COL_NAME_MAIL_ADDRESS2 = "mail_address2";
const COL_NAME_MAIL_ADDRESS3 = "mail_address3";
const COL_NAME_MAIL_POST_DATE = "mail_post_date";
const COL_NAME_MEMO = "memo";


protected $casts = [
self::COL_NAME_STATUS => ReceiptIssuingOrderStatus::class,
];

public function getModelName(): string
{
return "領収証発行依頼";
}
}

+ 12
- 0
app/Models/ReceiptIssuingOrderHistory.php View File

@@ -0,0 +1,12 @@
<?php

namespace App\Models;


class ReceiptIssuingOrderHistory extends HistoryModel
{
public function getModelName(): string
{
return "領収証発行依頼履歴";
}
}

+ 15
- 0
app/Models/SMSProvider.php View File

@@ -0,0 +1,15 @@
<?php

namespace App\Models;


class SMSProvider extends AppModel
{

const COL_NAME_PROVIDER_NAME = 'provider_name';

public function getModelName(): string
{
return "SMSプロバイダーマスタ";
}
}

+ 23
- 0
app/Models/SMSProviderFourSMessageCommunication.php View File

@@ -0,0 +1,23 @@
<?php

namespace App\Models;

use App\Codes\SMSProvicer;
use App\Models\Feature\ContractFeature;
use App\Models\Feature\SMSSendOrderFeature;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class SMSProviderFourSMessageCommunication extends AppModel
{
use ContractFeature, SMSSendOrderFeature;

public function getModelName(): string
{
return "FourSMessage通信実績";
}

public function getHistory(): ?HistoryModel
{
return null;
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save