package jp.mosp.common.action;

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

import jp.mosp.common.CommonConst;
import jp.mosp.common.TableLockUtility;
import jp.mosp.common.common.BaseDao;
import jp.mosp.common.common.MospException;
import jp.mosp.common.common.MospUtility;
import jp.mosp.common.common.VoInterface;
import jp.mosp.common.dao.CtWorkflowCommentDao;
import jp.mosp.common.dao.CtWorkflowDao;
import jp.mosp.common.dto.CtWorkflowCommentDto;
import jp.mosp.common.dto.CtWorkflowDto;
import jp.mosp.common.part.DatePart;
import jp.mosp.common.part.PartManager;
import jp.mosp.common.part.StringPart;
import jp.mosp.common.part.VariousPart;
import jp.mosp.common.part.WorkflowPart;
import jp.mosp.common.vo.MosPWorkFlowVo;

import jp.mosp.human.dto.MKihonDto;

import jp.mosp.kintai.dto.MSyukkinboDto;

import jp.mosp.workflow.dao.AmRouteDao;
import jp.mosp.workflow.dao.AmUnitDao;
import jp.mosp.workflow.dao.AttendanceRouteDao;
import jp.mosp.workflow.dao.CtOvertimeRequestDao;
import jp.mosp.workflow.dao.PmAttendanceDao;
import jp.mosp.workflow.dto.CtOvertimeRequestDto;

public abstract class MosPWorkflowAction extends AttendanceTotalAction {

	// フィールド
	private AmUnitDao unitDao;
	private AmRouteDao routeDao;
	private AttendanceRouteDao atRouteDao;

	private PmAttendanceDao pmAttendDao;

	/**
	 * 残業申請DAO
	 */
	private CtOvertimeRequestDao overRequestDao;

	/**
	 * ワークフローDAO
	 */
	private CtWorkflowDao workflowDao;


	/**
	 * 承認用ユニットDAOの準備
	 * @throws Exception
	 */
	private void prepareAmUnitDao() throws Exception {
		// DAO準備
		if (unitDao !=null) {
			return;
		}
		unitDao = PartManager.approval(partInfo()).getAmUnitDao();
	}

	/**
	 * 承認用ユニットDAO取得
	 * @return	初期化済みAmUnitDaoインスタンス
	 * @throws Exception
	 */
	protected AmUnitDao unitDao() throws Exception {
		// 承認用ユニットDAOの準備 
		prepareAmUnitDao();
		return unitDao;
	}

	/**
	 * 承認用ルートDAOの準備
	 * @throws Exception
	 */
	private void prepareAmRouteDao() throws Exception {
		// DAO準備
		if (routeDao !=null) {
			return;
		}
		routeDao = PartManager.approval(partInfo()).getAmRouteDao();
	}

	/**
	 * 承認用ルートDAOの取得
	 * @return	初期化済みAmRouteDaoインスタンス
	 * @throws Exception
	 */
	protected AmRouteDao routeDao() throws Exception {
		// 承認用ルートDAOの準備 
		prepareAmRouteDao();
		return routeDao;
	}

	/**
	 * 勤怠承認用ルートDAOの準備
	 * @throws Exception
	 */
	private void prepareAttendanceRouteDao() throws Exception {
		// DAO準備
		if (atRouteDao !=null) {
			return;
		}
		atRouteDao = PartManager.kintaiapproval(partInfo()).getAttendanceRouteDao();
	}

	/**
	 * 勤怠承認用ルートDAOの取得
	 * @return	初期化済みAttendanceRouteDaoインスタンス
	 * @throws Exception
	 */
	protected AttendanceRouteDao atRouteDao() throws Exception {
		prepareAttendanceRouteDao();
		return atRouteDao;
	}

	/**
	 * 勤怠承認用ルートマスタDAOの準備
	 * @throws Exception
	 */
	private void preparePmAttendanceDao() throws Exception {
		// DAO準備
		if (pmAttendDao != null) {
			return;
		}
		pmAttendDao = PartManager.kintaiapproval(partInfo()).getPmAttendanceDao();
	}

