diff --git a/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php b/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php new file mode 100644 index 0000000..1b5e64d --- /dev/null +++ b/app/Console/Commands/SeasonTikcetContractSelectionFillCandidates.php @@ -0,0 +1,90 @@ +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() + { + $access = SeasonTicketContractSelection::getAccess(); + $query = SeasonTicketContractSelection::getQuery() + ->whereIn(SeasonTicketContractSelection::FIELD_STATUS, [SelectionStatus::START]); + return $access->all($query); + } + + + private function handleData(SeasonTicketContractSelection $data) + { + FillCandidates::dispatch($data->getRecordId()); + } +} diff --git a/app/Http/Controllers/PDFController.php b/app/Http/Controllers/PDFController.php index 6f4c5a5..164bb1d 100644 --- a/app/Http/Controllers/PDFController.php +++ b/app/Http/Controllers/PDFController.php @@ -2,25 +2,26 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; +use App\Kintone\Models\Receipt; +use App\Logic\ReceiptManager; use PDF; class PDFController extends Controller { - public function index() + public function entry() { - // https://wkhtmltopdf.org/usage/wkhtmltopdf.txt + $manager = new ReceiptManager(2); + $file = $manager->makePdf(); - // $pdf = PDF::loadHTML('

Hello World 岩渕

'); - $pdf = PDF::loadView('pdf'); + $receipt = $manager->getReceipt(); + // キントーンへの保存 + $receipt->setFiles(Receipt::FIELD_RECEIPT_PDF_FILE, collect([$file])); + $receipt->save(); - // はがきサイズを指定 - $pdf->setOption('page-height', 148) - ->setOption('page-width', 100) - ->setOption('encoding', 'utf-8'); - return $pdf->inline(); + return response() + ->file($file->getFullPath()); } } diff --git a/app/Http/Controllers/Web/Image/ImageController.php b/app/Http/Controllers/Web/Image/ImageController.php index 732d72d..abe0e47 100644 --- a/app/Http/Controllers/Web/Image/ImageController.php +++ b/app/Http/Controllers/Web/Image/ImageController.php @@ -2,9 +2,9 @@ namespace App\Http\Controllers\Web\Image; +use App\Exceptions\AppCommonException; use App\Http\Controllers\Web\WebController; use App\Kintone\KintoneAccess; -use App\Kintone\Models\SeasonTicketContract; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -30,12 +30,21 @@ abstract class ImageController extends WebController protected function run(Request $request): Response { - $access = $this->getAccess(); - $file = $access->fileGet($this->param->id); - return response($file->body(), 200, [ - 'Content-Length' => $file->header('Content-Length'), - 'Content-Type' => $file->header('Content-Type'), - ]); + try { + + $id = $request->route('id'); + if (!$id) { + throw new AppCommonException("パラメータ不正"); + } + $access = $this->getAccess(); + $file = $access->fileGet($id); + return response($file->body(), 200, [ + 'Content-Length' => $file->header('Content-Length'), + 'Content-Type' => $file->header('Content-Type'), + ]); + } catch (Exception $e) { + return response(); + } } abstract protected function getAccess(): KintoneAccess; diff --git a/app/Jobs/SeasonTicketContract/Selection/FillCandidates.php b/app/Jobs/SeasonTicketContract/Selection/FillCandidates.php new file mode 100644 index 0000000..cb585ab --- /dev/null +++ b/app/Jobs/SeasonTicketContract/Selection/FillCandidates.php @@ -0,0 +1,47 @@ +onQueue(QueueName::JOB->value); + } + + protected function handleJob() + { + try { + $manager = new SeasonTicketContractSelectionManager($this->recordNo); + $manager->makeCandidates() + ->save(); + + $selection = $manager->getSelection(); + + info(sprintf( + "候補者設定 駐車場:%s 締日:%s 候補者数:%d件", + $selection->parkingName, + $selection->selectionFinalDate ? $selection->selectionFinalDate->format('Y/m/d') : "-", + $selection->candidateList->count(), + )); + } 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/KintoneAccess.php b/app/Kintone/KintoneAccess.php index 21a3710..cf3d51e 100644 --- a/app/Kintone/KintoneAccess.php +++ b/app/Kintone/KintoneAccess.php @@ -195,8 +195,16 @@ class KintoneAccess "id" => $model->getRecordId(), "record" => $model->getApiLayout(), "revision" => $model->getRevision(), - ]; + + logger([ + "ACTION" => "KINTONE UPDATE", + "APP" => $this->appName, + "RECORD_NO" => $model->getRecordId(), + "SEND_DATA" => $sendData, + "JSON" => json_encode($sendData, JSON_UNESCAPED_UNICODE), + ]); + $response = Http::withHeaders([ "X-Cybozu-API-Token" => $this->apiToken, ])->put($this->getRecordUrl(), $sendData); diff --git a/app/Kintone/KintoneRecordQuery.php b/app/Kintone/KintoneRecordQuery.php index e0b0a6c..9033d0c 100644 --- a/app/Kintone/KintoneRecordQuery.php +++ b/app/Kintone/KintoneRecordQuery.php @@ -32,51 +32,83 @@ class KintoneRecordQuery return $ret; } + /** + * @param string|\Closure(static $query): static $column + */ public function where(string|Closure $column, string|int|null $condition = null, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ) { return $this->whereQuery("and", $operator->value, $column, $condition); } + /** + * @param string|\Closure(static $query): static $column + */ public function notWhere(string|Closure $column, string|int|null $condition = null, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::NEQ) { return $this->whereQuery("and", $operator->value, $column, $condition); } - + /** + * @param string|\Closure(static $query): static $column + */ public function orWhere(string|Closure $column, string|int|null $condition = null, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ) { $this->whereQuery("or", $operator->value, $column, $condition); return $this; } + /** + * @param string|\Closure(static $query): static $column + */ public function notOrWhere(string|Closure $column, string|int|null $condition = null, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::NEQ) { return $this->whereQuery("or", $operator->value, $column, $condition); } + /** + * @param string|\Closure(static $query): static $column + */ public function whereIn(string|Closure $column, array $condition) { return $this->whereQuery("and", "in", $column, $condition); } + /** + * @param string|\Closure(static $query): static $column + */ public function whereNotIn(string|Closure $column, array $condition) { return $this->whereQuery("and", "not in", $column, $condition); } + /** + * @param string|\Closure(static $query): static $column + */ public function orWhereIn(string|Closure $column, array $condition) { return $this->whereQuery("or", "not in", $column, $condition); } - public function whereDate(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ) + public function whereDate(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ, bool $allowNull = false) { - return $this->whereNotNull($column)->whereQuery("and", $operator->value, $column, $date->format('Y-m-d')); + if (!$allowNull) { + $this->whereNotNull($column); + } + return $this->whereQuery("and", $operator->value, $column, $date->format('Y-m-d')); } - public function whereDateTime(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ) + public function whereDateTime(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ, bool $allowNull = false) { - return $this->whereNotNull($column)->whereQuery("and", $operator->value, $column, $date->toIso8601ZuluString()); + if (!$allowNull) { + $this->whereNotNull($column); + } + return $this->whereQuery("and", $operator->value, $column, $date->toIso8601ZuluString()); } - public function orWhereDate(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ) + public function orWhereDate(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ, bool $allowNull = false) { - return $this->whereNotNull($column)->whereQuery("or", $operator->value, $column, $date->format('Y-m-d')); + if (!$allowNull) { + $this->whereNotNull($column); + } + return $this->whereQuery("or", $operator->value, $column, $date->format('Y-m-d')); } - public function orWhereDateTime(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ) + public function orWhereDateTime(string $column, Carbon $date, KintoneRecordQueryOperator $operator = KintoneRecordQueryOperator::EQ, bool $allowNull = false) { - return $this->whereNotNull($column)->whereQuery("or", $operator->value, $column, $date->toIso8601ZuluString()); + if (!$allowNull) { + $this->whereNotNull($column); + } + return $this->whereQuery("or", $operator->value, $column, $date->toIso8601ZuluString()); } public function whereNull(string $column) { @@ -109,6 +141,9 @@ class KintoneRecordQuery return $this; } + /** + * @param string|\Closure(static $query): static $column + */ private function whereQuery( string $andOr, string $operator, diff --git a/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php b/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php new file mode 100644 index 0000000..c521bfe --- /dev/null +++ b/app/Kintone/Models/DropDown/SeasonTicketContractEntry/Status.php @@ -0,0 +1,9 @@ +data = new stdClass(); + + foreach (static::FIELDS as $fileCode => $type) { + if ($type === FieldType::SUBTABLE) { + $this->set($fileCode, collect()); + } + } } public function __get($name) @@ -244,12 +250,18 @@ abstract class KintoneModel if ($type === FieldType::SUBTABLE && $this->getFieldType($fieldCode)) { $ret = collect(); foreach ($value as $v) { - $rowRet = - $rowArray = data_get($v, 'value'); + $rowRet = []; + $rowArray = data_get($v, 'value'); + $isEmptyRow = true; foreach ($rowArray as $subFieldCode => $ele) { $rowRet[$subFieldCode] = $this->readData($ele); + if (!!$rowRet[$subFieldCode]) { + $isEmptyRow = false; + } + } + if (!$isEmptyRow) { + $ret->push($this->getSubTableData($fieldCode, $rowRet)); } - $ret->push($this->getSubTableData($fieldCode, $rowRet)); } return $ret; } @@ -332,7 +344,11 @@ abstract class KintoneModel foreach ($data as $ele) { if ($ele instanceof SubTableData) { $obj = []; - $obj['value'] = $ele->toArray(); + + $values = $ele->toArray(); + foreach ($values as $subFieldCode => $value) { + $obj['value'][$subFieldCode]['value'] = $value; + } $ret[] = $obj; } } diff --git a/app/Kintone/Models/ParkingRoom.php b/app/Kintone/Models/ParkingRoom.php index 3dd7968..5ba8a4e 100644 --- a/app/Kintone/Models/ParkingRoom.php +++ b/app/Kintone/Models/ParkingRoom.php @@ -12,10 +12,14 @@ class ParkingRoom extends KintoneModel const CONFIG_KEY = "KINTONE_APP_PARKING_ROOM"; const FIELD_PARKING_NAME = "定期駐車場"; + const FIELD_ROOM_NO = "車室番号"; + const FIELD_ROOM_TYPE = "車室タイプ"; protected const FIELDS = [ ...parent::FIELDS, self::FIELD_PARKING_NAME => FieldType::SINGLE_LINE_TEXT, + self::FIELD_ROOM_NO => FieldType::SINGLE_LINE_TEXT, + self::FIELD_ROOM_TYPE => FieldType::DROP_DOWN, ]; protected const FIELD_NAMES = [ diff --git a/app/Kintone/Models/SeasonTicketContractEntry.php b/app/Kintone/Models/SeasonTicketContractEntry.php index 67fe9a2..aaa3061 100644 --- a/app/Kintone/Models/SeasonTicketContractEntry.php +++ b/app/Kintone/Models/SeasonTicketContractEntry.php @@ -7,20 +7,24 @@ use Illuminate\Support\Carbon; /** * アプリ名 定期申込・予約 * @property string parkingName + * @property string status * @property string customerName * @property string customerNameKana * @property string phoneNo * @property string address * @property string email - * @property Carbon useStartDate + * @property ?Carbon useStartDate * @property string vehicleNo + * @property int carAmount * @property string paymentMethod + * @property Carbon entryDatetime */ class SeasonTicketContractEntry extends KintoneModel { const CONFIG_KEY = "KINTONE_APP_SEASON_TICEKT_CONTRACT_ENTRY"; const FIELD_PARKING_NAME = "駐車場"; + const FIELD_STATUS = "status"; const FIELD_CUSTOMER_NAME = "氏名"; const FIELD_CUSTOMER_NAME_KANA = "フリガナ"; const FIELD_PHONE_NO = "電話番号"; @@ -28,7 +32,9 @@ class SeasonTicketContractEntry extends KintoneModel const FIELD_EMAIL = "メールアドレス"; const FIELD_USE_START_DATE = "利用開始希望日"; const FIELD_VEHICLE_NO = "車両番号"; + const FIELD_CAR_AMOUNT = "台数"; const FIELD_PAYMENT_METHOD = "支払方法"; + const FIELD_ENTRY_DATETIME = "受付日時"; protected const FIELDS = [ ...parent::FIELDS, @@ -41,6 +47,7 @@ class SeasonTicketContractEntry extends KintoneModel self::FIELD_USE_START_DATE => FieldType::DATE, self::FIELD_VEHICLE_NO => FieldType::SINGLE_LINE_TEXT, self::FIELD_PAYMENT_METHOD => FieldType::DROP_DOWN, + self::FIELD_ENTRY_DATETIME => FieldType::DATETIME, ]; protected const FIELD_NAMES = [ diff --git a/app/Kintone/Models/SeasonTicketContractReserve.php b/app/Kintone/Models/SeasonTicketContractReserve.php deleted file mode 100644 index 28bbe50..0000000 --- a/app/Kintone/Models/SeasonTicketContractReserve.php +++ /dev/null @@ -1,48 +0,0 @@ - targetRoomList - */ -class SeasonTicketContractReserve extends KintoneModel -{ - const CONFIG_KEY = "KINTONE_APP_SEASON_TICEKT_CONTRACT_RESERVE"; - - const FIELD_PARKING_NAME = "駐車場"; - - const FIELD_TARGET_ROOM_LIST = "対象車室一覧"; - const FIELD_TARGET_ROOM_LIST_ROOM_NO = "対象車室一覧_車室番号"; - const FIELD_TARGET_ROOM_LIST_ROOM_TYPE = "対象車室一覧_車室タイプ"; - const FIELD_TARGET_ROOM_LIST_RECORD_NO = "対象車室一覧_車室レコード番号"; - - protected const FIELDS = [ - ...parent::FIELDS, - self::FIELD_PARKING_NAME => FieldType::SINGLE_LINE_TEXT, - self::FIELD_TARGET_ROOM_LIST => FieldType::SUBTABLE, - // self::FIELD_TARGET_ROOM_LIST => [ - // self::FIELD_TARGET_ROOM_LIST_RECORD_NO => FieldType::NUMBER, - // self::FIELD_TARGET_ROOM_LIST_ROOM_TYPE => FieldType::SINGLE_LINE_TEXT, - // self::FIELD_TARGET_ROOM_LIST_ROOM_NO => FieldType::SINGLE_LINE_TEXT, - // ], - ]; - - protected const SUB_TABLES = [ - self::FIELD_TARGET_ROOM_LIST => TargetRoom::class, - ]; - - protected const FIELD_NAMES = [ - ...parent::FIELD_NAMES, - ]; - - protected const RELATIONS = [ - SeasonTicketContractEntry::class, - ParkingRoom::class, - ]; -} diff --git a/app/Kintone/Models/SeasonTicketContractSelection.php b/app/Kintone/Models/SeasonTicketContractSelection.php new file mode 100644 index 0000000..7c34583 --- /dev/null +++ b/app/Kintone/Models/SeasonTicketContractSelection.php @@ -0,0 +1,104 @@ + targetRoomList + * @property Collection candidateList + * @property Collection entryList + * @property Collection resultList + * @property string selectionMessage + */ +class SeasonTicketContractSelection extends KintoneModel +{ + const CONFIG_KEY = "KINTONE_APP_SEASON_TICEKT_CONTRACT_SELECTION"; + + const FIELD_PARKING_NAME = "駐車場名"; + const FIELD_STATUS = "選考ステータス"; + + const FIELD_USE_START_DATE = "利用開始日"; + const FIELD_SELECTION_FINAL_DATE = "選考締切日"; + + const FIELD_TARGET_ROOM_LIST = "対象車室一覧"; + const FIELD_TARGET_ROOM_LIST_ROOM_NO = "対象車室一覧_車室番号"; + const FIELD_TARGET_ROOM_LIST_ROOM_TYPE = "対象車室一覧_車室タイプ"; + const FIELD_TARGET_ROOM_LIST_RECORD_NO = "対象車室一覧_車室レコード番号"; + + const FIELD_CANDIDATE_LIST = "申込者一覧"; + const FIELD_CANDIDATE_EMAIL_SEND_TARGET = "申込者一覧_通知対象"; + const FIELD_CANDIDATE_LIST_RECORD_NO = "申込者一覧_申込レコード番号"; + const FIELD_CANDIDATE_LIST_STATUS = "申込者一覧_状態"; + const FIELD_CANDIDATE_LIST_PLAN = "申込者一覧_プラン"; + const FIELD_CANDIDATE_LIST_ENTRY_NO = "申込者一覧_申込番号"; + const FIELD_CANDIDATE_LIST_NAME = "申込者一覧_氏名"; + const FIELD_CANDIDATE_LIST_ENTRY_DATETIME = "申込者一覧_受付日時"; + const FIELD_CANDIDATE_LIST_WANTS_TO_USE_START_DATE = "申込者一覧_利用開始希望日"; + const FIELD_CANDIDATE_LIST_CAR_AMOUNT = "申込者一覧_台数"; + const FIELD_CANDIDATE_LIST_EMAIL = "申込者一覧_メールアドレス"; + + const FIELD_ENTRY_LIST = "契約希望者一覧"; + const FIELD_ENTRY_LIST_STATUS = "契約希望者一覧_状態"; + const FIELD_ENTRY_LIST_PLAN = "契約希望者一覧_プラン"; + const FIELD_ENTRY_LIST_RECORD_NO = "契約希望者一覧_申込レコード番号"; + const FIELD_ENTRY_LIST_ENTRY_NO = "契約希望者一覧_申込番号"; + const FIELD_ENTRY_LIST_NAME = "契約希望者一覧_氏名"; + const FIELD_ENTRY_LIST_ENTRY_DATETIME = "契約希望者一覧_受付日時"; + const FIELD_ENTRY_LIST_WANTS_TO_USE_START_DATE = "契約希望者一覧_利用開始希望日"; + const FIELD_ENTRY_LIST_CAR_AMOUNT = "契約希望者一覧_台数"; + + const FIELD_RESULT_LIST = "選考結果一覧"; + const FIELD_RESULT_LIST_ROOM_RECORD_NO = "選考結果一覧_車室レコード番号"; + const FIELD_RESULT_LIST_ROOM_NO = "選考結果一覧_車室番号"; + const FIELD_RESULT_LIST_ROOM_TYPE = "選考結果一覧_車室タイプ"; + const FIELD_RESULT_LIST_ENTRY_RECORD_NO = "選考結果一覧_申込レコード番号"; + const FIELD_RESULT_LIST_STATUS = "選考結果一覧_ステータス"; + const FIELD_RESULT_LIST_PLAN = "選考結果一覧_プラン"; + const FIELD_RESULT_LIST_ENTRY_NO = "選考結果一覧_申込番号"; + const FIELD_RESULT_LIST_NAME = "選考結果一覧_氏名"; + + const FIELD_SELECTION_MESSAGE = "自動選考メッセージ"; + + const FIELD_SEARCH_CANDIDATE_LIST_RECORD_NO = "検索用_申込一覧_レコード番号"; + const FIELD_SEARCH_ENTRY_LIST_RECORD_NO = "検索用_契約希望者_レコード番号"; + const FIELD_SEARCH_RESULT_LIST_ENTRY_RECORD_NO = "検索用_選考結果_レコード番号"; + + + protected const FIELDS = [ + ...parent::FIELDS, + self::FIELD_PARKING_NAME => FieldType::SINGLE_LINE_TEXT, + self::FIELD_STATUS => FieldType::DROP_DOWN, + self::FIELD_TARGET_ROOM_LIST => FieldType::SUBTABLE, + self::FIELD_CANDIDATE_LIST => FieldType::SUBTABLE, + self::FIELD_ENTRY_LIST => FieldType::SUBTABLE, + self::FIELD_RESULT_LIST => FieldType::SUBTABLE, + self::FIELD_SELECTION_MESSAGE => FieldType::MULTI_LINE_TEXT, + ]; + + protected const SUB_TABLES = [ + self::FIELD_TARGET_ROOM_LIST => TargetRoom::class, + self::FIELD_CANDIDATE_LIST => Candidate::class, + self::FIELD_ENTRY_LIST => Entry::class, + self::FIELD_RESULT_LIST => Result::class, + ]; + + protected const FIELD_NAMES = [ + ...parent::FIELD_NAMES, + ]; + + protected const RELATIONS = [ + SeasonTicketContractEntry::class, + ParkingRoom::class, + ]; +} diff --git a/app/Kintone/Models/SubTable/SeasonTicketContractReserve/TargetRoom.php b/app/Kintone/Models/SubTable/SeasonTicketContractReserve/TargetRoom.php deleted file mode 100644 index 8369bae..0000000 --- a/app/Kintone/Models/SubTable/SeasonTicketContractReserve/TargetRoom.php +++ /dev/null @@ -1,31 +0,0 @@ -roomNo = data_get($data, SeasonTicketContractReserve::FIELD_TARGET_ROOM_LIST_ROOM_NO, ""); - $this->roomType = data_get($data, SeasonTicketContractReserve::FIELD_TARGET_ROOM_LIST_ROOM_TYPE, ""); - $this->roomRecordNo = intval(data_get($data, SeasonTicketContractReserve::FIELD_TARGET_ROOM_LIST_RECORD_NO, 0)); - parent::__construct($data); - } - - public function toArray(): array - { - return [ - SeasonTicketContractReserve::FIELD_TARGET_ROOM_LIST_ROOM_NO => $this->roomNo, - SeasonTicketContractReserve::FIELD_TARGET_ROOM_LIST_ROOM_TYPE => $this->roomType, - SeasonTicketContractReserve::FIELD_TARGET_ROOM_LIST_RECORD_NO => $this->roomRecordNo, - ]; - } -} diff --git a/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Candidate.php b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Candidate.php new file mode 100644 index 0000000..e1da1de --- /dev/null +++ b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Candidate.php @@ -0,0 +1,47 @@ +emailSendTarget = count(data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_EMAIL_SEND_TARGET, [])) !== 0; + $this->entryRecordNo = intval(data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_RECORD_NO, 0)); + $this->planName = data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_STATUS, ""); + $this->planName = data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_PLAN, ""); + $this->entryNo = data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_PLAN, ""); + $this->name = data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_NAME, ""); + $this->entryDatetime = DateUtil::parse(data_get($data, SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_ROOM_NO, "")); + $this->wantsToUseStartDate = DateUtil::parse(data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_WANTS_TO_USE_START_DATE, "")); + $this->carAmount = intval(data_get($data, SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_CAR_AMOUNT, 0)); + $this->email = data_get($data, SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_ROOM_NO, ""); + + parent::__construct($data); + } + + public function toArray(): array + { + return [ + SeasonTicketContractSelection::FIELD_CANDIDATE_EMAIL_SEND_TARGET => $this->emailSendTarget ? ["〇"] : [], + SeasonTicketContractSelection::FIELD_CANDIDATE_LIST_RECORD_NO => $this->entryRecordNo, + ]; + } +} diff --git a/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Entry.php b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Entry.php new file mode 100644 index 0000000..6c5f9d9 --- /dev/null +++ b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Entry.php @@ -0,0 +1,42 @@ +entryRecordNo = intval(data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_RECORD_NO, 0)); + $this->status = data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_STATUS, ""); + $this->planName = data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_PLAN, ""); + $this->entryNo = data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_ENTRY_NO, ""); + $this->name = data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_NAME, ""); + $this->entryDatetime = DateUtil::parse(data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_ENTRY_DATETIME, "")); + $this->wantsToUseStartDate = DateUtil::parse(data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_WANTS_TO_USE_START_DATE, "")); + $this->carAmount = intval(data_get($data, SeasonTicketContractSelection::FIELD_ENTRY_LIST_CAR_AMOUNT, 0)); + + parent::__construct($data); + } + + public function toArray(): array + { + return [ + SeasonTicketContractSelection::FIELD_ENTRY_LIST_RECORD_NO => $this->entryRecordNo, + ]; + } +} diff --git a/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Result.php b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Result.php new file mode 100644 index 0000000..d5498e5 --- /dev/null +++ b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/Result.php @@ -0,0 +1,42 @@ +roomRecordNo = intval(data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_ROOM_RECORD_NO, 0)); + $this->roomNo = data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_ROOM_NO, ""); + $this->roomType = data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_ROOM_TYPE, ""); + + $this->entryRecordNo = intval(data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_ENTRY_RECORD_NO, 0)); + $this->status = data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_STATUS, ""); + $this->planName = data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_PLAN, ""); + $this->entryNo = data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_ENTRY_NO, ""); + $this->name = data_get($data, SeasonTicketContractSelection::FIELD_RESULT_LIST_NAME, ""); + + parent::__construct($data); + } + + public function toArray(): array + { + return [ + SeasonTicketContractSelection::FIELD_RESULT_LIST_ROOM_RECORD_NO => $this->roomRecordNo, + SeasonTicketContractSelection::FIELD_RESULT_LIST_ENTRY_RECORD_NO => $this->entryRecordNo, + ]; + } +} diff --git a/app/Kintone/Models/SubTable/SeasonTicketContractSelection/TargetRoom.php b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/TargetRoom.php new file mode 100644 index 0000000..4cd5a84 --- /dev/null +++ b/app/Kintone/Models/SubTable/SeasonTicketContractSelection/TargetRoom.php @@ -0,0 +1,31 @@ +roomNo = data_get($data, SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_ROOM_NO, ""); + $this->roomType = data_get($data, SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_ROOM_TYPE, ""); + $this->roomRecordNo = intval(data_get($data, SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_RECORD_NO, 0)); + parent::__construct($data); + } + + public function toArray(): array + { + return [ + SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_ROOM_NO => $this->roomNo, + SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_ROOM_TYPE => $this->roomType, + SeasonTicketContractSelection::FIELD_TARGET_ROOM_LIST_RECORD_NO => $this->roomRecordNo, + ]; + } +} diff --git a/app/Logic/SeasonTicketContractSelectionManager.php b/app/Logic/SeasonTicketContractSelectionManager.php new file mode 100644 index 0000000..f127f6c --- /dev/null +++ b/app/Logic/SeasonTicketContractSelectionManager.php @@ -0,0 +1,242 @@ +selection = SelectionModel::find($recordNo); + } else { + $this->selection = new SelectionModel(); + $this->selection->status = SelectionStatus::START; + } + } + + public function makeCandidates() + { + if ($this->selection->status !== SelectionStatus::START) { + new AppCommonException( + sprintf( + "ステータス不正 候補者一覧を設定するにはステータスが%[s]である必要がある。現ステータス[%s]", + SelectionStatus::START, + $this->selection->status, + ) + ); + } + + $candidateList = $this->selection->candidateList->empty(); + + // 対象の申込を取得する + // TODO 空き車室に対応したプランの申込に絞る必要あり + $access = EntryModel::getAccess(); + + $getBaseQuery = function () { + $baseQuery = EntryModel::getQuery(); + $baseQuery->where(EntryModel::FIELD_PARKING_NAME, $this->selection->parkingName) + ->orderByAsc(EntryModel::FIELD_ENTRY_DATETIME); + if ($this->selection->useStartDate) { + $useStartDateQuery = function (KintoneRecordQuery $query) { + return $query->whereNull(EntryModel::FIELD_USE_START_DATE) + ->orWhereDate(EntryModel::FIELD_USE_START_DATE, $this->selection->useStartDate, KintoneRecordQueryOperator::GE, true); + }; + $baseQuery->where($useStartDateQuery); + } + return $baseQuery; + }; + + $reserveQuery = $getBaseQuery()->whereIn(EntryModel::FIELD_STATUS, [Status::RESERVE]); + $waitQuery = $getBaseQuery()->whereIn(EntryModel::FIELD_STATUS, [Status::WAIT_EMPTY]); + + + $reserveList = $access->all($reserveQuery); + $waitList = $access->all($waitQuery); + + $list = $reserveList->merge($waitList); + + foreach ($list as $ele) { + $candidate = new Candidate(); + $candidate->entryRecordNo = $ele->getRecordId(); + $candidateList->push($candidate); + } + $this->selection->candidateList = $candidateList; + + $this->selection->status = SelectionStatus::TARGET_SELECTING; + + return $this; + } + + public function sendNotine() + { + // メール送信 + + return $this; + } + + public function makeResult() + { + $messages = $this->doSelection(); + + $this->selection->selectionMessage = $messages->implode("\r\n"); + + return $this; + } + + public function save() + { + // レコード保存 + $this->selection->save(); + + return $this; + } + + public function entry(int $recordNo, string $hashPassword) + { + if (!$this->checkHash($recordNo, $hashPassword)) { + throw new AppCommonException("認証エラー"); + } + + $list = $this->selection->entryList; + + if ($this->hasAlreadyEnterd($recordNo)) { + // すでにエントリー済みのためスキップ + return $this; + } + + $entry = new Entry(); + $entry->entryRecordNo = $recordNo; + + $list->push($entry); + + $this->selection->entryList = $list; + + return $this; + } + + public function hasAlreadyEnterd(int $recordNo): bool + { + $list = $this->selection->entryList; + return $list->search(function (Entry $item) use ($recordNo) { + return $recordNo === $item->entryRecordNo; + }) !== false; + } + + public function getSelection() + { + return $this->selection; + } + + 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 + { + $expect = $this->getHash($recordNo); + return $expect === $hash; + } + + + // 抽選用------------------------ + + private function doSelection() + { + /** + * @var Collection + */ + $messages = collect(); + + $resultList = $this->selection->resultList->empty(); + + // 候補者 + if ($this->selection->entryList->isEmpty()) { + $this->selection->status = SelectionStatus::FAILED; + $messages->push("契約希望者不在のため、選考不調"); + return $messages; + } + + + // 希望者一覧のコピー + $entryList = $this->selection->entryList->empty(); + foreach ($this->selection->entryList as $entry) { + $entryList->push($entry); + } + + // 選考結果の設定 + $totalRoomAmount = 0; + foreach ($this->selection->targetRoomList as $room) { + + $entry = $this->selectionByRoom($room, $entryList); + + if ($entry) { + $result = new Result(); + $result->entryRecordNo = $entry->entryRecordNo; + $result->roomRecordNo = $room->roomRecordNo; + $totalRoomAmount += $entry->carAmount; + $resultList->push($result); + } + } + + if ($this->selection->targetRoomList->count() < $totalRoomAmount) { + $messages->push("!警告:空き枠以上の台数を抽選したため調整してください"); + } + + + $this->selection->status = $resultList->isNotEmpty() ? + SelectionStatus::COMPLETE : SelectionStatus::FAILED; + + $this->selection->resultList = $resultList; + + return $messages; + } + /** + * @param TargetRoom $room + * @param Collection $entries + */ + private function selectionByRoom(TargetRoom $room, Collection $entries): ?Entry + { + + // 予約者確認 + $entry = $entries->filter(function (Entry $entry) { + return $entry->status === Status::RESERVE; + })->sort(function (Entry $a, Entry $b) { + return $a->entryDatetime->lt($b->entryDatetime) ? -1 : 1; + })->first(); + + + if ($entry) { + return $entry; + } + + // 空き待ちで抽選 + $entry = $entries->filter(function (Entry $entry) { + return $entry->status === Status::WAIT_EMPTY; + })->shuffle(intval(DateUtil::now()->timestamp))->first(); + + if ($entry) { + return $entry; + } + + return null; + } +} diff --git a/app/Util/DateUtil.php b/app/Util/DateUtil.php index 92e0b15..8d2a74e 100644 --- a/app/Util/DateUtil.php +++ b/app/Util/DateUtil.php @@ -24,8 +24,11 @@ class DateUtil return new Carbon(); } - public static function parse(string $source): Carbon|null + public static function parse(?string $source): Carbon|null { + if ($source === null) { + return null; + } $date = Carbon::parse($source); if ($date->isValid()) { return $date->timezone(config('app.timezone')); diff --git a/app/Util/LoggingUtil.php b/app/Util/LoggingUtil.php new file mode 100644 index 0000000..d114762 --- /dev/null +++ b/app/Util/LoggingUtil.php @@ -0,0 +1,41 @@ + $e->getMessage(), + '_file' => $e->getFile(), + '_line' => $e->getLine(), + '_exceptionType' => $e::class, + ]; + } +} diff --git a/config/kintone.php b/config/kintone.php index 0b87b03..801490b 100644 --- a/config/kintone.php +++ b/config/kintone.php @@ -33,7 +33,7 @@ return [ ...App\Kintone\Models\SeasonTicketContract::setConfig(), ...App\Kintone\Models\SeasonTicketContractPlan::setConfig(), ...App\Kintone\Models\SeasonTicketContractEntry::setConfig(), - ...App\Kintone\Models\SeasonTicketContractReserve::setConfig(), + ...App\Kintone\Models\SeasonTicketContractSelection::setConfig(), ...App\Kintone\Models\PaymentPlan::setConfig(), ...App\Kintone\Models\GeneralApplication::setConfig(), ...App\Kintone\Models\FAQ::setConfig(),