diff --git a/app/Console/Commands/RouteListCsv.php b/app/Console/Commands/RouteListCsv.php index 3a0fce8..73a1098 100644 --- a/app/Console/Commands/RouteListCsv.php +++ b/app/Console/Commands/RouteListCsv.php @@ -103,11 +103,8 @@ class RouteListCsv extends BaseCommand "説明", "メソッド", "URI", - "S", - "C", - "A", - "L", - "N", + "NONE", + "NORMAL", "コントローラー", ], $this->separator); } @@ -127,9 +124,8 @@ class RouteListCsv extends BaseCommand $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), + $this->getRoleAuth($controller, UserRole::NONE), + $this->getRoleAuth($controller, UserRole::NORMAL), $controller::class, ], $this->separator); diff --git a/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php b/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php index 1b5e64d..ed33ca7 100644 --- a/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php +++ b/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php @@ -55,7 +55,7 @@ class SeasonTikcetContractSelectionFillCandidates extends BaseCommand $db = DBUtil::instance(); $db->beginTransaction(); - + $this->getName(); $targets = $this->getTargets(); $this->outputInfo(sprintf("取得対象 %d件", $targets->count())); diff --git a/app/Console/Commands/SeasonTikcetContractSelectionSetResult.php b/app/Console/Commands/SeasonTikcetContractSelectionSetResult.php new file mode 100644 index 0000000..0001ab8 --- /dev/null +++ b/app/Console/Commands/SeasonTikcetContractSelectionSetResult.php @@ -0,0 +1,94 @@ +managers = collect(); + } + + /** + * Execute the console command. + * + * @return int + */ + public function service(): int + { + try { + $db = DBUtil::instance(); + $db->beginTransaction(); + + + $targets = $this->getTargets(); + + $this->outputInfo(sprintf("取得対象 %d件", $targets->count())); + + // データハンドリング + foreach ($targets as $data) { + $this->handleData($data); + } + + $db->commit(); + } catch (Exception $e) { + $db->rollBack(); + throw $e; + } + + return self::RESULTCODE_SUCCESS; + } + + private function getTargets() + { + $today = DateUtil::now(); + $access = SeasonTicketContractSelection::getAccess(); + $query = SeasonTicketContractSelection::getQuery() + ->whereIn(SeasonTicketContractSelection::FIELD_STATUS, [SelectionStatus::ENTRY_ACCEPTING]) + ->whereDate(SeasonTicketContractSelection::FIELD_SELECTION_FINAL_DATE, $today, KintoneRecordQueryOperator::LT); + return $access->all($query); + } + + + private function handleData(SeasonTicketContractSelection $data) + { + SetResult::dispatch($data->getRecordId()); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a94c279..3702f47 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,6 +15,8 @@ class Kernel extends ConsoleKernel Schedules\HeartBeat::register($schedule); Schedules\SMBCPoll::register($schedule); Schedules\BankAccountRegisterRemaind::register($schedule); + Schedules\SeasonTikcetContractSelectionFillCandidates::register($schedule); + Schedules\SeasonTikcetContractSelectionSetResult::register($schedule); } /** diff --git a/app/Console/Schedules/SeasonTikcetContractSelectionFillCandidates.php b/app/Console/Schedules/SeasonTikcetContractSelectionFillCandidates.php new file mode 100644 index 0000000..1f643c9 --- /dev/null +++ b/app/Console/Schedules/SeasonTikcetContractSelectionFillCandidates.php @@ -0,0 +1,17 @@ +command(Command::class) + ->everyFiveMinutes() + ->description("定期選考申込者一覧設定"); + } +} diff --git a/app/Console/Schedules/SeasonTikcetContractSelectionSetResult.php b/app/Console/Schedules/SeasonTikcetContractSelectionSetResult.php new file mode 100644 index 0000000..fe1a6e5 --- /dev/null +++ b/app/Console/Schedules/SeasonTikcetContractSelectionSetResult.php @@ -0,0 +1,17 @@ +command(Command::class) + ->at("00:05") + ->description("定期選考結果設定"); + } +} diff --git a/app/Email/BaseEmailer.php b/app/Email/BaseEmailer.php index 76de0f3..f97320f 100644 --- a/app/Email/BaseEmailer.php +++ b/app/Email/BaseEmailer.php @@ -178,7 +178,7 @@ abstract class BaseEmailer extends Mailable * @param array|string $path * @return string */ - protected function getAppUrl(array|string $path): string + protected function getAppUrl(array|string $path, array $query = []): string { $elements = [config("app.url")]; if (is_array($path)) { @@ -187,9 +187,17 @@ abstract class BaseEmailer extends Mailable $elements[] = $path; } - return implode( - "/", - $elements, - ); + $url = implode("/", $elements); + + if (!!$query) { + $url .= "?"; + $queryStrList = []; + foreach ($query as $key => $value) { + $queryStrList[] = sprintf("%s=%s", $key, $value); + } + $url .= implode("&", $queryStrList); + } + + return $url; } } diff --git a/app/Email/Members/SelectionNotice.php b/app/Email/Members/SelectionNotice.php new file mode 100644 index 0000000..c76281e --- /dev/null +++ b/app/Email/Members/SelectionNotice.php @@ -0,0 +1,45 @@ +selection); + + return [ + 'customer_name' => $this->entry->customerName, + 'parking_name' => $this->entry->parkingName, + 'url' => $this->getAppUrl([ + 'season-ticket-contract', + 'selection', 'entry', + $this->selection->getRecordId(), + $this->entry->getRecordId(), + $manager->getHash($this->entry->getRecordId()) + ]), + ]; + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Entry/CancelController.php b/app/Http/Controllers/Web/SeasonTicketContract/Entry/CancelController.php new file mode 100644 index 0000000..a0407cd --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Entry/CancelController.php @@ -0,0 +1,41 @@ +param; + + $manager = new SeasonTicketContractEntryManager($param->recordNo); + + if (!$manager->checkHash($param->fs)) { + return $this->failedResponse(); + } + + $entry = $manager->getEntry(); + + return $this->successResponse($entry->toArray()); + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Entry/CancelParams.php b/app/Http/Controllers/Web/SeasonTicketContract/Entry/CancelParams.php new file mode 100644 index 0000000..ba5c603 --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Entry/CancelParams.php @@ -0,0 +1,20 @@ + $this->numeric(), + 'fs' => $this->str(), + ]; + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Entry/EntryInfoController.php b/app/Http/Controllers/Web/SeasonTicketContract/Entry/EntryInfoController.php new file mode 100644 index 0000000..952ad79 --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Entry/EntryInfoController.php @@ -0,0 +1,42 @@ +param; + + $manager = new SeasonTicketContractEntryManager($param->recordNo); + + if (!$manager->checkHash($param->fs)) { + return $this->failedResponse(); + } + + $entry = $manager->getEntry(); + + return $this->successResponse($entry->toArray()); + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Entry/EntryInfoParams.php b/app/Http/Controllers/Web/SeasonTicketContract/Entry/EntryInfoParams.php new file mode 100644 index 0000000..479ff86 --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Entry/EntryInfoParams.php @@ -0,0 +1,20 @@ + $this->numeric(), + 'fs' => $this->str(), + ]; + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Selection/EntryController.php b/app/Http/Controllers/Web/SeasonTicketContract/Selection/EntryController.php new file mode 100644 index 0000000..b6787eb --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Selection/EntryController.php @@ -0,0 +1,53 @@ +param; + + $manager = new SeasonTicketContractSelectionManager($param->selectionRecordNo); + + if (!$manager->checkHash($param->entryRecordNo, $param->fs)) { + return $this->failedResponse(); + } + + $manager->entry($param->entryRecordNo, $param->fs); + + $entryManager = new SeasonTicketContractEntryManager($param->entryRecordNo); + + $entry = $entryManager->getEntry(); + $entry->address = $param->address; + $entry->phoneNo = $param->phoneNo; + + $entryManager->save(); + $manager->save(); + + return $this->successResponse(); + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Selection/EntryParams.php b/app/Http/Controllers/Web/SeasonTicketContract/Selection/EntryParams.php new file mode 100644 index 0000000..677475a --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Selection/EntryParams.php @@ -0,0 +1,28 @@ + $this->numeric(), + 'entry_record_no' => $this->numeric(), + 'fs' => $this->str(), + 'address' => $this->str(), + 'phone_no' => $this->str([new SimpleRegEx("/[^0-9-]+/", "不正な文字が含まれています", true)]), + ]; + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Selection/NoticeToCandidatesController.php b/app/Http/Controllers/Web/SeasonTicketContract/Selection/NoticeToCandidatesController.php new file mode 100644 index 0000000..fe38178 --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Selection/NoticeToCandidatesController.php @@ -0,0 +1,37 @@ +param; + + NoticeToCandidates::dispatch($param->recordNo)->delay(5); + + return $this->successResponse(); + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Selection/NoticeToCandidatesParams.php b/app/Http/Controllers/Web/SeasonTicketContract/Selection/NoticeToCandidatesParams.php new file mode 100644 index 0000000..3a1055d --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Selection/NoticeToCandidatesParams.php @@ -0,0 +1,18 @@ + $this->numeric(), + ]; + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Selection/SelectionInfoController.php b/app/Http/Controllers/Web/SeasonTicketContract/Selection/SelectionInfoController.php new file mode 100644 index 0000000..2eb5e6c --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Selection/SelectionInfoController.php @@ -0,0 +1,49 @@ +param; + + $manager = new SeasonTicketContractSelectionManager($param->selectionRecordNo); + + if (!$manager->checkHash($param->entryRecordNo, $param->fs)) { + return $this->failedResponse(); + } + + if ($manager->getSelection()->status !== SelectionStatus::ENTRY_ACCEPTING) { + throw new GeneralErrorMessageException("募集期間が終了しています"); + } + + $entry = $manager->getEntry($param->entryRecordNo); + + return $this->successResponse($entry->toArray()); + } +} diff --git a/app/Http/Controllers/Web/SeasonTicketContract/Selection/SelectionInfoParams.php b/app/Http/Controllers/Web/SeasonTicketContract/Selection/SelectionInfoParams.php new file mode 100644 index 0000000..cba023b --- /dev/null +++ b/app/Http/Controllers/Web/SeasonTicketContract/Selection/SelectionInfoParams.php @@ -0,0 +1,22 @@ + $this->numeric(), + 'entry_record_no' => $this->numeric(), + 'fs' => $this->str(), + ]; + } +} diff --git a/app/Jobs/BaseJob.php b/app/Jobs/BaseJob.php index 9a22738..43bafbf 100644 --- a/app/Jobs/BaseJob.php +++ b/app/Jobs/BaseJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Util\DBUtil; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -13,10 +14,22 @@ abstract class BaseJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + protected DBUtil $db; + public function handle() { - $this->logConfig(); - $this->handleJob(); + $this->db = DBUtil::instance(); + try { + $this->db->beginTransaction(); + + $this->logConfig(); + $this->handleJob(); + + $this->db->commit(); + } catch (Exception $e) { + $this->db->rollBack(); + throw $e; + } } /** diff --git a/app/Jobs/SeasonTicketContract/Selection/NoticeToCandidates.php b/app/Jobs/SeasonTicketContract/Selection/NoticeToCandidates.php new file mode 100644 index 0000000..d32c53e --- /dev/null +++ b/app/Jobs/SeasonTicketContract/Selection/NoticeToCandidates.php @@ -0,0 +1,38 @@ +onQueue(QueueName::JOB->value); + } + + protected function handleJob() + { + try { + $manager = new SeasonTicketContractSelectionManager($this->recordNo); + $manager->sendNotice() + ->save(); + } catch (ModelNotFoundException $e) { + LoggingUtil::errorException($e, sprintf("データ存在なし削除 %s", self::class)); + } catch (Exception $e) { + LoggingUtil::errorException($e, sprintf("ジョブ失敗->削除 %s", self::class)); + } + } +} diff --git a/app/Jobs/SeasonTicketContract/Selection/SetResult.php b/app/Jobs/SeasonTicketContract/Selection/SetResult.php new file mode 100644 index 0000000..30a8d64 --- /dev/null +++ b/app/Jobs/SeasonTicketContract/Selection/SetResult.php @@ -0,0 +1,62 @@ +onQueue(QueueName::JOB->value); + } + + protected function handleJob() + { + try { + $manager = new SeasonTicketContractSelectionManager($this->recordNo); + $manager->makeResult() + ->save(); + + $selection = $manager->getSelection(); + + // 以下ログ出力処理 + if ($selection->status === SelectionStatus::RESULT_DECISION) { + foreach ($selection->resultList as $result) { + info(sprintf( + "選考結果設定 駐車場:%s 選考レコード番号:%d 締日:%s 氏名:%s 申込番号:%s", + $selection->parkingName, + $selection->getRecordId(), + $selection->selectionFinalDate ? $selection->selectionFinalDate->format('Y/m/d') : "-", + $result->name, + $result->entryNo, + )); + } + } else if ($selection->status === SelectionStatus::FAILED) { + info(sprintf( + "選考不調 駐車場:%s 選考レコード番号:%d 締日:%s", + $selection->parkingName, + $selection->getRecordId(), + $selection->selectionFinalDate ? $selection->selectionFinalDate->format('Y/m/d') : "-", + )); + } + } catch (ModelNotFoundException $e) { + LoggingUtil::errorException($e, sprintf("データ存在なし削除 %s", self::class)); + } catch (Exception $e) { + LoggingUtil::errorException($e, sprintf("ジョブ失敗->削除 %s", self::class)); + } + } +} diff --git a/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php b/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php index c521bfe..8759653 100644 --- a/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php +++ b/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php @@ -6,4 +6,5 @@ abstract class Status { const RESERVE = "予約"; const WAIT_EMPTY = "空き待ち"; + const CANCEL = "キャンセル"; } diff --git a/app/Kintone/Models/SeasonTicketContractEntry.php b/app/Kintone/Models/SeasonTicketContractEntry.php index aaa3061..ba5c53e 100644 --- a/app/Kintone/Models/SeasonTicketContractEntry.php +++ b/app/Kintone/Models/SeasonTicketContractEntry.php @@ -18,6 +18,7 @@ use Illuminate\Support\Carbon; * @property int carAmount * @property string paymentMethod * @property Carbon entryDatetime + * @property string planName */ class SeasonTicketContractEntry extends KintoneModel { @@ -35,6 +36,7 @@ class SeasonTicketContractEntry extends KintoneModel const FIELD_CAR_AMOUNT = "台数"; const FIELD_PAYMENT_METHOD = "支払方法"; const FIELD_ENTRY_DATETIME = "受付日時"; + const FIELD_PLAN_NAME = "ParkingNaviプラン"; protected const FIELDS = [ ...parent::FIELDS, @@ -48,12 +50,21 @@ class SeasonTicketContractEntry extends KintoneModel self::FIELD_VEHICLE_NO => FieldType::SINGLE_LINE_TEXT, self::FIELD_PAYMENT_METHOD => FieldType::DROP_DOWN, self::FIELD_ENTRY_DATETIME => FieldType::DATETIME, + self::FIELD_PLAN_NAME => FieldType::SINGLE_LINE_TEXT, ]; protected const FIELD_NAMES = [ ...parent::FIELD_NAMES, + self::FIELD_PARKING_NAME => 'parking_name', + self::FIELD_CUSTOMER_NAME => 'customer_name', + self::FIELD_PLAN_NAME => 'plan_name', + self::FIELD_ENTRY_DATETIME => 'entry_datetime', + self::FIELD_PHONE_NO => 'phone_no', + self::FIELD_ADDRESS => 'address', + self::FIELD_CAR_AMOUNT => 'car_amount', ]; + public function getParking(): Parking { return Parking::findByParkingName($this->parkingName); diff --git a/app/Logic/SeasonTicketContractEntryManager.php b/app/Logic/SeasonTicketContractEntryManager.php new file mode 100644 index 0000000..234d545 --- /dev/null +++ b/app/Logic/SeasonTicketContractEntryManager.php @@ -0,0 +1,51 @@ +entry = SeasonTicketContractEntry::find($recordNo); + return; + } else if ($recordNo instanceof SeasonTicketContractEntry) { + $this->entry = $recordNo; + return; + } + } + + public function getEntry() + { + return $this->entry; + } + + public function cancel() + { + $this->entry->status = Status::CANCEL; + return $this; + } + + public function save() + { + $this->entry->save(); + return $this; + } + + public function getHash() + { + $source = sprintf("%010d-%s", $this->entry->getRecordId(), $this->entry->entryDatetime->format("YmdHi")); + return hash('sha256', $source); + } + + public function checkHash(string $hash): bool + { + $expect = $this->getHash(); + return $expect === $hash; + } +} diff --git a/app/Logic/SeasonTicketContractSelectionManager.php b/app/Logic/SeasonTicketContractSelectionManager.php index f127f6c..28fb065 100644 --- a/app/Logic/SeasonTicketContractSelectionManager.php +++ b/app/Logic/SeasonTicketContractSelectionManager.php @@ -2,7 +2,9 @@ namespace App\Logic; +use App\Email\Members\SelectionNotice; use App\Exceptions\AppCommonException; +use App\Exceptions\GeneralErrorMessageException; use App\Kintone\KintoneRecordQuery; use App\Kintone\KintoneRecordQueryOperator; use App\Kintone\Models\DropDown\SeasonTicketContractEntry\Status; @@ -10,6 +12,7 @@ use App\Kintone\Models\SeasonTicketContractEntry as EntryModel; use App\Kintone\Models\SeasonTicketContractSelection as SelectionModel; use App\Kintone\Models\SubTable\SeasonTicketContractSelection\Entry; use App\Kintone\Models\DropDown\SeasonTicketContractSelection\SelectionStatus; +use App\Kintone\Models\SeasonTicketContractEntry; use App\Kintone\Models\SubTable\SeasonTicketContractSelection\Candidate; use App\Kintone\Models\SubTable\SeasonTicketContractSelection\Result; use App\Kintone\Models\SubTable\SeasonTicketContractSelection\TargetRoom; @@ -21,10 +24,12 @@ class SeasonTicketContractSelectionManager private SelectionModel $selection; - public function __construct(?int $recordNo = null) + public function __construct(int|SelectionModel|null $recordNo = null) { - if ($recordNo) { + if (is_int($recordNo)) { $this->selection = SelectionModel::find($recordNo); + } else if ($recordNo instanceof SelectionModel) { + $this->selection = $recordNo; } else { $this->selection = new SelectionModel(); $this->selection->status = SelectionStatus::START; @@ -84,9 +89,26 @@ class SeasonTicketContractSelectionManager return $this; } - public function sendNotine() + public function sendNotice() { // メール送信 + foreach ($this->selection->candidateList as $candidate) { + if ($candidate->emailSendTarget) { + $entry = SeasonTicketContractEntry::find($candidate->entryRecordNo); + + if (!in_array($entry->status, [Status::RESERVE, Status::WAIT_EMPTY])) { + continue; + } + if (!$entry->email) { + continue; + } + + $email = new SelectionNotice($this->selection, $entry); + (new EmailManager($email->setEmail($entry->email)))->confirm(); + } + } + + $this->selection->status = SelectionStatus::ENTRY_ACCEPTING; return $this; } @@ -114,6 +136,10 @@ class SeasonTicketContractSelectionManager throw new AppCommonException("認証エラー"); } + if ($this->selection->status !== SelectionStatus::ENTRY_ACCEPTING) { + throw new GeneralErrorMessageException("募集期間が終了しています"); + } + $list = $this->selection->entryList; if ($this->hasAlreadyEnterd($recordNo)) { @@ -144,13 +170,22 @@ class SeasonTicketContractSelectionManager return $this->selection; } + public function getEntry(int $entryRecordNo) + { + return SeasonTicketContractEntry::find($entryRecordNo); + } + + /** + * @param integer $recordNo 申込レコード番号 + * @return string + */ public function getHash(int $recordNo): string { $source = sprintf("%010d-%010d", $recordNo, intval($this->selection->getRecordId())); return hash('sha256', $source); } - private function checkHash(int $recordNo, string $hash): bool + public function checkHash(int $recordNo, string $hash): bool { $expect = $this->getHash($recordNo); return $expect === $hash; @@ -192,6 +227,8 @@ class SeasonTicketContractSelectionManager $result = new Result(); $result->entryRecordNo = $entry->entryRecordNo; $result->roomRecordNo = $room->roomRecordNo; + $result->name = $entry->name; + $result->entryNo = $entry->entryNo; $totalRoomAmount += $entry->carAmount; $resultList->push($result); } @@ -203,7 +240,7 @@ class SeasonTicketContractSelectionManager $this->selection->status = $resultList->isNotEmpty() ? - SelectionStatus::COMPLETE : SelectionStatus::FAILED; + SelectionStatus::RESULT_DECISION : SelectionStatus::FAILED; $this->selection->resultList = $resultList; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 113f31e..41f4f04 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -31,8 +31,8 @@ class RouteServiceProvider extends ServiceProvider ->prefix('api') ->group(base_path('routes/api.php')); Route::middleware('api') - ->prefix('api-email') - ->group(base_path('routes/api_email.php')); + ->prefix('api-from-kintone') + ->group(base_path('routes/apiFromKintone.php')); Route::middleware('web') ->group(base_path('routes/web.php')); diff --git a/app/Rules/SimpleRegEx.php b/app/Rules/SimpleRegEx.php new file mode 100644 index 0000000..22a19b0 --- /dev/null +++ b/app/Rules/SimpleRegEx.php @@ -0,0 +1,33 @@ +pattern, $value); + return $this->notMatch ? $match === 0 : $match !== 0; + } + + /** + * Get the validation error message. + * + * @return string + */ + public function message() + { + return $this->message; + } +}