Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

280 rindas
8.7KB

  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. return $this;
  74. }
  75. public function sendNotice()
  76. {
  77. // メール送信
  78. foreach ($this->selection->candidateList as $candidate) {
  79. if ($candidate->emailSendTarget) {
  80. $entry = SeasonTicketContractEntry::find($candidate->entryRecordNo);
  81. if (!in_array($entry->status, [Status::RESERVE, Status::WAIT_EMPTY])) {
  82. continue;
  83. }
  84. if (!$entry->email) {
  85. continue;
  86. }
  87. $email = new SelectionNotice($this->selection, $entry);
  88. (new EmailManager($email->setEmail($entry->email)))->confirm();
  89. }
  90. }
  91. $this->selection->status = SelectionStatus::ENTRY_ACCEPTING;
  92. return $this;
  93. }
  94. public function makeResult()
  95. {
  96. $messages = $this->doSelection();
  97. $this->selection->selectionMessage = $messages->implode("\r\n");
  98. return $this;
  99. }
  100. public function save()
  101. {
  102. // レコード保存
  103. $this->selection->save();
  104. return $this;
  105. }
  106. public function entry(int $recordNo, string $hashPassword)
  107. {
  108. if (!$this->checkHash($recordNo, $hashPassword)) {
  109. throw new AppCommonException("認証エラー");
  110. }
  111. if ($this->selection->status !== SelectionStatus::ENTRY_ACCEPTING) {
  112. throw new GeneralErrorMessageException("募集期間が終了しています");
  113. }
  114. $list = $this->selection->entryList;
  115. if ($this->hasAlreadyEnterd($recordNo)) {
  116. // すでにエントリー済みのためスキップ
  117. return $this;
  118. }
  119. $entry = new Entry();
  120. $entry->entryRecordNo = $recordNo;
  121. $list->push($entry);
  122. $this->selection->entryList = $list;
  123. return $this;
  124. }
  125. public function hasAlreadyEnterd(int $recordNo): bool
  126. {
  127. $list = $this->selection->entryList;
  128. return $list->search(function (Entry $item) use ($recordNo) {
  129. return $recordNo === $item->entryRecordNo;
  130. }) !== false;
  131. }
  132. public function getSelection()
  133. {
  134. return $this->selection;
  135. }
  136. public function getEntry(int $entryRecordNo)
  137. {
  138. return SeasonTicketContractEntry::find($entryRecordNo);
  139. }
  140. /**
  141. * @param integer $recordNo 申込レコード番号
  142. * @return string
  143. */
  144. public function getHash(int $recordNo): string
  145. {
  146. $source = sprintf("%010d-%010d", $recordNo, intval($this->selection->getRecordId()));
  147. return hash('sha256', $source);
  148. }
  149. public function checkHash(int $recordNo, string $hash): bool
  150. {
  151. $expect = $this->getHash($recordNo);
  152. return $expect === $hash;
  153. }
  154. // 抽選用------------------------
  155. private function doSelection()
  156. {
  157. /**
  158. * @var Collection<int, string>
  159. */
  160. $messages = collect();
  161. $resultList = $this->selection->resultList->empty();
  162. // 候補者
  163. if ($this->selection->entryList->isEmpty()) {
  164. $this->selection->status = SelectionStatus::FAILED;
  165. $messages->push("契約希望者不在のため、選考不調");
  166. return $messages;
  167. }
  168. // 希望者一覧のコピー
  169. $entryList = $this->selection->entryList->empty();
  170. foreach ($this->selection->entryList as $entry) {
  171. $entryList->push($entry);
  172. }
  173. // 選考結果の設定
  174. $totalRoomAmount = 0;
  175. foreach ($this->selection->targetRoomList as $room) {
  176. $entry = $this->selectionByRoom($room, $entryList);
  177. if ($entry) {
  178. $result = new Result();
  179. $result->entryRecordNo = $entry->entryRecordNo;
  180. $result->roomRecordNo = $room->roomRecordNo;
  181. $result->name = $entry->name;
  182. $result->entryNo = $entry->entryNo;
  183. $totalRoomAmount += $entry->carAmount;
  184. $resultList->push($result);
  185. }
  186. }
  187. if ($this->selection->targetRoomList->count() < $totalRoomAmount) {
  188. $messages->push("!警告:空き枠以上の台数を抽選したため調整してください");
  189. }
  190. $this->selection->status = $resultList->isNotEmpty() ?
  191. SelectionStatus::RESULT_DECISION : SelectionStatus::FAILED;
  192. $this->selection->resultList = $resultList;
  193. return $messages;
  194. }
  195. /**
  196. * @param TargetRoom $room
  197. * @param Collection<int, Entry> $entries
  198. */
  199. private function selectionByRoom(TargetRoom $room, Collection $entries): ?Entry
  200. {
  201. // 予約者確認
  202. $entry = $entries->filter(function (Entry $entry) {
  203. return $entry->status === Status::RESERVE;
  204. })->sort(function (Entry $a, Entry $b) {
  205. return $a->entryDatetime->lt($b->entryDatetime) ? -1 : 1;
  206. })->first();
  207. if ($entry) {
  208. return $entry;
  209. }
  210. // 空き待ちで抽選
  211. $entry = $entries->filter(function (Entry $entry) {
  212. return $entry->status === Status::WAIT_EMPTY;
  213. })->shuffle(intval(DateUtil::now()->timestamp))->first();
  214. if ($entry) {
  215. return $entry;
  216. }
  217. return null;
  218. }
  219. }