/*
 * 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 Affero General Public License
 * as published by the Free Software Foundation, either version 3
 * 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jp.mosp.time.bean.impl;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import jp.mosp.framework.base.MospException;
import jp.mosp.framework.base.MospParams;
import jp.mosp.framework.constant.MospConst;
import jp.mosp.framework.utils.DateUtility;
import jp.mosp.platform.base.PlatformBean;
import jp.mosp.platform.bean.human.impl.HumanSearchBean;
import jp.mosp.platform.bean.workflow.RouteApplicationReferenceBeanInterface;
import jp.mosp.platform.bean.workflow.WorkflowIntegrateBeanInterface;
import jp.mosp.platform.constant.PlatformConst;
import jp.mosp.platform.dto.human.HumanDtoInterface;
import jp.mosp.platform.dto.workflow.ApprovalRouteUnitDtoInterface;
import jp.mosp.platform.dto.workflow.RouteApplicationDtoInterface;
import jp.mosp.time.bean.SubordinateSearchBeanInterface;
import jp.mosp.time.bean.TotalTimeCalcBeanInterface;
import jp.mosp.time.bean.TotalTimeCorrectionReferenceBeanInterface;
import jp.mosp.time.bean.TotalTimeEmployeeTransactionReferenceBeanInterface;
import jp.mosp.time.dao.settings.TotalTimeDataDaoInterface;
import jp.mosp.time.dto.settings.SubordinateListDtoInterface;
import jp.mosp.time.dto.settings.TotalTimeCorrectionDtoInterface;
import jp.mosp.time.dto.settings.TotalTimeDataDtoInterface;
import jp.mosp.time.dto.settings.TotalTimeEmployeeDtoInterface;
import jp.mosp.time.dto.settings.impl.SubordinateListDto;

/**
 * 部下一覧検索クラス。
 */
public class SubordinateSearchBean extends HumanSearchBean implements SubordinateSearchBeanInterface {
	
	/**
	 * 勤怠集計データDAO。
	 */
	private TotalTimeDataDaoInterface							totalTimeDataDao;
	
	/**
	 * 勤怠集計修正情報参照。
	 */
	private TotalTimeCorrectionReferenceBeanInterface			totalTimeCorrection;
	
	/**
	 * 社員勤怠集計管理参照。
	 */
	private TotalTimeEmployeeTransactionReferenceBeanInterface	totalTimeEmployeeTransaction;
	
	/**
	 * 勤怠集計。
	 */
	TotalTimeCalcBeanInterface									totalTimeCalc;
	
	/**
	 * 承認ルート設定適用参照クラス。
	 */
	protected RouteApplicationReferenceBeanInterface			routeApplication;
	
	/**
	 * ワークフロー統合クラス。
	 */
	protected WorkflowIntegrateBeanInterface					workflowIntegrate;
	
	/**
	 * 未承認。
	 */
	private String												approval;
	
	/**
	 * 締状態。
	 */
	private String												calc;
	

	/**
	 * コンストラクタ。
	 */
	public SubordinateSearchBean() {
		// 処理無し
	}
	
	/**
	 * {@link PlatformBean#PlatformBean(MospParams, Connection)}を実行する。<br>
	 * @param mospParams MosP処理情報
	 * @param connection DBコネクション
	 */
	public SubordinateSearchBean(MospParams mospParams, Connection connection) {
		super(mospParams, connection);
	}
	
	@Override
	public void initBean() throws MospException {
		super.initBean();
		// 勤怠集計データDAO取得
		totalTimeDataDao = (TotalTimeDataDaoInterface)createDao(TotalTimeDataDaoInterface.class);
		// 勤怠集計修正情報参照クラス取得
		totalTimeCorrection = (TotalTimeCorrectionReferenceBeanInterface)createBean(TotalTimeCorrectionReferenceBeanInterface.class);
		// 謝金勤怠集計管理参照クラス取得
		totalTimeEmployeeTransaction = (TotalTimeEmployeeTransactionReferenceBeanInterface)createBean(TotalTimeEmployeeTransactionReferenceBeanInterface.class);
		// 勤怠集計クラス取得
		totalTimeCalc = (TotalTimeCalcBeanInterface)createBean(TotalTimeCalcBeanInterface.class);
		// 承認ルート設定適用参照クラス取得
		routeApplication = (RouteApplicationReferenceBeanInterface)createBean(RouteApplicationReferenceBeanInterface.class);
		// ワークフロー統合クラス取得
		workflowIntegrate = (WorkflowIntegrateBeanInterface)createBean(WorkflowIntegrateBeanInterface.class);
	}
	
