/*
 * MosP - Mind Open Source Project    http://www.mosp.jp/
 * Copyright (C) MIND Co., Ltd.       http://www.e-mind.co.jp/
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package jp.mosp.kintai.common.action;

import java.util.Date;
import java.util.List;
import java.util.Map;

import jp.mosp.common.CommonConst;
import jp.mosp.common.common.MospException;
import jp.mosp.common.common.MospUtility;
import jp.mosp.common.utils.DateUtil;
import jp.mosp.common.utils.StringUtil;
import jp.mosp.common.utils.TimeUtil;
import jp.mosp.common.utils.VariousUtil;
import jp.mosp.kintai.common.dao.CtWorkflowCommentDao;
import jp.mosp.kintai.common.dao.CtWorkflowDao;
import jp.mosp.kintai.common.dto.CtWorkflowCommentDto;
import jp.mosp.kintai.common.dto.CtWorkflowDto;
import jp.mosp.kintai.common.dto.MKihonDto;
import jp.mosp.kintai.common.part.HolidayRequestPart;
import jp.mosp.kintai.common.part.KintaiApprovalPart;
import jp.mosp.kintai.common.part.KintaiCommonPart;
import jp.mosp.kintai.common.part.LaborAgreementPart;
import jp.mosp.kintai.common.part.PaidHolidayPart;
import jp.mosp.kintai.common.part.SubstituteHolidayPart;
import jp.mosp.kintai.common.part.WorkflowPart;
import jp.mosp.kintai.common.vo.MosPWorkFlowVo;
import jp.mosp.kintai.kintai.dto.MSyukkinboDto;
import jp.mosp.kintai.payroll.dao.KdSubstituteHolidayDao;
import jp.mosp.kintai.payroll.dto.KdSubstituteHolidayDto;
import jp.mosp.kintai.schedule.dto.MScheduleDto;
import jp.mosp.kintai.workflow.dao.AmRouteDao;
import jp.mosp.kintai.workflow.dao.AmUnitDao;
import jp.mosp.kintai.workflow.dao.AttendanceRouteDao;
import jp.mosp.kintai.workflow.dao.CtOvertimeRequestDao;
import jp.mosp.kintai.workflow.dao.KtHolidayRequestDao;
import jp.mosp.kintai.workflow.dao.PmAttendanceDao;
import jp.mosp.kintai.workflow.dto.CtOvertimeRequestDto;
import jp.mosp.kintai.workflow.dto.KtHolidayRequestDto;

/**
 * @author yoshida
 *
 */
public abstract class MosPWorkflowAction extends AttendanceTotalAction {
	
	/**
	 * 承認用ユニットDAO取得
	 * @return	初期化済みAmUnitDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected AmUnitDao unitDao() throws Exception {
		return part().approval().getAmUnitDao();
	}
	
	/**
	 * 承認用ルートDAOの取得
	 * @return	初期化済みAmRouteDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected AmRouteDao routeDao() throws Exception {
		return part().approval().getAmRouteDao();
	}
	
	/**
	 * 勤怠承認用ルートDAOの取得
	 * @return	初期化済みAttendanceRouteDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected AttendanceRouteDao atRouteDao() throws Exception {
		return part().kintaiApproval().getAttendanceRouteDao();
	}
	
	/**
	 * 勤怠承認用ルートマスタDAOの取得
	 * @return	初期化済みPmAttendanceDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected PmAttendanceDao pmAttendDao() throws Exception {
		return part().kintaiApproval().getPmAttendanceDao();
	}
	
	/**
	 * 残業申請テーブルDAOの取得
	 * @return	初期化済みCtOvertimeRequestDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected CtOvertimeRequestDao overTimeDao() throws Exception {
		return part().overtime().getCtOvertimeRequestDao();
	}
	
	/**
	 * 休暇申請テーブルDAOの取得
	 * @return	初期化済みKtHolidayRequestDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected KtHolidayRequestDao holidayDao() throws Exception {
		return part().holidayRequest().getKtHolidayRequestDao();
	}
	
	/**
	 * ワークフローテーブルDAOの取得
	 * @return	初期化済みCtWorkflowDaoインスタンス
	 * @throws Exception	例外処理が発生した場合
	 */
	protected CtWorkflowDao workflowDao() throws Exception {
		return part().workflow().getCtWorkflowDao();
	}
	
	/**
	 * 現状承認ユニットコード一覧（AM_UNIT）
	 * @return　現状の承認ユニットコードリスト
	 * @throws Exception	例外処理が発生した場合
	 */
	protected String[][] getCurrentUnitArray() throws Exception {
		return part().approval().getCurrentUnitArray();
	}
	
	/**
	 * 利用可能勤怠承認ルート情報一覧
	 * @param kCode 対象社員コード
	 * @param targetDate 対象年月日
	 * @return 現状所属承認ユニットコードリスト
	 * <p>
	 * [0]…一次承認ルートコード
	 * [1]…二次承認ルートコード
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected String[] getTargetRouteInfo(String kCode, Date targetDate) throws Exception {
		return part().kintaiApproval().getTargetRouteInfo(kCode, user.getKCode(), targetDate);
	}
	
	/**
	 * 勤怠承認処理
	 * @param kcode			対象者
	 * @param targetDate	対象年月日(yyyy/MM/dd)
	 * @param reason		承認理由
	 * @param comment		承認コメント
	 * @param approver		承認者
	 * @throws Exception	例外処理が発生した場合
	 */
	protected void runDayApprovalProcess(String kcode, String targetDate, String reason, String comment, String approver)
			throws Exception {
		// 情報の取得
		MSyukkinboDto dto = kintai().getSyukkinboInfo(kcode, targetDate, true);
		// 排他確認
		chkExist(dto);
		// 承認情報設定
		dto.setNinsyo(approver);
		dto.setSnRiyuu(reason);
		dto.setSnComent(comment);
		dto.setSnTimes(DateUtil.getDateTime(DateUtil.getNowDate()));
		dto.setUpdtTimes(DateUtil.getDateTime(DateUtil.getNowDate()));
		// 承認処理
		syukkinboDao().update(dto);
		// DTOの再取得
		chkUpdate(kintai().getSyukkinboInfo(dto.getKCode(), dto.getHizuke(), false));
	}
	