	/**
	 * 勤怠承認用ルートマスタDAOの取得
	 * @return	初期化済みPmAttendanceDaoインスタンス
	 * @throws Exception
	 */
	protected PmAttendanceDao pmAttendDao() throws Exception {
		// 勤怠承認用ルートマスタDAOの準備
		preparePmAttendanceDao();
		return pmAttendDao;
	}

	/**
	 * 残業申請テーブルDAOの準備
	 * @throws Exception
	 */
	private void prepareCtOvertimeRequestDao() throws Exception {
		// 残業申請テーブルDAOの準備
		if (overRequestDao != null) {
			return;
		}
		overRequestDao = PartManager.overtime(partInfo()).getCtOvertimeRequestDao();
	}

	/**
	 * 残業申請テーブルDAOの取得
	 * @return	初期化済みCtOvertimeRequestDaoインスタンス
	 * @throws Exception
	 */
	protected CtOvertimeRequestDao overRequestDao() throws Exception {
		// 残業申請テーブルDAOの準備
		prepareCtOvertimeRequestDao();
		return overRequestDao;
	}

	/**
	 * ワークフローテーブルDAOの準備
	 * @throws Exception
	 */
	private void prepareCtWorkflowDao() throws Exception {
		// ワークフローテーブルDAOの準備
		if (workflowDao != null) {
			return;
		}
		workflowDao = PartManager.workflow(partInfo()).getCtWorkflowDao();
	}

	/**
	 * ワークフローテーブルDAOの取得
	 * @return	初期化済みCtWorkflowDaoインスタンス
	 * @throws Exception
	 */
	protected CtWorkflowDao workflowDao() throws Exception {
		// ワークフローテーブルDAOの準備
		prepareCtWorkflowDao();
		return workflowDao;
	}

	/**
	 * 現状承認ユニットコード一覧（AM_UNIT）
	 * @return　現状の承認ユニットコードリスト
	 * @throws Exception
	 */
	protected String[][] getCurrentUnitArray() throws Exception {
		return PartManager.approval(partInfo()).getCurrentUnitArray();
	}

	/**
	 * 利用可能勤怠承認ルート情報一覧
	 * @param kcode 対象社員コード
	 * @return 現状所属承認ユニットコードリスト
	 * @throws Exception
	 */
	protected String[][] getBelongRouteInfoArray(String kcode, Date targetDate) throws Exception {
		return PartManager.kintaiapproval(partInfo()).getBelongRouteInfoArray(kcode, targetDate);
	}

	/**
	 * 利用可能勤怠承認ルート情報一覧
	 * @param kcode 対象社員コード
	 * @return 現状所属承認ユニットコードリスト
	 * <p>
	 * [0]…一次承認ルートコード
	 * [1]…二次承認ルートコード
	 * </p>
	 * @throws Exception
	 */
	protected String[] getTargetRouteInfo(String kcode, Date targetDate) throws Exception {
		return PartManager.kintaiapproval(partInfo()).getTargetRouteInfo(kcode, targetDate);
	}

	/**
	 * 勤怠承認処理
	 * @param kcode			対象者
	 * @param targetDate	対象年月日
	 * @param reason		承認理由
	 * @param comment		承認コメント
	 * @param approver		承認者
	 * @throws Exception
	 */
	protected void runApprovalProcess(
			String kcode,
			String targetDate,
			String reason,
			String comment,
			String approver
	) throws Exception {
		// 情報の取得
		MSyukkinboDto dto = getSyukkinboInfo(kcode, targetDate, true);
		// 排他確認
		chkExist(dto);
		// 承認情報設定
		dto.setNinsyo(approver);
		dto.setSnRiyuu(reason);
		dto.setSnComent(comment);
		dto.setSnTimes(util.getDateTime(util.getNowDate()));
		dto.setUpdtTimes(util.getDateTime(util.getNowDate()));
		// 承認処理
		syukkinboDao().update(dto);
		// DTOの再取得
		chkUpdate(getSyukkinboInfo(dto.getKCode(), dto.getHizuke()));
	}

