Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その42 )( 貸出承認画面の作成2 )
概要
Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( その41 )( 貸出承認画面の作成 ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
- 貸出承認画面の作成
- パラメータで指定されている貸出申請 ID の「申請する」が選択された書籍と申請理由の一覧を DB から取得して表示する処理の実装
- 貸出承認画面の作成
参照したサイト・書籍
目次
- LendingapprovalParamForm, ApplyingBookForm, LendingapprovalForm クラスの作成
- LendingAppDao クラスの変更
- LendingBookDao クラスの変更
- messages_ja_JP.properties にエラーメッセージを定義する
- LendingBookApprovalResultValues クラスの作成
- LendingapprovalService クラスの作成
- LendingapprovalController クラスの変更
- lendingapproval.html の変更
- 動作確認
- 次回は。。。
手順
LendingapprovalParamForm, ApplyingBookForm, LendingapprovalForm クラスの作成
貸出申請画面では一覧内の各書籍の入力データ用のクラスを Dto クラスで定義したのですが、Form クラスの方が良さそうに思えたので、今回は全て Form クラスで作成します。
src/main/java/ksbysample/webapp/lending/web/lendingapp の下の LendingappParamForm.java を src/main/java/ksbysample/webapp/lending/web/lendingapproval の下へ LendingapprovalParamForm.java というファイル名でコピーします。リンク先の内容 になります。
src/main/java/ksbysample/webapp/lending/web/lendingapproval の下に ApplyingBookForm.java を作成します。作成後、リンク先の内容 に変更します。
src/main/java/ksbysample/webapp/lending/web/lendingapproval の下に LendingapprovalForm.java を作成します。作成後、リンク先の内容 に変更します。
LendingAppDao クラスの変更
lending_app_id だけではなく status も指定してデータを取得するメソッドを追加します。src/main/java/ksbysample/webapp/lending/dao の下の LendingAppDao.java を リンク先の内容 に変更します。
src/main/resources/META-INF/ksbysample/webapp/lending/dao/LendingAppDao の下に selectByIdAndStatus.sql を作成します。作成後、リンク先の内容 に変更します。
LendingBookDao クラスの変更
「申請する」が選択された書籍だけを取得するメソッドを追加します。src/main/java/ksbysample/webapp/lending/dao の下の LendingBookDao.java を リンク先の内容 に変更します。
src/main/resources/META-INF/ksbysample/webapp/lending/dao/LendingBookDao の下に selectByLendingAppIdAndLendingAppFlg.sql を作成します。作成後、リンク先の内容 に変更します。
messages_ja_JP.properties にエラーメッセージを定義する
- エラーメッセージを定義します。src/main/resources の下の messages_ja_JP.properties を リンク先の内容 に変更します。
LendingBookApprovalResultValues クラスの作成
- lending_book.approval_result にセットする「承認」「却下」の値を定義する Values 列挙型を定義します。src/main/java/ksbysample/webapp/lending/values の下に LendingBookApprovalResultValues.java を作成します。作成後、リンク先の内容 に変更します。
LendingapprovalService クラスの作成
- src/main/java/ksbysample/webapp/lending/web/lendingapproval の下に LendingapprovalService.java を作成します。作成後、リンク先の内容 に変更します。
LendingapprovalController クラスの変更
- src/main/java/ksbysample/webapp/lending/web/lendingapproval の下の LendingapprovalController.java を リンク先の内容 に変更します。
lendingapproval.html の変更
- src/main/resources/templates/lendingapproval の下の lendingapproval.html を リンク先の内容 に変更します。
動作確認
動作確認します。データは以下の状態です。
Gradle projects View から bootRun タスクを実行して Tomcat を起動します。
ブラウザを起動し http://localhost:8080/lendingapproval へアクセスします ( lendingAppId パラメータが指定されていない )。最初はログイン画面が表示されますので ID に "tanaka.taro@sample.com"、Password に "taro" を入力して、「次回から自動的にログインする」をチェックせずに「ログイン」ボタンをクリックします。
共通エラー画面が表示され「貸出申請IDが指定されていません。」のエラーメッセージが表示されることが確認できます。
http://localhost:8080/lendingapproval?lendingAppId=a にアクセスします ( lendingAppId パラメータに数値以外が指定されている )。
こちらも共通エラー画面が表示され「貸出申請IDが指定されていません。」のエラーメッセージが表示されることが確認できます。
http://localhost:8080/lendingapproval?lendingAppId=106 にアクセスします ( lendingAppId パラメータに指定された貸出申請ID のデータが存在しない )。
貸出承認画面が表示されますが、「指定された貸出申請IDでは貸出申請されておりません。」のエラーメッセージが表示されることが確認できます。
http://localhost:8080/lendingapproval?lendingAppId=105 にアクセスします。
貸出承認画面が表示され、貸出申請ID、ステータス、申請者、「申請する」が選択された書籍一覧が表示されることが確認できます。
Ctrl+F2 を押して Tomcat を停止します。
一旦 commit します。
次回は。。。
「確定」ボタンをクリックした時の処理を実装します。
ソースコード
LendingapprovalParamForm.java
package ksbysample.webapp.lending.web.lendingapproval; import lombok.Data; import javax.validation.constraints.DecimalMax; import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotNull; @Data public class LendingapprovalParamForm { @NotNull @DecimalMin(value = "1") @DecimalMax(value = "9223372036854775807") private Long lendingAppId; }
ApplyingBookForm.java
package ksbysample.webapp.lending.web.lendingapproval; import ksbysample.webapp.lending.entity.LendingBook; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.BeanUtils; @Data @NoArgsConstructor public class ApplyingBookForm { private Long lendingBookId; private String isbn; private String bookName; private String lendingAppReason; private String approvalResult; private String approvalReason; private Long version; public ApplyingBookForm(LendingBook lendingBook) { BeanUtils.copyProperties(lendingBook, this); } }
LendingapprovalForm.java
package ksbysample.webapp.lending.web.lendingapproval; import ksbysample.webapp.lending.entity.LendingApp; import ksbysample.webapp.lending.entity.LendingBook; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; import java.util.stream.Collectors; @Data @NoArgsConstructor public class LendingapprovalForm { private LendingApp lendingApp; private String username; private List<ApplyingBookForm> applyingBookFormList; public void setApplyingBookFormList(List<LendingBook> lendingBookList) { this.applyingBookFormList = lendingBookList.stream() .map(ApplyingBookForm::new) .collect(Collectors.toList()); } }
LendingAppDao.java
package ksbysample.webapp.lending.dao; import ksbysample.webapp.lending.entity.LendingApp; import ksbysample.webapp.lending.util.doma.ComponentAndAutowiredDomaConfig; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.Insert; import org.seasar.doma.Select; import org.seasar.doma.Update; import org.seasar.doma.jdbc.SelectOptions; import java.util.List; /** */ @Dao @ComponentAndAutowiredDomaConfig public interface LendingAppDao { /** * @param lendingAppId * @return the LendingApp entity */ @Select LendingApp selectById(Long lendingAppId); @Select(ensureResult = true) LendingApp selectByIdAndVersion(Long lendingAppId, Long version); @Select LendingApp selectById(Long lendingAppId, SelectOptions options); @Select(ensureResult = true) LendingApp selectByIdAndVersion(Long lendingAppId, Long version, SelectOptions options); @Select LendingApp selectByIdAndStatus(Long lendingAppId, List<String> statusList); @Select LendingApp selectByIdAndStatus(Long lendingAppId, List<String> statusList, SelectOptions options); /** * @param entity * @return affected rows */ @Insert int insert(LendingApp entity); /** * @param entity * @return affected rows */ @Update int update(LendingApp entity); /** * @param entity * @return affected rows */ @Delete int delete(LendingApp entity); }
- selectByIdAndStatus メソッドを追加します。
selectByIdAndStatus.sql
select /*%expand*/* from lending_app where lending_app_id = /* lendingAppId */105 /*%if statusList != null */ and status in /* statusList */('3', '4') /*%end*/
LendingBookDao.java
package ksbysample.webapp.lending.dao; import ksbysample.webapp.lending.entity.LendingBook; import ksbysample.webapp.lending.util.doma.ComponentAndAutowiredDomaConfig; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.Insert; import org.seasar.doma.Select; import org.seasar.doma.Update; import org.seasar.doma.jdbc.SelectOptions; import java.util.List; /** */ @Dao @ComponentAndAutowiredDomaConfig public interface LendingBookDao { /** * @param lendingBookId * @return the LendingBook entity */ @Select LendingBook selectById(Long lendingBookId); @Select(ensureResult = true) LendingBook selectByIdAndVersion(Long lendingBookId, Long version); @Select List<LendingBook> selectByLendingAppId(Long lendingAppId); @Select List<LendingBook> selectByLendingAppId(Long lendingAppId, SelectOptions options); @Select List<LendingBook> selectByLendingAppIdAndLendingAppFlg(Long lendingAppId, String lendingAppFlg); @Select List<LendingBook> selectByLendingAppIdAndLendingAppFlg(Long lendingAppId, String lendingAppFlg, SelectOptions options); /** * @param entity * @return affected rows */ @Insert int insert(LendingBook entity); /** * @param entity * @return affected rows */ @Update int update(LendingBook entity); @Update(include = {"lendingState"}) int updateLendingState(LendingBook entity); @Update(include = {"lendingAppFlg", "lendingAppReason"}) int updateLendingAppFlgAndReason(LendingBook entity); /** * @param entity * @return affected rows */ @Delete int delete(LendingBook entity); }
- selectByLendingAppIdAndLendingAppFlg メソッドを追加します。
selectByLendingAppIdAndLendingAppFlg.sql
select /*%expand*/* from lending_book where lending_app_id = /* lendingAppId */105 /*%if lendingAppFlg != null */ and lending_app_flg = /* lendingAppFlg */'1' /*%end*/ order by lending_book_id
messages_ja_JP.properties
LendingappForm.lendingAppId.emptyerr=貸出申請IDが指定されていません。 LendingappForm.lendingApp.nodataerr=指定された貸出申請IDのデータが登録されておりません。 LendingappForm.lendingBookDtoList.notExistApply=最低1冊「申請する」を選択してください。 LendingappForm.lendingBookDtoList.emptyReason=「申請する」を選択している書籍の申請理由を入力してください。 LendingapprovalParamForm.lendingAppId.emptyerr=貸出申請IDが指定されていません。 LendingapprovalForm.lendingApp.nodataerr=指定された貸出申請IDでは貸出申請されておりません。
LendingapprovalParamForm.lendingAppId.emptyerr
,LendingapprovalForm.lendingApp.nodataerr
を追加します。
LendingBookApprovalResultValues.java
package ksbysample.webapp.lending.values; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum LendingBookApprovalResultValues implements Values { APPROVAL("1", "承認") , REJECT("2", "却下"); private final String value; private final String text; }
LendingapprovalService.java
package ksbysample.webapp.lending.web.lendingapproval; import ksbysample.webapp.lending.dao.LendingAppDao; import ksbysample.webapp.lending.dao.LendingBookDao; import ksbysample.webapp.lending.dao.UserInfoDao; import ksbysample.webapp.lending.entity.LendingApp; import ksbysample.webapp.lending.entity.LendingBook; import ksbysample.webapp.lending.entity.UserInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; import static ksbysample.webapp.lending.values.LendingAppStatusValues.APPLOVED; import static ksbysample.webapp.lending.values.LendingAppStatusValues.PENDING; import static ksbysample.webapp.lending.values.LendingBookLendingAppFlgValues.APPLY; @Service public class LendingapprovalService { @Autowired private LendingAppDao lendingAppDao; @Autowired private UserInfoDao userInfoDao; @Autowired private LendingBookDao lendingBookDao; public void setDispData(Long lendingAppId, LendingapprovalForm lendingapprovalForm) { LendingApp lendingApp = lendingAppDao.selectByIdAndStatus(lendingAppId , Arrays.asList(PENDING.getValue(), APPLOVED.getValue())); String username = ""; if (lendingApp != null) { UserInfo userInfo = userInfoDao.selectById(lendingApp.getLendingUserId()); username = userInfo.getUsername(); } List<LendingBook> lendingBookList = lendingBookDao.selectByLendingAppIdAndLendingAppFlg(lendingAppId, APPLY.getValue()); lendingapprovalForm.setLendingApp(lendingApp); lendingapprovalForm.setUsername(username); lendingapprovalForm.setApplyingBookFormList(lendingBookList); } }
LendingapprovalController.java
package ksbysample.webapp.lending.web.lendingapproval; import ksbysample.webapp.lending.exception.WebApplicationRuntimeException; import ksbysample.webapp.lending.helper.message.MessagesPropertiesHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/lendingapproval") public class LendingapprovalController { @Autowired private LendingapprovalService lendingapprovalService; @Autowired private MessagesPropertiesHelper messagesPropertiesHelper; @RequestMapping public String index(@Validated LendingapprovalParamForm lendingapprovalParamForm , BindingResult bindingResult , LendingapprovalForm lendingapprovalForm , BindingResult bindingResultOfLendingapprovalForm) { if (bindingResult.hasErrors()) { throw new WebApplicationRuntimeException( messagesPropertiesHelper.getMessage("LendingapprovalParamForm.lendingAppId.emptyerr", null)); } // 画面に表示するデータを取得する lendingapprovalService.setDispData(lendingapprovalParamForm.getLendingAppId(), lendingapprovalForm); // 指定された貸出申請IDで申請中、承認済のデータがない場合には、貸出承認画面上にエラーメッセージを表示する if (lendingapprovalForm.getLendingApp() == null) { bindingResultOfLendingapprovalForm.reject("LendingapprovalForm.lendingApp.nodataerr"); } return "lendingapproval/lendingapproval"; } @RequestMapping(value = "/complete", method = RequestMethod.POST) public String complete() { return "lendingapproval/lendingapproval"; } }
@Autowired private LendingapprovalService lendingapprovalService;
を追加します。@Autowired private MessagesPropertiesHelper messagesPropertiesHelper;
を追加します。- index メソッドの以下の点を変更します。
- 引数に lendingapprovalParamForm, bindingResult, lendingapprovalForm, bindingResultOfLendingapprovalForm の4つを追加します。
- 内部の処理を実装します。
lendingapproval.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <title>貸出承認</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"/> <link th:replace="common/head-cssjs"/> <style> .content-wrapper { background-color: #fffafa; } .selected-library { color: #ffffff !important; font-size: 100%; font-weight: 700; } .box-body.no-padding { padding-bottom: 10px !important; } .table>tbody>tr>td , .table>tbody>tr>th , .table>tfoot>tr>td , .table>tfoot>tr>th , .table>thead>tr>td , .table>thead>tr>th { padding: 5px; font-size: 90%; } .jp-gothic { font-family: Verdana, "游ゴシック", YuGothic, "Hiragino Kaku Gothic ProN", Meiryo, sans-serif; } .btn-approval.active { background-color: #00a65a; } .btn-reject.active { background-color: #dd4b39; } </style> </head> <!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. --> <body class="skin-blue layout-top-nav"> <div class="wrapper"> <!-- Main Header --> <div th:replace="common/mainparts :: main-header"></div> <!-- Full Width Column --> <div class="content-wrapper"> <div class="container"> <!-- Content Header (Page header) --> <section class="content-header"> <h1>貸出承認</h1> </section> <!-- Main content --> <section class="content"> <div class="row"> <div class="col-xs-12"> <form id="lendingapprovalForm" method="post" action="/lendingapproval/complete" th:action="@{/lendingapproval/complete}" th:object="${lendingapprovalForm}"> <div class="alert alert-danger" th:if="${#fields.hasGlobalErrors()}"> <p th:each="err : ${#fields.globalErrors()}" th:text="${err}">共通エラーメッセージ表示エリア</p> </div> <div class="box" th:if="*{lendingApp != null}"> <div class="box-body no-padding"> <div class="col-xs-6 no-padding"> <table class="table table-bordered"> <colgroup> <col width="30%"/> <col width="70%"/> </colgroup> <tr> <th class="bg-purple">貸出申請ID</th> <td th:text="*{lendingApp.lendingAppId}">1</td> </tr> <tr> <th class="bg-purple">ステータス</th> <td th:text="${@vh.getText('LendingAppStatusValues', lendingapprovalForm.lendingApp.status)}">申請中</td> </tr> <tr> <th class="bg-purple">申請者</th> <td th:text="*{username}">田中 太郎</td> </tr> </table> </div> <br/> <table class="table"> <colgroup> <col width="5%"/> <col width="15%"/> <col width="15%"/> <col width="20%"/> <col width="20%"/> <col width="25%"/> </colgroup> <thead class="bg-purple"> <tr> <th>No.</th> <th>ISBN</th> <th>書名</th> <th>申請理由</th> <th>承認/却下</th> <th>却下理由</th> </tr> </thead> <tbody class="jp-gothic"> <tr th:each="applyingBookForm, iterStat : *{applyingBookFormList}"> <td th:text="${iterStat.count}">1</td> <td th:text="${applyingBookForm.isbn}">978-4-7741-6366-6</td> <td th:text="${applyingBookForm.bookName}">GitHub実践入門</td> <td th:text="${applyingBookForm.lendingAppReason}">開発で使用する為</td> <td> <div class="btn-group-sm" data-toggle="buttons"> <label class="btn btn-default btn-approval"> <input type="radio" th:field="*{applyingBookFormList[__${iterStat.index}__].approvalResult}" th:value="${@vh.getValue('LendingBookApprovalResultValues', 'APPROVAL')}"/> 承認 </label> <label class="btn btn-default btn-reject"> <input type="radio" th:field="*{applyingBookFormList[__${iterStat.index}__].approvalResult}" th:value="${@vh.getValue('LendingBookApprovalResultValues', 'REJECT')}"/> 却下 </label> </div> </td> <td> <input type="text" class="form-control input-sm" th:field="*{applyingBookFormList[__${iterStat.index}__].approvalReason}"/> </td> <input type="hidden" th:field="*{applyingBookFormList[__${iterStat.index}__].lendingBookId}"/> <input type="hidden" th:field="*{applyingBookFormList[__${iterStat.index}__].version}"/> </tr> </tbody> </table> <div class="text-center"> <button class="btn bg-blue js-btn-complete"><i class="fa fa-check-square-o"></i> 確定</button> </div> </div> </div> </form> </div> </div> </section> <!-- /.content --> </div> <!-- /.container --> </div> </div> <!-- ./wrapper --> <script th:replace="common/bottom-js"></script> <script type="text/javascript"> <!-- $(document).ready(function() { $(".js-btn-complete").click(function(){ $("#lendingapprovalForm").submit(); return false; }); }); --> </script> </body> </html>
<form id="lendingapprovalForm" ...>
を<div class="box">
の上へ移動します。</form>
も対応する位置へ移動します。<form id="lendingapprovalForm" ...>
の下に共通エラーメッセージ表示エリアである<div class="alert alert-danger" th:if="${#fields.hasGlobalErrors()}"> ... </div>
を追加します。- table タグ内でデータを表示する部分のタグが th タグを使用していたので td タグに変更します。
- 貸出申請ID, ステータス, 申請者にデータが表示されるよう
th:text="..."
を追加します。ステータスは ValuesHelper クラスを利用してth:text="${@vh.getText('LendingAppStatusValues', lendingapprovalForm.lendingApp.status)}"
でコードに対応する文字列が表示されるようにします。 <tbody class="jp-gothic">
の下の<tr>
→<tr th:each="applyingBookForm, iterStat : *{applyingBookFormList}">
へ変更します。貸出申請画面と同様にイテレーション処理中のステータスを取るための変数iterStat
を記述します。- 一覧表の各行にデータが表示されるように
th:text="..."
を追加します。 - 承認/却下の選択ボタンは以下の点を変更します。
th:field="*{applyingBookFormList[__${iterStat.index}__].approvalResult}"
を記述して Form オブジェクトと関連付けます。th:value="${@vh.getValue('LendingBookApprovalResultValues', '...')}"
を追加します。
- 却下理由の入力フィールドに
th:field="..."
を記述して Form オブジェクトと関連付けます。 - 更新時にキー項目が必要になるので、
<input type="hidden" th:field="*{lendingBookList[__${iterStat.index}__].lendingBookId}"/>
,<input type="hidden" th:field="*{applyingBookFormList[__${iterStat.index}__].version}"/>
を追加します。
履歴
2016/01/10
初版発行。