You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
9.5KB

  1. <?php
  2. namespace App\Logic;
  3. use App\Email\Members\SelectionNotice;
  4. use App\Exceptions\AppCommonException;
  5. use App\Exceptions\GeneralErrorMessageException;
  6. use App\Kintone\KintoneRecordQuery;
  7. use App\Kintone\KintoneRecordQueryOperator;
  8. use App\Kintone\Models\DropDown\SeasonTicketContractEntry\Status;
  9. use App\Kintone\Models\SeasonTicketContractEntry as EntryModel;
  10. use App\Kintone\Models\SeasonTicketContractSelection as SelectionModel;
  11. use App\Kintone\Models\SubTable\SeasonTicketContractSelection\Entry;
  12. use App\Kintone\Models\DropDown\SeasonTicketContractSelection\SelectionStatus;
  13. use App\Kintone\Models\SeasonTicketContractEntry;
  14. use App\Kintone\Models\SubTable\SeasonTicketContractSelection\Candidate;
  15. use App\Kintone\Models\SubTable\SeasonTicketContractSelection\Result;
  16. use App\Kintone\Models\SubTable\SeasonTicketContractSelection\TargetRoom;
  17. use App\Util\DateUtil;
  18. use Illuminate\Support\Collection;
  19. class SeasonTicketContractSelectionManager
  20. {
  21. private SelectionModel $selection;
  22. public function __construct(int|SelectionModel|null $recordNo = null)
  23. {
  24. if (is_int($recordNo)) {
  25. $this->selection = SelectionModel::find($recordNo);
  26. } else if ($recordNo instanceof SelectionModel) {
  27. $this->selection = $recordNo;
  28. } else {
  29. $this->selection = new SelectionModel();
  30. $this->selection->status = SelectionStatus::START;
  31. }
  32. }
  33. public function makeCandidates()
  34. {
  35. if ($this->selection->status !== SelectionStatus::START) {
  36. new AppCommonException(
  37. sprintf(
  38. "ステータス不正 候補者一覧を設定するにはステータスが%[s]である必要がある。現ステータス[%s]",
  39. SelectionStatus::START,
  40. $this->selection->status,
  41. )
  42. );
  43. }
  44. $candidateList = $this->selection->candidateList->empty();
  45. // 対象の申込を取得する
  46. // TODO 空き車室に対応したプランの申込に絞る必要あり
  47. $access = EntryModel::getAccess();
  48. $getBaseQuery = function () {
  49. $baseQuery = EntryModel::getQuery();
  50. $baseQuery->where(EntryModel::FIELD_PARKING_NAME, $this->selection->parkingName)
  51. ->orderByAsc(EntryModel::FIELD_ENTRY_DATETIME);
  52. if ($this->selection->useStartDate) {
  53. $useStartDateQuery = function (KintoneRecordQuery $query) {
  54. return $query->whereNull(EntryModel::FIELD_USE_START_DATE)
  55. ->orWhereDate(EntryModel::FIELD_USE_START_DATE, $this->selection->useStartDate, KintoneRecordQueryOperator::GE, true);
  56. };
  57. $baseQuery->where($useStartDateQuery);
  58. }
  59. return $baseQuery;
  60. };
  61. $reserveQuery = $getBaseQuery()->whereIn(EntryModel::FIELD_STATUS, [Status::RESERVE]);
  62. $waitQuery = $getBaseQuery()->whereIn(EntryModel::FIELD_STATUS, [Status::WAIT_EMPTY]);
  63. $reserveList = $access->all($reserveQuery);
  64. $waitList = $access->all($waitQuery);
  65. $list = $reserveList->merge($waitList);
  66. foreach ($list as $ele) {
  67. $candidate = new Candidate();
  68. $candidate->entryRecordNo = $ele->getRecordId();
  69. $candidateList->push($candidate);
  70. }
  71. $this->selection->candidateList = $candidateList;
  72. $this->selection->status = SelectionStatus::TARGET_SELECTING;
  73. // 検索用文字列の作成
  74. $search = collect();
  75. foreach ($this->selection->candidateList as $ele) {
  76. $search->push(sprintf("c%dc", $ele->entryRecordNo));
  77. }
  78. $this->selection->searchCandidateListRecordNo = $search->implode(",");
  79. return $this;
  80. }
  81. public function sendNotice()
  82. {
  83. // メール送信
  84. foreach ($this->selection->candidateList as $candidate) {
  85. if ($candidate->emailSendTarget) {
  86. $entry = SeasonTicketContractEntry::find($candidate->entryRecordNo);
  87. if (!in_array($entry->status, [Status::RESERVE, Status::WAIT_EMPTY])) {
  88. continue;
  89. }
  90. if (!$entry->email) {
  91. continue;
  92. }
  93. $email = new SelectionNotice($this->selection, $entry);
  94. (new EmailManager($email->setEmail($entry->email)))->confirm();
  95. }
  96. }
  97. $this->selection->status = SelectionStatus::ENTRY_ACCEPTING;
  98. return $this;
  99. }
  100. public function makeResult()
  101. {
  102. $messages = $this->doSelection();
  103. $this->selection->selectionMessage = $messages->implode("\r\n");
  104. // 検索用文字列の作成
  105. $search = collect();
  106. foreach ($this->selection->resultList as $ele) {
  107. $search->push(sprintf("r%dr", $ele->entryRecordNo));
  108. }
  109. $this->selection->searchResultListEntryRecordNo = $search->implode(",");
  110. return $this;
  111. }
  112. public function save()
  113. {
  114. // レコード保存
  115. $this->selection->save();
  116. return $this;
  117. }
  118. public function entry(int $recordNo, string $hashPassword)
  119. {
  120. if (!$this->checkHash($recordNo, $hashPassword)) {
  121. throw new AppCommonException("認証エラー");
  122. }
  123. if ($this->selection->status !== SelectionStatus::ENTRY_ACCEPTING) {
  124. throw new GeneralErrorMessageException("募集期間が終了しています");
  125. }
  126. $list = $this->selection->entryList;
  127. if ($this->hasAlreadyEnterd($recordNo)) {
  128. // すでにエントリー済みのためスキップ
  129. return $this;
  130. }
  131. $entry = new Entry();
  132. $entry->entryRecordNo = $recordNo;
  133. $list->push($entry);
  134. $this->selection->entryList = $list;
  135. // 検索用文字列の作成
  136. $search = collect();
  137. foreach ($this->selection->entryList as $ele) {
  138. $search->push(sprintf("e%de", $ele->entryRecordNo));
  139. }
  140. $this->selection->searchEntryListRecordNo = $search->implode(",");
  141. return $this;
  142. }
  143. public function hasAlreadyEnterd(int $recordNo): bool
  144. {
  145. $list = $this->selection->entryList;
  146. return $list->search(function (Entry $item) use ($recordNo) {
  147. return $recordNo === $item->entryRecordNo;
  148. }) !== false;
  149. }
  150. public function getSelection()
  151. {
  152. return $this->selection;
  153. }
  154. public function getEntry(int $entryRecordNo)
  155. {
  156. return SeasonTicketContractEntry::find($entryRecordNo);
  157. }
  158. /**
  159. * @param integer $recordNo 申込レコード番号
  160. * @return string
  161. */
  162. public function getHash(int $recordNo): string
  163. {
  164. $source = sprintf("%010d-%010d", $recordNo, intval($this->selection->getRecordId()));
  165. return hash('sha256', $source);
  166. }
  167. public function checkHash(int $recordNo, string $hash): bool
  168. {
  169. $expect = $this->getHash($recordNo);
  170. return $expect === $hash;
  171. }
  172. // 抽選用------------------------
  173. private function doSelection()
  174. {
  175. /**
  176. * @var Collection<int, string>
  177. */
  178. $messages = collect();
  179. $resultList = $this->selection->resultList->empty();
  180. // 候補者
  181. if ($this->selection->entryList->isEmpty()) {
  182. $this->selection->status = SelectionStatus::FAILED;
  183. $messages->push("契約希望者不在のため、選考不調");
  184. return $messages;
  185. }
  186. // 希望者一覧のコピー
  187. $entryList = $this->selection->entryList->empty();
  188. foreach ($this->selection->entryList as $entry) {
  189. $entryList->push($entry);
  190. }
  191. // 選考結果の設定
  192. $totalRoomAmount = 0;
  193. foreach ($this->selection->targetRoomList as $room) {
  194. $entry = $this->selectionByRoom($room, $entryList);
  195. if ($entry) {
  196. $result = new Result();
  197. $result->entryRecordNo = $entry->entryRecordNo;
  198. $result->roomRecordNo = $room->roomRecordNo;
  199. $result->name = $entry->name;
  200. $result->entryNo = $entry->entryNo;
  201. $totalRoomAmount += $entry->carAmount;
  202. $resultList->push($result);
  203. }
  204. }
  205. if ($this->selection->targetRoomList->count() < $totalRoomAmount) {
  206. $messages->push("!警告:空き枠以上の台数を抽選したため調整してください");
  207. }
  208. $this->selection->status = $resultList->isNotEmpty() ?
  209. SelectionStatus::RESULT_DECISION : SelectionStatus::FAILED;
  210. $this->selection->resultList = $resultList;
  211. return $messages;
  212. }
  213. /**
  214. * @param TargetRoom $room
  215. * @param Collection<int, Entry> $entries
  216. */
  217. private function selectionByRoom(TargetRoom $room, Collection $entries): ?Entry
  218. {
  219. // 予約者確認
  220. $entry = $entries->filter(function (Entry $entry) {
  221. return $entry->status === Status::RESERVE;
  222. })->sort(function (Entry $a, Entry $b) {
  223. return $a->entryDatetime->lt($b->entryDatetime) ? -1 : 1;
  224. })->first();
  225. if ($entry) {
  226. return $entry;
  227. }
  228. // 空き待ちで抽選
  229. $entry = $entries->filter(function (Entry $entry) {
  230. return $entry->status === Status::WAIT_EMPTY;
  231. })->shuffle(intval(DateUtil::now()->timestamp))->first();
  232. if ($entry) {
  233. return $entry;
  234. }
  235. return null;
  236. }
  237. }