	/**
	 * テーブルロック用ユーティリティ
	 */
	private TableLockUtility tableLock = null;

	/**
	 * テーブルロック開始
	 */
	protected void prepareTableLock() throws Exception {
		tableLock = new TableLockUtility(cfg, cmd, aspUser, user, conn);
		// 対象テーブル追加
		addTargetTable(workflowDao(), true);
		addTargetTable(AmRouteDao.TABLE, false);
	}

	/**
	 * テーブルロック対象DAO追加
	 * @param dao		対象テーブルDAO
	 * @param isWrite	WRITEの場合true、READの場合false
	 */
	protected void addTargetTable(BaseDao dao, boolean isWrite) {
		tableLock.addTargetTable(dao, isWrite);
	}

	/**
	 * テーブルロック対象DAO追加
	 * @param tablename		対象テーブル名称
	 * @param isWrite	WRITEの場合true、READの場合false
	 */
	protected void addTargetTable(String tablename, boolean isWrite) {
		tableLock.addTargetTable(tablename, isWrite);
	}

	/**
	 * ワークフロー登録用テーブルロック
	 * @throws Exception
	 */
	protected void lockTables() throws Exception {
		tableLock.lockTables();
	}

	/**
	 * ワークフロー登録用テーブルロック解除
	 * @throws Exception
	 */
	protected void unLockTables() throws Exception {
		tableLock.unLockTables();
	}

	/**
	 * ワークフロー取得
	 * @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 PartManager.workflow(partInfo()).isStatusRequest(workflow);
	}	

	/**
	 * 承認済み状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	承認済み状況の是非
	 * @throws Exception 
	 */
	protected boolean isStatusApproved(long workflow) throws Exception {
		return PartManager.workflow(partInfo()).isStatusApproved(workflow);
	}

	/**
	 * 差戻状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	差戻状況の是非
	 * @throws Exception 
	 */
	protected boolean isStatusReturned(long workflow) throws Exception {
		return PartManager.workflow(partInfo()).isStatusReturned(workflow);
	}

	/**
	 * 下書き状況の確認
	 * @param workflow		対象ワークフロー番号
	 * @return	下書き状況の是非
	 * @throws Exception
	 */
	protected boolean isStatusDraft(long workflow) throws Exception {
		return PartManager.workflow(partInfo()).isStatusDraft(workflow);
	}

	/**
	 * 承認対象者一覧取得
	 * @param targetDate
	 * @return
	 * @throws Exception
	 */
	protected List<MKihonDto> getApprovalEmployeeList(Date targetDate) throws Exception {
		return PartManager.kintaiapproval(partInfo())
		.getApprovalEmployeeList(
				mKihon.getKCode(),
				targetDate
		);
	}

	/**
	 * 申請登録
	 * <p>
	 * 必ず{@link #unLockTables()}を実行すること。
	 * </p>
	 * @param kCode		対象社員コード
	 * @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 = PartManager.kintaiapproval(partInfo()).getRequestRouteCode(kCode);
		dto.setRtCode(targetRouteCode);
		long workflow = 0;
		// テーブルロック
		lockTables();
		// ルートコードを設定
		if (StringPart.isNotNull(targetRouteCode)) {
			String maxNo = workflowDao().findForMax();
			if (maxNo == null || maxNo.equals("")) {
				maxNo = "-1";
			}
			// 最大値をセット
			dto.setWorkflow(Long.parseLong(maxNo) + 1);
			workflowDao().insert(dto);
			workflow = dto.getWorkflow();
		}
		return workflow;
	}

	/**
	 * ワークフロー更新処理
	 * @param dto			対象ワークフローDTO
	 * @param vo			エラーメッセージ表示VO
	 * @throws Exception
	 */
	protected boolean workflowRegist(CtWorkflowDto dto, VoInterface vo) throws Exception {
		// ルートコードを確認
		if (VariousPart.chkExistDto(routeDao().findForKey(dto.getRtCode()))) {
			chkExclusive(workflowDao().findForUpdate(dto.getWorkflow()), dto, vo);
			workflowDao().update(dto);
			chkUpdate(getWorkflowDto(dto.getWorkflow()));
			return true;
		}
		// エラーメッセージ
		vo.addErrMessage(MospUtility.getMessage(msg,
				CommonConst.MSG_TARGET_NOT_EXIST,
				CommonConst.NAM_APPROVAL_ROUTE)
		);
		return false;
	}