	@Override
	public List<SubordinateListDtoInterface> getSubordinateList() throws MospException {
		// 検索条件設定(検索区分(社員コード))
		setEmployeeCodeType(PlatformConst.SEARCH_FORWARD_MATCH);
		// 検索条件設定(休退職区分)
		setStateType(PlatformConst.EMPLOYEE_STATE_PRESENCE);
		// 検索条件設定(下位所属要否)
		setNeedLowerSection(true);
		// 検索条件設定(兼務要否)
		setNeedConcurrent(true);
		// 検索条件設定(操作区分)
		setOperationType(MospConst.OPERATION_TYPE_REFER);
		// 部下一覧情報リスト取得
		return getSubordinateList(search());
	}
	
	@Override
	public List<SubordinateListDtoInterface> getApprovableList() throws MospException {
		/* THINK
		 * 承認者から被承認社員を取得する処理は、承認ルート設定適用の仕様上、コストがかかる。
		 * 
		 * 承認ルート設定適用は、被承認者が設定されている承認ルートを特定するためのものであり、
		 * 承認適用設定情報から被承認者を取得することは困難である。
		 * 
		 * 承認ルート設定適用は、個人或いはマスタ(勤務地、雇用契約、所属、職位)の組合せで、
		 * 被承認者の承認ルートを特定するものである。
		 * 承認ルートを特定するにあたり、優先順位が存在する。
		 * その優先順位に沿って確認が行われ、被承認者の人事基本情報に最初に合致した
		 * 承認ルート設定適用が用いられることになる。
		 * 
		 * そのため、承認ルート設定適用から被承認者を取得しようとした場合、
		 * その承認ルート設定適用に合致する社員を取得するだけでなく、その承認ルート設定適用
		 * より優先順位の高い承認ルート設定適用に合致する社員を除かなくてはならない。
		 * 例えば、マスタの組合せで全て空白の承認ルート設定適用があった場合、登録されている
		 * 全ての承認ルート設定適用に対して合致する社員を取得しそれらを除かなくてはならない。
		 * 
		 * 当メソッドでは、検索条件に合致する社員一人一人に対して、承認ルート設定適用情報を取得
		 * し、承認者がユニットとして登録されているルートが適用される承認ルート設定適用情報と
		 * 合致するかで判断することとする。
		 * この方法でもコストがかかることに変わりは無い。
		 * 検索条件を指定しない場合などは、全社員に対して承認ルート設定適用情報を確認することに
		 * なり、相応のコストが想定される。
		 * 
		 * 利用に堪えないようであれば、承認ルート設定適用から被承認者を取得する方法を試してみる。
		 * それでも改善されない場合は、仕様自体を再検討する必要がある。
		 */
		/* THINK
		 * 代理も考慮する必要がある。
		 */
		// 検索条件設定(検索区分(社員コード))
		setEmployeeCodeType(PlatformConst.SEARCH_FORWARD_MATCH);
		// 検索条件設定(休退職区分)
		setStateType(PlatformConst.EMPLOYEE_STATE_PRESENCE);
		// 検索条件設定(下位所属要否)
		setNeedLowerSection(true);
		// 検索条件設定(兼務要否)
		setNeedConcurrent(true);
		// 検索条件設定(操作区分)
		setOperationType(MospConst.OPERATION_TYPE_REFER);
		// 人事情報検索(部下一覧)
		List<HumanDtoInterface> approvableList = search();
		// 検索条件再設定(操作区分)(承認対象者全員が検索対象となるためnullを設定)
		setOperationType(null);
		// 人事情報検索
		List<HumanDtoInterface> humanList = search();
		// 承認者がユニットとして登録されているルートのリストを取得
		List<String> routeList = getRouteList(mospParams.getUser().getPersonalId(), targetDate);
		// 検索人事情報毎に被承認者かどうかを確認して人事情報リストに追加
		for (HumanDtoInterface humanDto : humanList) {
			addApprovable(humanDto, routeList, approvableList);
		}
		// 部下一覧情報リスト取得
		return getSubordinateList(approvableList);
	}
	