	@Override
	protected void prepareTableLock() throws Exception {
		super.prepareTableLock();
		// 対象テーブル追加
		addTargetTable(workflowDao(), true);
		addTargetTable(AmRouteDao.TABLE, false);
	}
	
	/**
	 * ワークフロー取得
	 * @param workflow		ワークフロー番号
	 * @return	検索結果(DTO)
	 * @throws Exception	例外処理が発生した場合
	 */
	protected CtWorkflowDto getWorkflowDto(long workflow) throws Exception {
		return workflowDao().findForKey(workflow);
	}
	
	/**
	 * ワークフロー取得
	 * @param workflow	対象ワークフロー番号
	 * @return 検索結果(DTO)
	 * @throws Exception	例外処理が発生した場合
	 */
	protected CtWorkflowDto getWorkflowDto(String workflow) throws Exception {
		return getWorkflowDto(Long.parseLong(workflow));
	}
	
	/**
	 * 未承認状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	未承認状況の是非
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean isStatusRequest(long workflow) throws Exception {
		return part().workflow().isStatusRequest(workflow);
	}
	
	/**
	 * 承認済み状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	承認済み状況の是非
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean isStatusApproved(long workflow) throws Exception {
		return part().workflow().isStatusApproved(workflow);
	}
	
	/**
	 * 差戻状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	差戻状況の是非
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean isStatusReturned(long workflow) throws Exception {
		return part().workflow().isStatusReturned(workflow);
	}
	
	/**
	 * 下書き状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	下書き状況の是非
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean isStatusDraft(long workflow) throws Exception {
		return part().workflow().isStatusDraft(workflow);
	}
	
	/**
	 * 承認対象者一覧取得
	 * @param targetDate	対象年月日
	 * @return	承認対象者一覧リスト
	 * @throws Exception	例外処理が発生した場合
	 */
	protected List<MKihonDto> getApprovalEmployeeList(Date targetDate) throws Exception {
		return part().kintaiApproval().getApprovalEmployeeList(userBasis.getKCode(), targetDate);
	}
	
	/**
	 * 申請登録
	 * <p>
	 * 必ず{@link #unLockTables()}を実行すること。
	 * </p>
	 * @param kCode		対象社員コード
	 * @param isDraft 下書きフラグ
	 * @return workflow		ワークフロー番号
	 * @throws Exception	例外処理が発生した場合
	 */
	protected long requestRegist(String kCode, boolean isDraft) throws Exception {
		CtWorkflowDto dto = new CtWorkflowDto();
		// 現状を設定
		if (isDraft) {
			// 下書き
			dto.setCurrentStatus(WorkflowPart.getStatusCodeArray()[4]);
		} else {
			// 申請中
			dto.setCurrentStatus(WorkflowPart.getStatusCodeArray()[0]);
		}
		// 承認階層を設定
		dto.setLayerNumber(0);
		String targetRouteCode = part().kintaiApproval().getRequestRouteCode(kCode);
		dto.setRtCode(targetRouteCode);
		long workflow = 0;
		// テーブルロック
		lockTables();
		// ルートコードを設定
		if (StringUtil.isNotNull(targetRouteCode)) {
			String maxNo = workflowDao().findForMax();
			if (StringUtil.isNull(maxNo)) {
				maxNo = "-1";
			}
			// 最大値をセット
			dto.setWorkflow(Long.parseLong(maxNo) + 1);
			workflowDao().insert(dto);
			workflow = dto.getWorkflow();
		}
		return workflow;
	}
	
	/**
	 * ワークフロー更新処理
	 * @param dto			対象ワークフローDTO
	 * @return 更新できた場合true、そうでない場合false
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean workflowUpdate(CtWorkflowDto dto) throws Exception {
		// ルートコードを確認
		if (VariousUtil.chkExistDto(routeDao().findForKey(dto.getRtCode()))) {
			chkExclusive(workflowDao().findForUpdate(dto.getWorkflow()), dto);
			workflowDao().update(dto);
			chkUpdate(getWorkflowDto(dto.getWorkflow()));
			return true;
		}
		// エラーメッセージ
		addErrMessage(getMessage(CommonConst.MSG_TARGET_NOT_EXIST, CommonConst.NAM_APPROVAL_ROUTE));
		return false;
	}
	
	/**
	 * 差戻コメント一覧
	 * @param target		対象社員コード
	 * @param workflow		対象ワークフロー番号
	 * @throws Exception	例外処理が発生した場合
	 */
	protected void setReturnedComment(String target, long workflow) throws Exception {
		MosPWorkFlowVo vo = (MosPWorkFlowVo)getVo();
		vo.setCommentFields(part().workflow().getWorkflowCommentArray(target, workflow));
	}
	