	/**
	 * ワークフロー状況を承認へ変更
	 * @param dto		対象ワークフローDTO
	 * @return
	 * @throws Exception 
	 */
	protected CtWorkflowDto shiftApproved(CtWorkflowDto dto) throws Exception {
		dto.setCurrentStatus(WorkflowPart.getStatusCodeArray()[1]);
		dto.setLayerNumber(CommonConst.NUM_LAST_LAYER);
		return dto;
	}

	/**
	 * 差戻コメント一覧
	 * @param vo			対象VO
	 * @param target		対象社員コード
	 * @param workflow		対象ワークフロー番号
	 * @throws Exception
	 */
	protected void setReturnedComment(
			MosPWorkFlowVo vo,
			String target,
			long workflow
	) throws Exception {
		vo.setCommentFields(PartManager.workflow(partInfo())
				.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 = PartManager.workflow(partInfo()).getCtWorkflowCommentDao();
		String maxCommentId = dao.findForMax();
		if (maxCommentId == null || maxCommentId.equals("")) {
			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
	 * @throws Exception
	 */
	protected String getCurrentWorkflowComment(String kCode, long workflow) throws MospException, Exception {
		CtWorkflowCommentDto dto = PartManager.workflow(partInfo()).getWorkflowCommentForTarget(
				kCode,
				workflow
		);
		if (chkExistDto(dto)) {
			return dto.getAuthorComment();
		}
		return "";
	}

	/**
	 * 勤怠承認ステータスコード取得 
	 * @param workflow			対象ワークフロー番号
	 * @return	勤怠承認ステータスコード
	 * @throws MospException
	 * @throws Exception
	 */
	protected String toKintaiApprovalStatus(long workflow) throws MospException, Exception {
		return PartManager.workflow(partInfo()).toKintaiApprovalStatus(workflow);
	}

	/**
	 * 勤怠承認状況名称取得
	 * @param snRiyuu			勤怠承認状況コード
	 * @return	勤怠承認状況名称
	 * @throws MospException
	 * @throws Exception
	 */
	protected String getApprovalName(String snRiyuu) throws MospException, Exception {
		return PartManager.kintaiapproval(partInfo()).getApprovalName(snRiyuu);
	}

	/**
	 * 対象年月日対象ユニットコード承認者判断
	 * @param unitcode		対象ユニットコード
	 * @param targetDate	対象年月日
	 * @return
	 * <p>
	 * 対象年月日承認ユニット情報DTOがある場合true、そうでない場合false
	 * </p>
	 * @throws Exception
	 */
	protected boolean isApprover(String unitcode, Date targetDate) throws Exception {
		return PartManager.approval(partInfo()).isApprover(unitcode, targetDate);
	}

	/**
	 * 残業申請情報取得
	 * @param voucher		対象伝票番号
	 * @return	残業申請情報DTO
	 * @throws Exception
	 */
	protected CtOvertimeRequestDto getOverTimeRequest(int voucher) throws Exception {
		return overRequestDao().findForKey(voucher);
	}

	/**
	 * 対象年月日残業申請情報取得
	 * @param kCode				対象社員コード
	 * @param targetDate		対象年月日
	 * @return	残業申請情報DTO
	 * @throws Exception
	 */
	protected CtOvertimeRequestDto getTargetOverTimeRequest(
			String kCode,
			Date targetDate
	) throws Exception {
		return overRequestDao().findForWorkDate(
				kCode,
				DatePart.convDateFormattedByHyphen(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 vo		対象VO
	 * @param dto		対象DTO
	 * @return
	 * @throws Exception 
	 */
	protected boolean runOverTimeRequestApproved(
			CtOvertimeRequestDto dto,
			VoInterface vo
	) throws Exception {
		long workflow = dto.getWorkflow();
		// ワークフロー取得
		CtWorkflowDto workflowDto = workflowDao().findForUpdate(workflow);
		// 承認ルート取得
		String[] aryRouteInfo = getTargetRouteInfo(dto.getKCode(), dto.getWorkDate());
		// 承認フラグ
		boolean canApproval = false;
		// 一次承認
		if (workflowDto.getLayerNumber() == CommonConst.NUM_REQUEST_LAYER) {
			if (isApprover(aryRouteInfo[0], dto.getWorkDate())) {
				if (isStatusRequest(workflow)) {
					workflowDto.setCurrentStatus(WorkflowPart.getStatusCodeArray()[1]);
					workflowDto.setLayerNumber(CommonConst.NUM_LAYER1);
					canApproval = true;
				}
			} else {
				StringBuffer sb = new StringBuffer();
				sb.append(DatePart.convDateToFormatDateJP(dto.getWorkDate()));
				sb.append(CommonConst.STR_UNDER_SEPARATOR);
				sb.append(CommonConst.NAM_OVER_TIME_REQUEST);
				vo.addErrMessage(MospUtility.getMessage(
						msg,
						CommonConst.MSG_CODE_DENY_APPROVAL,
						sb.toString())
				);
			}
		} 
		// 二次承認
		else if (workflowDto.getLayerNumber() == CommonConst.NUM_LAYER1) {
			if (isApprover(aryRouteInfo[1], dto.getWorkDate())) {
				if (isStatusApproved(workflow)) {
					workflowDto.setCurrentStatus(WorkflowPart.getStatusCodeArray()[1]);
					workflowDto.setLayerNumber(CommonConst.NUM_LAST_LAYER);
					canApproval = true;
				}
			} else {
				StringBuffer sb = new StringBuffer();
				sb.append(DatePart.convDateToFormatDateJP(dto.getWorkDate()));
				sb.append(CommonConst.STR_UNDER_SEPARATOR);
				sb.append(CommonConst.NAM_OVER_TIME_REQUEST);
				vo.addErrMessage(MospUtility.getMessage(
						msg,
						CommonConst.MSG_CODE_DENY_APPROVAL,
						sb.toString())
				);
			}
		} else {
			if (isStatusApproved(workflow)) {
				StringBuffer sb = new StringBuffer();
				sb.append(DatePart.convDateToFormatDateJP(dto.getWorkDate()));
				sb.append(CommonConst.MSG_OF);
				sb.append(CommonConst.NAM_OVER_TIME_REQUEST);
				// エラーメッセージ
				vo.addErrMessage(MospUtility.getMessage(msg, 
						CommonConst.MSG_CODE_SELECT_ALREADY_APPROVED,
						sb.toString())
				);
			}
		}
		// 差戻し中の場合
		if (isStatusReturned(workflow)) {
			StringBuffer sb = new StringBuffer();
			sb.append(DatePart.convDateToFormatDateJP(dto.getWorkDate()));
			sb.append(CommonConst.MSG_OF);
			sb.append(CommonConst.NAM_OVER_TIME_REQUEST);
			// エラーメッセージ
			vo.addErrMessage(MospUtility.getMessage(msg, 
					CommonConst.MSG_CODE_SELECT_ALREADY_RETURNED,
					sb.toString())
			);
		}
		// ワークフローの更新
		if (canApproval && workflowRegist(workflowDto, vo)) {
			return true;
		}
		return false;
	}

}