	/**
	 * 人事情報リストを基に、部下一覧情報リストを取得する。<br>
	 * @param humanList 人事情報リスト
	 * @return 部下一覧情報リスト
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	protected List<SubordinateListDtoInterface> getSubordinateList(List<HumanDtoInterface> humanList)
			throws MospException {
		// 年
		int year = DateUtility.getYear(targetDate);
		// 月
		int month = DateUtility.getMonth(targetDate);
		// 部下一覧リスト準備
		List<SubordinateListDtoInterface> subordinateList = new ArrayList<SubordinateListDtoInterface>();
		// 検索結果から部下一覧リストを作成
		for (HumanDtoInterface dto : humanList) {
			TotalTimeDataDtoInterface totalTimeDto = totalTimeDataDao.findForKey(dto.getPersonalId(), year, month);
			SubordinateListDtoInterface subordinateListDto = getSubordinateListDto(dto, totalTimeDto, year, month);
			if (subordinateListDto != null) {
				subordinateList.add(subordinateListDto);
			}
		}
		return subordinateList;
	}
	
	@Override
	public SubordinateListDtoInterface getSubordinateListDto(HumanDtoInterface humanDto,
			TotalTimeDataDtoInterface totalTimeDataDto, int year, int month) throws MospException {
		if (humanDto == null) {
			return null;
		}
		SubordinateListDtoInterface dto = new SubordinateListDto();
		dto.setPersonalId(humanDto.getPersonalId());
		dto.setEmployeeCode(humanDto.getEmployeeCode());
		dto.setLastName(humanDto.getLastName());
		dto.setFirstName(humanDto.getFirstName());
		dto.setSectionCode(humanDto.getSectionCode());
		dto.setApproval("");
		dto.setCalc("");
		dto.setCorrection("");
		TotalTimeCorrectionDtoInterface totalTimeCorrectionDto = totalTimeCorrection.getLatestTotalTimeCorrectionInfo(
				humanDto.getPersonalId(), year, month);
		if (totalTimeCorrectionDto != null) {
			if (totalTimeCorrectionDto.getCorrectionPersonalId().equals(humanDto.getPersonalId())) {
				dto.setCorrection(mospParams.getName("CorrectionHistory"));
			} else {
				dto.setCorrection(mospParams.getName("Other"));
			}
		}
		if (mospParams.hasErrorMessage()) {
			return null;
		}
		// 状態
		TotalTimeEmployeeDtoInterface totalTimeEmployeeDto = totalTimeEmployeeTransaction.findForKey(humanDto
			.getPersonalId(), year, month);
		if (!calc.isEmpty()) {
			if ("1".equals(calc) && totalTimeEmployeeDto != null && totalTimeEmployeeDto.getCutoffState() == 1) {
				return null;
			}
			if ("2".equals(calc) && (totalTimeEmployeeDto == null || totalTimeEmployeeDto.getCutoffState() != 1)) {
				return null;
			}
		}
		dto.setCalc("1");
		if (totalTimeDataDto != null && totalTimeEmployeeDto != null && totalTimeEmployeeDto.getCutoffState() == 1) {
			// 仮締
			dto.setCalc("2");
		}
		String approvalStatus = totalTimeCalc.getApprovalStatus(humanDto.getPersonalId(), year, month);
		if (mospParams.hasErrorMessage()) {
			mospParams.getErrorMessageList().clear();
			return null;
		}
		if (!approval.isEmpty() && !approval.equals(approvalStatus)) {
			return null;
		}
		dto.setApproval(approvalStatus);
		if (totalTimeDataDto != null) {
			// 仮締・確定
			dto.setWorkDate(totalTimeDataDto.getTimesWorkDate());
			dto.setWorkTime(totalTimeDataDto.getWorkTime());
			dto.setRestTime(totalTimeDataDto.getRestTime());
			dto.setLateTime(totalTimeDataDto.getLateTime());
			dto.setLeaveEarlyTime(totalTimeDataDto.getLeaveEarlyTime());
			dto.setOverTimeIn(totalTimeDataDto.getOvertimeIn());
			dto.setOverTimeOut(totalTimeDataDto.getOvertimeOut());
			dto.setWorkOnHolidayTime(totalTimeDataDto.getWorkOnHoliday() + totalTimeDataDto.getWorkOnSpecificHoliday());
			dto.setLateNightTime(totalTimeDataDto.getLateNight());
			dto.setPaidHoliday(totalTimeDataDto.getTimesPaidHoliday());
			dto.setPaidHolidayHour(totalTimeDataDto.getPaidHolidayHour());
			dto.setTotalSpecialHoliday(totalTimeDataDto.getTotalSpecialHoliday());
			dto.setTotalOtherHoliday(totalTimeDataDto.getTotalOtherHoliday());
			dto.setTimesCompensation(totalTimeDataDto.getTimesCompensation());
			dto.setAllHoliday(totalTimeDataDto.getTotalSpecialHoliday() + totalTimeDataDto.getTotalOtherHoliday()
					+ totalTimeDataDto.getTimesCompensation());
			dto.setAbsence(totalTimeDataDto.getTotalAbsence());
			dto.setTimesWork(totalTimeDataDto.getTimesWork());
			dto.setTimesLate(totalTimeDataDto.getTimesLate());
			dto.setTimesLeaveEarly(totalTimeDataDto.getTimesLeaveEarly());
			dto.setTimesOvertime(totalTimeDataDto.getTimesOvertime());
			dto.setTimesWorkingHoliday(totalTimeDataDto.getTimesWorkingHoliday());
			dto.setTimesLegalHoliday(totalTimeDataDto.getTimesLegalHoliday());
			dto.setTimesSpecificHoliday(totalTimeDataDto.getTimesSpecificHoliday());
			dto.setTimesHolidaySubstitute(totalTimeDataDto.getTimesHolidaySubstitute());
		}
		return dto;
	}
	
	@Override
	public void setApproval(String approval) {
		this.approval = approval;
	}
	
	@Override
	public void setCalc(String calc) {
		this.calc = calc;
	}
	
	/**
	 * 対象人事情報の社員が被承認者である場合、被承認者リストに追加する。<br>
	 * 対象人事情報から承認ルート適用設定情報を取得し、そのルートコードが
	 * 承認対象ルートリストに含まれるかで、被承認者であるかを確認する。<br>
	 * @param humanDto       対象人事情報
	 * @param routeList      承認対象ルートリスト
	 * @param approvableList 被承認者リストルート設定適用情報リスト
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	protected void addApprovable(HumanDtoInterface humanDto, List<String> routeList,
			List<HumanDtoInterface> approvableList) throws MospException {
		// 被承認者リスト確認
		for (HumanDtoInterface approvable : approvableList) {
			// 既に被承認者リストに存在している場合は処理無し
			if (approvable.getPersonalId().equals(humanDto.getPersonalId())) {
				return;
			}
		}
		// 対象人事情報の社員が適用されている承認ルート適用設定情報を取得
		RouteApplicationDtoInterface targetRouteApplication = workflowIntegrate.findForPerson(humanDto.getPersonalId(),
				targetDate, PlatformConst.WORKFLOW_TYPE_TIME);
		// 承認ルート適用設定情報を確認
		if (isApprovable(targetRouteApplication, routeList)) {
			// 人事情報リスト(部下一覧)に追加
			approvableList.add(humanDto);
		}
	}
	
	/**
	 * 対象ルート設定適用情報に設定されたルートコードが、
	 * ルート設定適用情報リストの中に含まれているかを確認する。<br>
	 * @param routeApplicationDto  対象ルート設定適用リスト
	 * @param routeList            ルートリスト
	 * @return 確認結果(true：対象がリストの中に含まれる、false：対象がリストの中に含まれない)
	 */
	protected boolean isApprovable(RouteApplicationDtoInterface routeApplicationDto, List<String> routeList) {
		// 対象ルート設定適用情報確認
		if (routeApplicationDto == null) {
			return false;
		}
		// 承認ルート適用設定情報を確認
		for (String routeCode : routeList) {
			// 承認ルート適用設定情報のルートコードを確認
			if (routeApplicationDto.getRouteCode().equals(routeCode)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 承認者がユニットとして登録されているルートのリストを取得する。<br>
	 * @param approverId 承認者個人ID
	 * @param targetDate 対象日
	 * @return 承認者がユニットとして登録されているルートのリスト
	 * @throws MospException インスタンスの取得或いはSQL実行に失敗した場合
	 */
	protected List<String> getRouteList(String approverId, Date targetDate) throws MospException {
		// 承認ルートリスト準備
		List<String> list = new ArrayList<String>();
		// ログインユーザが含まれる承認ルートユニット情報リストを取得
		List<ApprovalRouteUnitDtoInterface> routeUnitList = workflowIntegrate.getListForApprover(mospParams.getUser()
			.getPersonalId(), targetDate);
		// 承認ルートユニット毎に承認ルートを取得
		for (ApprovalRouteUnitDtoInterface routeUnit : routeUnitList) {
			// 承認ルートリストに追加
			list.add(routeUnit.getRouteCode());
		}
		return list;
	}
}