	/**
	 * ワークフローコメント登録
	 * @param workflow			ワークフロー番号
	 * @param kcode				社員コード
	 * @param targetKCode		対象者
	 * @param comment			コメント
	 * @throws Exception	例外処理が発生した場合
	 */
	protected void registComment(long workflow, String kcode, String targetKCode, String comment) throws Exception {
		CtWorkflowCommentDto dto = new CtWorkflowCommentDto();
		dto.setWorkflow(workflow);
		dto.setKCode(kcode);
		dto.setTarget(targetKCode);
		dto.setAuthorComment(comment);
		// ワークフローコメント用DAO準備
		CtWorkflowCommentDao dao = part().workflow().getCtWorkflowCommentDao();
		String maxCommentId = dao.findForMax();
		if (StringUtil.isNull(maxCommentId)) {
			maxCommentId = "-1";
		} else {
			dao.findForUpdate(Long.parseLong(maxCommentId));
		}
		// 最大値をセット
		dto.setCommentId(Long.parseLong(maxCommentId) + 1);
		dao.insert(dto);
		chkInsert(dao.findForKey(dto.getCommentId()));
	}
	
	/**
	 * 最新対象社員コメント取得
	 * @param kCode			対象社員コード
	 * @param workflow		対象ワークフロー
	 * @return	コメント
	 * @throws MospException	MosP例外処理が発生した場合
	 * @throws Exception	例外処理が発生した場合
	 */
	protected String getCurrentWorkflowComment(String kCode, long workflow) throws Exception {
		CtWorkflowCommentDto dto = part().workflow().getWorkflowCommentForTarget(kCode, workflow);
		if (chkExistDto(dto)) {
			return dto.getAuthorComment();
		}
		return "";
	}
	
	/**
	 * 勤怠承認ステータスコード取得 
	 * @param workflow			対象ワークフロー番号
	 * @return	勤怠承認ステータスコード
	 * @throws Exception	例外処理が発生した場合
	 */
	protected String toKintaiApprovalStatus(long workflow) throws Exception {
		return part().workflow().toKintaiApprovalStatus(workflow);
	}
	
	/**
	 * 勤怠承認状況名称取得
	 * @param snRiyuu			勤怠承認状況コード
	 * @return	勤怠承認状況名称
	 * @throws Exception	例外処理が発生した場合
	 */
	protected String getApprovalName(String snRiyuu) throws Exception {
		return part().kintaiApproval().getApprovalName(snRiyuu);
	}
	
	/**
	 * 対象年月日対象ユニットコード承認者判断
	 * @param unitcode		対象ユニットコード
	 * @param targetDate	対象年月日
	 * @return
	 * <p>
	 * 対象年月日承認ユニット情報DTOがある場合true、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean isApprover(String unitcode, Date targetDate) throws Exception {
		return part().approval().isApprover(unitcode, user.getKCode(), targetDate);
	}
	
	/**
	 * 残業申請情報取得
	 * @param voucher		対象伝票番号
	 * @return	残業申請情報DTO
	 * @throws Exception	例外処理が発生した場合
	 */
	protected CtOvertimeRequestDto getOverTimeRequest(int voucher) throws Exception {
		return overTimeDao().findForKey(voucher);
	}
	
	/**
	 * 対象年月日残業申請情報取得
	 * @param kCode				対象社員コード
	 * @param targetDate		対象年月日
	 * @return	残業申請情報DTO
	 * @throws Exception	例外処理が発生した場合
	 */
	protected CtOvertimeRequestDto getOverTimeRequest(String kCode, Date targetDate) throws Exception {
		return part().overtime().getOverTimeRequest(kCode, targetDate);
	}
	
	/**
	 * 対象年月日休暇申請情報取得
	 * @param kCode				対象社員コード
	 * @param targetDate		対象年月日
	 * @return	休暇申請情報DTO
	 * @throws Exception	例外処理が発生した場合
	 */
	protected KtHolidayRequestDto getHolidayRequest(String kCode, Date targetDate) throws Exception {
		return part().holidayRequest().getHolidayRequest(kCode, targetDate);
	}
	
	/**
	 * 申請種類配列取得
	 * @return	申請種類配列
	 */
	protected String[][] getApplicationArray() {
		return new String[][]{
		// 日次勤怠
			{ CommonConst.CODE_DAILY_APPROVAL, CommonConst.NAM_DAILY_APPROVAL },
			// 残業申請
			{ CommonConst.CODE_OVER_TIME_REQUEST, CommonConst.NAM_OVER_TIME_REQUEST },
			// 休暇申請
			{ CommonConst.CODE_HOLIDAY_REQUEST, CommonConst.NAM_HOLIDAY_REQUEST }, };
	}
	
	/**
	 * 残業申請承認処理
	 * @param dto		対象DTO
	 * @return
	 * <p>
	 * 残業申請を承認できた場合true、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean runOverTimeRequestApproved(CtOvertimeRequestDto dto) throws Exception {
		return runWorkflowApprovalProcess(dto.getKCode(), dto.getWorkDate(), dto.getWorkflow(),
				CommonConst.NAM_OVER_TIME_REQUEST);
	}
	
	/**
	 * 休暇申請承認処理
	 * @param dto		対象DTO
	 * @return
	 * <p>
	 * 休暇申請を承認できた場合true、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean runHolidayRequestApproved(KtHolidayRequestDto dto) throws Exception {
		// 承認された場合、出勤簿、予定簿の情報を変更
		if (VariousUtil.isNotNull(dto)
				&& checkHolidayRequest(dto)
				&& runWorkflowApprovalProcess(dto.getKCode(), dto.getHolidayDate(), dto.getWorkflow(),
						CommonConst.NAM_HOLIDAY_REQUEST)) {
			// 休暇時間申請と予定簿のマージ
			MScheduleDto scheduleDto = kintai().getScheduleInfo(dto.getKCode(), dto.getHolidayDate());
			margeHolidayRequest(scheduleDto);
			// 休暇時間申請と出勤簿のマージ
			MSyukkinboDto syukkinboDto = kintai().getSyukkinboInfo(dto.getKCode(), dto.getHolidayDate());
			margeHolidayRequest(syukkinboDto);
			// 登録
			registSchedule(scheduleDto);
			registSyukkinbo(syukkinboDto);
			// 時間外代休の場合
			if (HolidayRequestPart.isSubHoliday(dto) && part().mospcode().useSubstituteHolidayFunction()
					&& WorkflowPart.isLastLayer(workflowDao().findForKey(dto.getWorkflow()))) {
				// 利用状況、残日数の情報を更新する。
				registSubstituteInfo(dto);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * 申請承認処理
	 * @param kCode			社員コード
	 * @param targetDate	対象年月日
	 * @param workflow		対象ワークフロー番号
	 * @param requestName	対象申請種類
	 * @return
	 * <p>
	 * 申請を承認できた場合true、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean runWorkflowApprovalProcess(String kCode, Date targetDate, long workflow, String requestName)
			throws Exception {
		return runWorkflowProcess(kCode, targetDate, workflow, requestName, "", true);
	}
	
	/**
	 * 申請差戻処理
	 * @param kCode			社員コード
	 * @param targetDate	対象年月日
	 * @param workflow		対象ワークフロー番号
	 * @param requestName	対象申請種類
	 * @param inputComment	差戻しコメント
	 * @return
	 * <p>
	 * 申請を差戻しできた場合true、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean runWorkflowReturnProcess(String kCode, Date targetDate, long workflow, String requestName,
			String inputComment) throws Exception {
		return runWorkflowProcess(kCode, targetDate, workflow, requestName, inputComment, false);
	}
	
	/**
	 * ワークフロー処理
	 * @param kCode			対象社員コード
	 * @param targetDate	対象年月日
	 * @param workflow		対象ワークフロー番号
	 * @param requestName	対象申請種類
	 * @param inputComment	入力コメント
	 * @param isApproval	承認・差戻判断
	 * @return
	 * <p>
	 * 申請を処理できた場合true、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean runWorkflowProcess(String kCode, Date targetDate, long workflow, String requestName,
			String inputComment, boolean isApproval) throws Exception {
		// ワークフロー取得
		CtWorkflowDto dto = workflowDao().findForUpdate(workflow);
		// 承認ルート取得
		String[] aryRouteInfo = getTargetRouteInfo(kCode, targetDate);
		// 承認状況
		String status = dto.getCurrentStatus();
		// 処理フラグ
		boolean canProcess = false;
		if (WorkflowPart.isRequestLayer(dto)) {
			// 一次
			if (isApprover(aryRouteInfo[0], targetDate) && WorkflowPart.isRequest(status)) {
				if (isApproval) {
					WorkflowPart.shiftApproved(dto);
				} else {
					WorkflowPart.shiftReturned(dto);
				}
				canProcess = true;
			} else {
				// エラーメッセージ
				addDenyApprovalMessage(kCode, targetDate, requestName);
			}
		} else if (WorkflowPart.is1stLayer(dto)) {
			// 二次
			if (isApprover(aryRouteInfo[1], targetDate) && WorkflowPart.isApproved(status)) {
				if (isApproval) {
					WorkflowPart.shiftLastApproved(dto);
				} else {
					WorkflowPart.shiftLastReturned(dto);
				}
				canProcess = true;
			} else {
				// エラーメッセージ
				addDenyApprovalMessage(kCode, targetDate, requestName);
			}
		}
		// 差戻し中の場合
		if (WorkflowPart.isReturned(status)) {
			// エラーメッセージ
			addAlreadyReturnedMessage(kCode, targetDate, requestName);
		}
		// ワークフロー処理
		if (canProcess) {
			if (isApproval) {
				// ワークフローの登録
				return workflowUpdate(dto);
			} else {
				// ワークフローの更新
				workflowDao().update(dto);
				chkUpdate(workflowDao().findForKey(dto.getWorkflow()));
				// 差戻しコメントの登録
				registComment(dto.getWorkflow(), userBasis.getKCode(), kCode, inputComment);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 日次勤怠承認処理
	 * @param kCode			対象社員コード
	 * @param targetDate	対象年月日
	 * @param comment		承認コメント
	 * @param isApproval	承認処理フラグ
	 * @return
	 * <p>
	 * 日次勤怠承認処理が成功すればtrue、そうでない場合false
	 * </p>
	 * @throws Exception	例外処理が発生した場合
	 */
	protected boolean runDailyApprovalProcess(String kCode, Date targetDate, String comment, boolean isApproval)
			throws Exception {
		MSyukkinboDto dto = kintai().getSyukkinboInfo(kCode, targetDate);
		String reason = "";
		// 申請中の場合
		if (KintaiApprovalPart.isRequest(dto)) {
			reason = CommonConst.NUM_ATTENDANCE_1ST_RETURNED;
			if (isApproval) {
				reason = CommonConst.NUM_ATTENDANCE_1ST_APPROVED;
			}
		} else if (KintaiApprovalPart.is1stApproved(dto)) {
			// 一次承認済みの場合
			reason = CommonConst.NUM_ATTENDANCE_2ND_RETURNED;
			if (isApproval) {
				reason = CommonConst.NUM_ATTENDANCE_2ND_APPROVED;
			}
		} else if (KintaiApprovalPart.is1stReturned(dto) || KintaiApprovalPart.is2ndReturned(dto)) {
			// 差戻し中
			// エラーメッセージ
			addAlreadyReturnedMessage(kCode, targetDate, CommonConst.NAM_DAILY_APPROVAL);
		} else if (KintaiApprovalPart.is2ndApproved(dto)) {
			// 二次承認済み
			// エラーメッセージ
			addAlreadyApprovedMessage(kCode, targetDate, CommonConst.NAM_DAILY_APPROVAL);
		}
		// 承認者判断
		if (part().kintaiApproval().isApprover(kCode, userBasis.getKCode(), dto.getSnRiyuu(), targetDate)) {
			// 日次勤怠承認
			runDayApprovalProcess(kCode, DateUtil.convDateFormatted(targetDate), reason, comment, userBasis.getKCode());
			return true;
		} else {
			// エラーメッセージ
			addDenyApprovalMessage(kCode, targetDate, CommonConst.NAM_DAILY_APPROVAL);
		}
		return false;
	}
	
	/**
	 * 承認不可メッセージ出力
	 * @param kCode			対象社員コード
	 * @param targetDate	対象年月日
	 * @param requestName	対象申請種類
	 * @throws Exception	例外処理が発生した場合
	 */
	protected void addDenyApprovalMessage(String kCode, Date targetDate, String requestName) throws Exception {
		// エラーメッセージ
		addErrMessage(getMessage(CommonConst.MSG_CODE_DENY_APPROVAL, getApprovalMessage(kCode, targetDate, requestName,
				true)));
	}
	
	/**
	 * 承認済みメッセージ出力
	 * @param kCode			対象社員コード
	 * @param targetDate	対象年月日
	 * @param requestName	対象申請種類
	 * @throws Exception	例外処理が発生した場合
	 */
	protected void addAlreadyApprovedMessage(String kCode, Date targetDate, String requestName) throws Exception {
		// エラーメッセージ
		addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_APPROVED, getApprovalMessage(kCode, targetDate,
				requestName, false)));
	}
	
	/**
	 * 差戻し中メッセージ出力
	 * @param kCode			対象社員コード
	 * @param targetDate	対象年月日
	 * @param requestName	対象申請種類
	 * @throws Exception	例外処理が発生した場合
	 */
	protected void addAlreadyReturnedMessage(String kCode, Date targetDate, String requestName) throws Exception {
		// エラーメッセージ
		addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_RETURNED, getApprovalMessage(kCode, targetDate,
				requestName, false)));
	}
	
	/**
	 * 承認エラー用メッセージ生成
	 * @param kCode			対象社員コード
	 * @param targetDate	対象年月日
	 * @param requestName	対象申請種類
	 * @param isSeparate	分割メッセージ判断
	 * @return	承認エラー用メッセージ
	 * @throws Exception 	例外処理が発生した場合
	 */
	protected String getApprovalMessage(String kCode, Date targetDate, String requestName, boolean isSeparate)
			throws Exception {
		StringBuffer sb = new StringBuffer();
		sb.append(getKName(kCode));
		sb.append(CommonConst.MSG_OF);
		sb.append(DateUtil.convDateToFormatDateJP(targetDate));
		if (isSeparate) {
			sb.append(CommonConst.STR_UNDER_SEPARATOR);
		} else {
			sb.append(CommonConst.MSG_OF);
		}
		sb.append(requestName);
		return sb.toString();
	}
	
	/**
	 * 休暇申請・承認のチェック
	 * @param dto 対象DTO
	 * @return
	 * <p>
	 * 休暇申請が申請または承認できる場合true、そうでない場合false。
	 * </p>
	 * @throws Exception 	例外処理が発生した場合
	 */
	protected boolean checkHolidayRequest(KtHolidayRequestDto dto) throws Exception {
		if (VariousUtil.isNotNull(dto)) {
			String kCode = dto.getKCode();
			Date targetDate = dto.getHolidayDate();
			// 申請日のチェック
			if (KintaiApprovalPart.isApproved(part().kintai().getSyukkinboInfo(kCode, targetDate))) {
				// エラーメッセージ
				addErrMessage(getMessage(CommonConst.MSG_ALREADY_APPROVED, DateUtil
					.convDateFormattedByComma(targetDate)));
				return false;
			}
			// 申請日の重複チェック
			if (checkHolidayRequestDuplicate(dto)) {
				// 有休の場合
				if (HolidayRequestPart.isPaidHoliday(dto)) {
					// 残日数、残時間のチェック
					return checkRemainPaidHoliday(dto);
				} else if (HolidayRequestPart.isSubHoliday(dto) && part().mospcode().useSubstituteHolidayFunction()) {
					// 月60時間時間外による代休の場合
					// 代休の残日数のチェック
					return checkRemainSubstitute(dto);
				}
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 申請日の重複チェック
	 * @param dto 対象DTO
	 * @return 申請日の他の申請と重複しない場合true、そうでない場合false。
	 * @throws Exception  	例外処理が発生した場合
	 */
	protected boolean checkHolidayRequestDuplicate(KtHolidayRequestDto dto) throws Exception {
		if (VariousUtil.isNotNull(dto)) {
			String kCode = dto.getKCode();
			Date targetDate = dto.getHolidayDate();
			// 申請日の出勤簿
			MScheduleDto scheduleDto = part().kintai().getScheduleInfo(kCode, targetDate);
			// 申請日の休暇申請一覧
			List<KtHolidayRequestDto> currentList = part().holidayRequest().getDateRequestList(kCode, targetDate);
			// 同一の申請をカットする
			HolidayRequestPart.removeSameRequest(dto, currentList);
			// 勤怠情報設定
			setCalcParam();
			// 有休または月60時間時間外による代休の場合
			if (HolidayRequestPart.isPaidHoliday(dto) || HolidayRequestPart.isSubHoliday(dto)) {
				// 全休
				if (HolidayRequestPart.isAllHolidayType(dto)) {
					if (currentList.size() == 0) {
						return true;
					} else {
						// エラーメッセージ
						addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
							.convDateToFormatDateJPShort(targetDate)));
						return false;
					}
				} else if (HolidayRequestPart.isHalfHolidayType(dto)) {
					// 半休の申請の場合
					// 予定の確認
					if (VariousUtil.isNull(scheduleDto)) {
						// エラーメッセージ
						StringBuffer sb = new StringBuffer();
						sb.append(DateUtil.convDateToFormatDateJPShort(targetDate));
						sb.append(CommonConst.STR_UNDER_SEPARATOR);
						sb.append(getKName(kCode));
						addErrMessage(getMessage("WC2054", sb.toString()));
						return false;
					}
					// 同日に他の申請がある場合
					// 新申請分と旧申請分で時間の重複はないか
					for (KtHolidayRequestDto currentDto : currentList) {
						if (HolidayRequestPart.isAllHolidayType(currentDto)) {
							// 既存の申請が全休の場合
							addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
								.convDateToFormatDateJPShort(targetDate)));
							return false;
						} else if (HolidayRequestPart.isStartHalfHolidayType(currentDto)) {
							// 既存の申請が前半の場合
							if (HolidayRequestPart.isStartHalfHolidayType(dto)) {
								// 休暇種類が同一の場合
								addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
									.convDateToFormatDateJPShort(targetDate)));
								return false;
							}
						} else if (HolidayRequestPart.isQuitHalfHolidayType(currentDto)) {
							// 既存の申請が後半の場合
							if (HolidayRequestPart.isQuitHalfHolidayType(dto)) {
								// 休暇種類が同一の場合
								addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
									.convDateToFormatDateJPShort(targetDate)));
								return false;
							}
						} else {
							// 時間単位年休の使用可否
							// 既存の申請が時間単位年休の場合
							if (part().mospcode().useHourPaidHolidayFunction()
									&& HolidayRequestPart.isPaidHolidayTime(currentDto)) {
								// 予定出勤時刻
								int scheduleStart = KintaiCommonPart.getWorkStartTime(scheduleDto);
								// 予定退勤時刻
								int scheduleQuit = KintaiCommonPart.getWorkQuitTime(scheduleDto);
								// 前半休の申請の場合
								if (HolidayRequestPart.isStartHalfHolidayType(dto)) {
									scheduleQuit -= halfHoliday;
								}
								// 後半休の申請の場合
								if (HolidayRequestPart.isQuitHalfHolidayType(dto)) {
									scheduleStart += halfHoliday;
								}
								// 開始時刻
								int currentStartTime = currentDto.getStartTime();
								// 終了時刻
								int currentEndTime = currentDto.getEndTime();
								// 時間の重複チェック
								if (TimeUtil.coverPeriod(currentStartTime, currentEndTime, scheduleStart, scheduleQuit)) {
									// エラーメッセージ
									addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
										.convDateToFormatDateJPShort(targetDate)));
									return false;
								}
							}
						}
					}
					return true;
				} else if (HolidayRequestPart.isPaidHolidayTime(dto) && part().mospcode().useHourPaidHolidayFunction()) {
					// 時間単位年休の申請のチェック
					// 予定の確認
					if (VariousUtil.isNull(scheduleDto)) {
						// エラーメッセージ
						StringBuffer sb = new StringBuffer();
						sb.append(DateUtil.convDateToFormatDateJPShort(targetDate));
						sb.append(CommonConst.STR_UNDER_SEPARATOR);
						sb.append(getKName(kCode));
						addErrMessage(getMessage("WC2054", sb.toString()));
						return false;
					}
					int workStart = KintaiCommonPart.getWorkStartTime(scheduleDto);
					int workQuit = KintaiCommonPart.getWorkQuitTime(scheduleDto);
					int requestStart = dto.getStartTime();
					int requestEnd = dto.getEndTime();
					// 単位時間の確認
					if (requestEnd - requestStart != TimeUtil.convHourToMinutes(part().mospcode()
						.getHourPaidHolidayUnit())) {
						// エラーメッセージ
						StringBuffer sb = new StringBuffer();
						sb.append(DateUtil.convDateToFormatDateJPShort(targetDate));
						sb.append(CommonConst.STR_UNDER_SEPARATOR);
						sb.append(getKName(kCode));
						addErrMessage(getMessage("WC2055", sb.toString()));
						return false;
					}
					// 時間内の申請
					if (requestStart >= workStart && workQuit >= requestEnd) {
						// 同日に他の申請がある場合
						// 新申請分と旧申請分で時間の重複はないか
						for (KtHolidayRequestDto currentDto : currentList) {
							if (HolidayRequestPart.isAllHolidayType(currentDto)) {
								// 既存の申請が全休の場合
								addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
									.convDateToFormatDateJPShort(targetDate)));
								return false;
							} else if (HolidayRequestPart.isHalfHolidayType(currentDto)) {
								int holidayStart = workStart;
								int holidayEnd = workQuit;
								// 既存の申請が前半の場合
								if (HolidayRequestPart.isStartHalfHolidayType(currentDto)) {
									holidayEnd -= halfHoliday;
								} else if (HolidayRequestPart.isQuitHalfHolidayType(currentDto)) {
									// 既存の申請が後半の場合
									holidayStart += halfHoliday;
								}
								// 時間の重複チェック
								if (TimeUtil.coverPeriod(holidayStart, holidayEnd, requestStart, requestEnd)) {
									// エラーメッセージ
									addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
										.convDateToFormatDateJPShort(targetDate)));
									return false;
								}
							} else if (HolidayRequestPart.isPaidHolidayTime(currentDto)) {
								// 既存の申請が時間単位年休の場合
								// 開始時刻
								int currentStartTime = currentDto.getStartTime();
								// 終了時刻
								int currentEndTime = currentDto.getEndTime();
								// 時間の重複チェック
								if (TimeUtil.coverPeriod(currentStartTime, currentEndTime, requestStart, requestEnd)) {
									// エラーメッセージ
									addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
										.convDateToFormatDateJPShort(targetDate)));
									return false;
								}
							}
						}
					} else {
						// エラーメッセージ
						StringBuffer sb = new StringBuffer();
						sb.append(getKName(kCode));
						sb.append(CommonConst.STR_UNDER_SEPARATOR);
						sb.append(DateUtil.convDateToFormatDateJPShort(targetDate));
						addErrMessage(getMessage("WC2055", sb.toString()));
						return false;
					}
					return true;
				}
			} else {
				// その他
				if (currentList.size() != 0) {
					// エラーメッセージ
					addErrMessage(getMessage(CommonConst.MSG_CODE_SELECT_ALREADY_INSERT, DateUtil
						.convDateToFormatDateJPShort(targetDate)));
					return false;
				}
				// 休暇理由のチェック
				if (VariousUtil.checkCodeExist(dto.getReason(), aryHolidayReason)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * 有休の残日数、残時間のチェック
	 * @param dto 対象DTO
	 * @return 
	 * <p>
	 * 有給休暇申請の際、残日数・残時間情報を取得し、申請・承認が可能な場合true、そうでない場合false。
	 * </p>
	 * @throws Exception   	例外処理が発生した場合
	 */
	protected boolean checkRemainPaidHoliday(KtHolidayRequestDto dto) throws Exception {
		if (VariousUtil.isNotNull(dto)) {
			String kCode = dto.getKCode();
			Date targetDate = dto.getHolidayDate();
			// 入力日数
			double inputDays = 0;
			// 入力時間
			int inputHour = 0;
			if (HolidayRequestPart.isPaidHolidayAll(dto)) {
				inputDays++;
			}
			if (HolidayRequestPart.isPaidHolidayHalf(dto)) {
				inputDays = CommonConst.NUM_HALF_DAY;
			}
			if (HolidayRequestPart.isPaidHolidayTime(dto)) {
				inputHour = HolidayRequestPart.getHolidayTime(dto);
			}
			return checkRemainPaidHoliday(kCode, targetDate, inputDays, inputHour);
		}
		return false;
	}
	
	/**
	 * 有休の残日数、残時間のチェック
	 * @param kCode 対象社員コード
	 * @param targetDate 対象年月日
	 * @param inputDays 入力日数
	 * @param inputHour 入力時間
	 * @return 残日数・残時間情報を取得し、申請・承認が可能な場合true、そうでない場合false。
	 * @throws Exception   	例外処理が発生した場合
	 */
	protected boolean checkRemainPaidHoliday(String kCode, Date targetDate, double inputDays, int inputHour)
			throws Exception {
		// 対象年月日までの有休情報
		Map<String, Double> map = part().paidHoliday().getTargetPaidHolidayInfoMap(kCode, targetDate);
		// 労使協定情報
		setLaborAgreementInfo(kCode, targetDate);
		// 所定労働時間
		int prescribedTime = LaborAgreementPart.getPrescribedTime(agreement);
		if (inputDays > 0 || inputHour > 0) {
			// 有休の残日数、残時間のチェック
			if (!PaidHolidayPart.checkRemainInfoMap(PaidHolidayPart.calcRemainMap(inputDays, inputHour, map,
					prescribedTime))) {
				// エラーメッセージ
				StringBuffer sb = new StringBuffer();
				sb.append(getKName(kCode));
				sb.append(CommonConst.STR_UNDER_SEPARATOR);
				sb.append(DateUtil.convDateToFormatDateJPShort(targetDate));
				addErrMessage(getMessage("WC2050", sb.toString()));
			} else {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 時間外代休の残日数取得
	 * @param kCode 対象社員コード
	 * @param targetDate 対象年月日
	 * @return 時間外代休の残日数
	 * @throws Exception 	例外処理が発生した場合
	 */
	protected double getRemainSubstituteHoliday(String kCode, Date targetDate) throws Exception {
		return part().subHoliday().getRemainSubstituteHoliday(kCode, targetDate);
	}
	
	/**
	 * 時間外代休の残日数のチェック
	 * @param dto 対象DTO
	 * @return
	 * <p>
	 * 申請内容から残日数を超えない場合true、そうでない場合false。
	 * </p>
	 * @throws Exception 	例外処理が発生した場合
	 */
	protected boolean checkRemainSubstitute(KtHolidayRequestDto dto) throws Exception {
		if (VariousUtil.isNotNull(dto)) {
			// 利用日数
			double useDay = 0;
			// 全日の場合
			if (HolidayRequestPart.isSubHolidayAll(dto)) {
				useDay = 1;
			} else if (HolidayRequestPart.isSubHolidayHalf(dto)) {
				// 半日の場合
				useDay = CommonConst.NUM_HALF_DAY;
			}
			return checkRemainSubstitute(dto.getKCode(), dto.getHolidayDate(), useDay);
		}
		return false;
	}
	
	/**
	 * 時間外代休の残日数のチェック
	 * @param kCode 対象社員コード
	 * @param targetDate 対象年月日
	 * @param useDay 利用日数
	 * @return
	 * <p>
	 * 申請内容から残日数を超えない場合true、そうでない場合false。
	 * </p>
	 * @throws Exception 	例外処理が発生した場合
	 */
	protected boolean checkRemainSubstitute(String kCode, Date targetDate, double useDay) throws Exception {
		// 代休取得可能日数
		double remainDays = getRemainSubstituteHoliday(kCode, targetDate) - useDay;
		if (remainDays < 0) {
			// エラーメッセージ
			StringBuffer sb = new StringBuffer();
			sb.append(getKName(kCode));
			sb.append(CommonConst.STR_UNDER_SEPARATOR);
			sb.append(DateUtil.convDateToFormatDateJPShort(targetDate));
			addErrMessage(getMessage("WC2051", sb.toString()));
			return false;
		}
		return true;
	}
	
	private void registSubstituteInfo(KtHolidayRequestDto dto) throws Exception {
		// 社員コード
		String kCode = dto.getKCode();
		// 対象年月日
		Date targetDate = dto.getHolidayDate();
		// 労使協定情報設定
		setLaborAgreementInfo(kCode, targetDate);
		// 所定労働時間
		int prescribedTime = LaborAgreementPart.getPrescribedTime(agreement);
		// 利用時間
		int useTime = 0;
		if (HolidayRequestPart.isSubHolidayAll(dto)) {
			useTime = prescribedTime;
		}
		if (HolidayRequestPart.isSubHolidayHalf(dto)) {
			useTime = Double.valueOf(prescribedTime / 2d).intValue();
		}
		// DAOの準備
		KdSubstituteHolidayDao dao = part().subHoliday().getKdSubstituteHolidayDao();
		// 前月分
		Date oneMonthBefore = DateUtil.addMonth(getCalcAttendanceMonth(targetDate), -1);
		KdSubstituteHolidayDto oneMonthBeforeDto = dao.findForOccurMonth(kCode, MospUtility.getYear(oneMonthBefore),
				MospUtility.getMonth(oneMonthBefore), CommonConst.CODE_ACQUISITION_STATUS_REMAIN,
				CommonConst.CODE_DECISION_STATUS_FIXED);
		// 有効期間
		if (part().subHoliday().isPeriodTwoMonths()) {
			// 2ヶ月の場合
			// 前々月分
			Date twoMonthBefore = DateUtil.addMonth(getCalcAttendanceMonth(targetDate), -2);
			KdSubstituteHolidayDto twoMonthBeforeDto = dao.findForOccurMonth(kCode,
					MospUtility.getYear(twoMonthBefore), MospUtility.getMonth(twoMonthBefore),
					CommonConst.CODE_ACQUISITION_STATUS_REMAIN, CommonConst.CODE_DECISION_STATUS_FIXED);
			// 残時間
			// 前々月分
			int acquireTwo = 0;
			// 前々月分
			if (VariousUtil.isNotNull(twoMonthBeforeDto)) {
				acquireTwo = twoMonthBeforeDto.getPaymentAcquireTime() + twoMonthBeforeDto.getPaymentAcquireHoliday();
			}
			// 利用時間
			int useTimeTwo = 0;
			int useTimeOne = 0;
			if (acquireTwo > 0) {
				useTimeTwo = useTime;
				// 前々月の休日分＋平日分を超過する場合
				if (acquireTwo - useTime < 0) {
					useTimeTwo = acquireTwo;
					useTimeOne = useTime - acquireTwo;
				}
			} else {
				useTimeOne = useTime;
			}
			// 前々月分
			if (useTimeTwo > 0) {
				dto.setOccurDate2(DateUtil.convDateToStringYearMonthShort(twoMonthBefore));
				dto.setUseTime2(useTimeTwo);
				KdSubstituteHolidayDto twoCurrentDto = dao.findForUpdate(twoMonthBeforeDto.getSubstituteId());
				// 利用時間をセット
				twoCurrentDto.setUseHolidayTime(twoCurrentDto.getUseHolidayTime() + useTimeTwo);
				// 残数の確認
				if (SubstituteHolidayPart.isSubstituteUseFinished(twoCurrentDto)) {
					// 使い切った場合、取得済み変更する。
					twoCurrentDto.setAcquisitionStatus(CommonConst.CODE_ACQUISITION_STATUS_FINISH);
				}
				// 更新
				dao.update(twoCurrentDto);
			}
			// 前月分
			if (useTimeOne > 0) {
				dto.setOccurDate1(DateUtil.convDateToStringYearMonthShort(oneMonthBefore));
				dto.setUseTime1(useTimeOne);
				KdSubstituteHolidayDto oneCurrentDto = dao.findForUpdate(oneMonthBeforeDto.getSubstituteId());
				// 利用時間をセット
				oneCurrentDto.setUseHolidayTime(oneCurrentDto.getUseHolidayTime() + useTimeOne);
				// 残数の確認
				if (SubstituteHolidayPart.isSubstituteUseFinished(oneCurrentDto)) {
					// 使い切った場合、取得済み変更する。
					oneCurrentDto.setAcquisitionStatus(CommonConst.CODE_ACQUISITION_STATUS_FINISH);
				}
				// 更新
				dao.update(oneCurrentDto);
			}
		} else if (part().subHoliday().isPeriodOneMonth()) {
			// 1ヶ月の場合、前月分を取得
			// 利用発生年月、時間を登録
			dto.setOccurDate1(DateUtil.convDateToStringYearMonthShort(oneMonthBefore));
			dto.setUseTime1(useTime);
			KdSubstituteHolidayDto currentDto = dao.findForUpdate(oneMonthBeforeDto.getSubstituteId());
			// 利用時間をセット
			currentDto.setUseHolidayTime(currentDto.getUseHolidayTime() + useTime);
			// 残数の確認
			if (SubstituteHolidayPart.isSubstituteUseFinished(currentDto)) {
				// 使い切った場合、取得済み変更する。
				currentDto.setAcquisitionStatus(CommonConst.CODE_ACQUISITION_STATUS_FINISH);
			}
			// 更新
			dao.update(currentDto);
		}
		// 休暇申請の更新
		KtHolidayRequestDao requestDao = part().holidayRequest().getKtHolidayRequestDao();
		chkExclusive(dto, requestDao.findForUpdate(dto.getVoucher()));
		requestDao.update(dto);
	}
	
}
