/*
 * MosP - Mind Open Source Project         http://www.mosp.jp/
 * Copyright (C) 1987-2009 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.payroll.base;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jp.mosp.common.action.MosPAction;
import jp.mosp.common.common.BaseAction;
import jp.mosp.common.common.MospConst;
import jp.mosp.common.common.MospException;
import jp.mosp.common.common.MospUtility;
import jp.mosp.common.common.ValidateUtility;
import jp.mosp.human.dao.MKihonDao;
import jp.mosp.human.dto.MKihonDto;
import jp.mosp.legal.LegalConst;
import jp.mosp.legal.LegalUtility;
import jp.mosp.payroll.dao.CmBankDao;
import jp.mosp.payroll.dao.CmBranchDao;
import jp.mosp.payroll.dao.CmCoBankDao;
import jp.mosp.payroll.dao.CmCommutationDao;
import jp.mosp.payroll.dao.CmCompanyDao;
import jp.mosp.payroll.dao.CmIncomeTaxDao;
import jp.mosp.payroll.dao.CmPaymentMeansDao;
import jp.mosp.payroll.dao.CmResidenceTaxDao;
import jp.mosp.payroll.dao.CmSocInsuranceDao;
import jp.mosp.payroll.dao.CmUnitAmountDao;
import jp.mosp.payroll.dao.MKihonPayrollDao;
import jp.mosp.payroll.dao.PdPayAllowanceDao;
import jp.mosp.payroll.dao.PdPayBreakdownDao;
import jp.mosp.payroll.dao.PdPayDeductionDao;
import jp.mosp.payroll.dao.PdPayTimeDao;
import jp.mosp.payroll.dao.PdPaySumDao;
import jp.mosp.payroll.dao.PhPaymentDao;
import jp.mosp.payroll.dao.PiPaymentDao;
import jp.mosp.payroll.dao.PmDetailPaDao;
import jp.mosp.payroll.dao.PmDetailPbDao;
import jp.mosp.payroll.dao.PmDetailPdDao;
import jp.mosp.payroll.dao.PmDetailPsDao;
import jp.mosp.payroll.dao.PmDetailPtDao;
import jp.mosp.payroll.dao.PtCurrentDao;
import jp.mosp.payroll.dto.CmBankDto;
import jp.mosp.payroll.dto.CmBranchDto;
import jp.mosp.payroll.dto.CmCoBankDto;
import jp.mosp.payroll.dto.CmCommutationDto;
import jp.mosp.payroll.dto.CmCompanyDto;
import jp.mosp.payroll.dto.CmPaymentMeansDto;
import jp.mosp.payroll.dto.CmUnitAmountDto;
import jp.mosp.payroll.dto.DetailTimeDto;
import jp.mosp.payroll.dto.PhPaymentDto;
import jp.mosp.payroll.dto.PtCurrentDto;
import jp.mosp.payroll.payment.vo.EndCalcVo;

/**
 * 給与システムのActionで用いられる共通機能を提供する。<br><br>
 * 給与システム内の各Actionは、当クラスを拡張して実装される。
 */
public abstract class PayrollAction extends MosPAction {
	
	/**
	 * 給与システムで用いるActionの共通初期化処理を行う。<br><br>
	 * 以下の処理を行う。
	 * <ul><li>
	 * {@link BaseAction#BaseAction()}
	 * </li></ul>
	 */
	protected PayrollAction() {
		super();
		setCheckAuth(false);
	}
	
	/**
	 * 社員インスタンスを取得する。<br><br>
	 * kCodeをキーとして人事情報を取得し、{@link Employee}に格納する。
	 * @param kCode 社員コード
	 * @return 社員
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	protected Employee getEmployee(
			String kCode
	) throws SQLException, IllegalAccessException, NoSuchFieldException {
		MKihonDao mKihonDao = new MKihonDao();
		mKihonDao.initDao(cfg, cmd, aspUser, user, conn);
		MKihonDto mKihonDto = mKihonDao.findForKey(kCode);
		if (mKihonDto == null) {
			return null;
		}
		CmSocInsuranceDao cmSocInsuranceDao = new CmSocInsuranceDao();
		CmIncomeTaxDao    cmIncomeTaxDao    = new CmIncomeTaxDao   ();
		CmResidenceTaxDao cmResidenceTaxDao = new CmResidenceTaxDao();
		CmCommutationDao  cmCommutationDao  = new CmCommutationDao ();
		CmUnitAmountDao   cmUnitAmountDao   = new CmUnitAmountDao  ();
		CmPaymentMeansDao cmPaymentMeansDao = new CmPaymentMeansDao();
		cmSocInsuranceDao.initDao(cfg, cmd, aspUser, user, conn);
		cmIncomeTaxDao   .initDao(cfg, cmd, aspUser, user, conn);
		cmResidenceTaxDao.initDao(cfg, cmd, aspUser, user, conn);
		cmCommutationDao .initDao(cfg, cmd, aspUser, user, conn);
		cmUnitAmountDao  .initDao(cfg, cmd, aspUser, user, conn);
		cmPaymentMeansDao.initDao(cfg, cmd, aspUser, user, conn);
		Employee employee = new Employee(
				mKihonDto,
				cmSocInsuranceDao.findForKey(kCode, getDefaultDate()),
				cmIncomeTaxDao   .findForEmployee(kCode),
				cmResidenceTaxDao.findForKey(kCode, getDefaultDate()),
				cmCommutationDao .findForEmployee(kCode),
				cmUnitAmountDao  .findForEmployee(kCode),
				cmPaymentMeansDao.findForEmployee(kCode)
		);
		employee.setSectionName(getCodeName(employee.getSectionCode(), getSectionArrayAll()));
		return employee;
	}
	
	/**
	 * 給与伝票インスタンスを取得する。<br><br>
	 * kCode、calcDateをキーとして給与情報を取得し、{@link Payment}に格納する。
	 * @param kCode     社員コード
	 * @param calcMonth 計算年月
	 * @return 給与伝票
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	protected Payment getPayment(
			String kCode, Date calcMonth
	) throws SQLException, IllegalAccessException, NoSuchFieldException {
		PhPaymentDto header = new PhPaymentDao(
				cfg, cmd, aspUser, user, conn).findForKey(kCode, calcMonth);
		if (header == null) {
			return null;
		}
		return new Payment(
				header,
				new PmDetailPtDao(cfg, cmd, aspUser, user, conn).findForSlip(header.getSlipCode()),
				new PmDetailPaDao(cfg, cmd, aspUser, user, conn).findForSlip(header.getSlipCode()),
				new PmDetailPdDao(cfg, cmd, aspUser, user, conn).findForSlip(header.getSlipCode()),
				new PmDetailPbDao(cfg, cmd, aspUser, user, conn).findForSlip(header.getSlipCode()),
				new PmDetailPsDao(cfg, cmd, aspUser, user, conn).findForSlip(header.getSlipCode()),
				new PdPayTimeDao     (cfg, cmd, aspUser, user, conn).find(kCode, calcMonth),
				new PdPayAllowanceDao(cfg, cmd, aspUser, user, conn).find(kCode, calcMonth),
				new PdPayDeductionDao(cfg, cmd, aspUser, user, conn).find(kCode, calcMonth),
				new PdPayBreakdownDao(cfg, cmd, aspUser, user, conn).find(kCode, calcMonth),
				new PdPaySumDao      (cfg, cmd, aspUser, user, conn).find(kCode, calcMonth)
		);
	}
	
	/**
	 * 新規給与伝票インスタンスを取得する。
	 * @param slipCode 伝票コード
	 * @return 新規給与伝票
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	private Payment getPayment(
			String kCode, Date calcMonth, String slipCode
	) throws SQLException, IllegalAccessException, NoSuchFieldException {
		PhPaymentDto header = new PhPaymentDto();
		header.setKCode    (kCode    );
		header.setCalcMonth(calcMonth);
		header.setSlipCode (slipCode );
		return new Payment(
				header,
				new PmDetailPtDao(cfg, cmd, aspUser, user, conn).findForSlip(slipCode),
				new PmDetailPaDao(cfg, cmd, aspUser, user, conn).findForSlip(slipCode),
				new PmDetailPdDao(cfg, cmd, aspUser, user, conn).findForSlip(slipCode),
				new PmDetailPbDao(cfg, cmd, aspUser, user, conn).findForSlip(header.getSlipCode()),
				new PmDetailPsDao(cfg, cmd, aspUser, user, conn).findForSlip(slipCode)
		);
	}
	
	/**
	 * 計算中年月を取得する。<br>
	 * @param officeCode 事業所コード
	 * @param calcType   計算区分
	 * @return 計算中年月(計算中でなければnull)
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	protected Date getCurrent(
			String officeCode, byte calcType
	) throws SQLException, IllegalAccessException, NoSuchFieldException {
		// DAO準備
		PtCurrentDao dao = new PtCurrentDao(cfg, cmd, aspUser, user, conn);
		// 計算中情報取得
		List<PtCurrentDto> list = dao.findForState(
				officeCode, calcType, PayrollConst.CURRENT_START);
		// 計算中情報確認
		if (list == null || list.size() == 0) {
			return null;
		}
		return list.get(0).getCalcDate();
	}
	
	/**
	 * 計算年月日かどうかを確認する。
	 * @param date       確認対象年月日
	 * @param officeCode 事業所コード
	 * @param calcType   計算区分
	 * @return 計算年月日確認結果(true:計算年月日、false：計算年月日でない)
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	protected boolean isCurrent(
			Date date, String officeCode, byte calcType
	) throws SQLException, IllegalAccessException, NoSuchFieldException {
		Date current = getCurrent(officeCode, calcType);
		if (current == null) {
			return false;
		}
		if (date.compareTo(current) != 0) {
			return false;
		}
		return true;
	}
	
	/**
	 * 計算対象者を取得する。
	 * @param date 対象外となる退職日
	 * @return 計算対象者社員コード
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	protected String[] getTargetEmployee(
			Date date
	) throws SQLException, IllegalAccessException, NoSuchFieldException {
		// 日付調整
		int intDate = MospUtility.getYear(date) * 10000 +
				MospUtility.getMonth(date) * 100 + MospUtility.getDay(date);
		// DAOの準備
		MKihonPayrollDao dao = new MKihonPayrollDao();
		dao.initDao(cfg, cmd, aspUser, user, conn);
		// 情報の取得
		List<MKihonDto> listKihon = dao.find(intDate);
		// 配列の作成
		String[] aryKihon = new String[listKihon.size()];
		for (int i = 0; i < listKihon.size(); i++) {
			aryKihon[i] = listKihon.get(i).getKCode();
		}
		return aryKihon;
	}
	
	/**
	 * 給与の計算を行う。<br>
	 * @param kCode       計算対象社員コード
	 * @param targetMonth 計算対象年月
	 * @throws Exception
	 */
	protected void calcPayment(String kCode, Date targetMonth) throws Exception {
		// DAO準備
		PiPaymentDao      piPaymentDao      = new PiPaymentDao     (cfg, cmd, aspUser, user, conn);
		PhPaymentDao      phPaymentDao      = new PhPaymentDao     (cfg, cmd, aspUser, user, conn);
		PdPayTimeDao      pdPayTimeDao      = new PdPayTimeDao     (cfg, cmd, aspUser, user, conn);
		PdPayAllowanceDao pdPayAllowanceDao = new PdPayAllowanceDao(cfg, cmd, aspUser, user, conn);
		PdPayDeductionDao pdPayDeductionDao = new PdPayDeductionDao(cfg, cmd, aspUser, user, conn);
		PdPayBreakdownDao pdPayBreakdownDao = new PdPayBreakdownDao(cfg, cmd, aspUser, user, conn);
		PdPaySumDao       pdPaySumDao       = new PdPaySumDao      (cfg, cmd, aspUser, user, conn);
		// 給与明細削除
		pdPayTimeDao     .delete(kCode, targetMonth);
		pdPayAllowanceDao.delete(kCode, targetMonth);
		pdPayDeductionDao.delete(kCode, targetMonth);
		pdPayBreakdownDao.delete(kCode, targetMonth);
		pdPaySumDao      .delete(kCode, targetMonth);
		// 人事情報取得
		Employee employee = getEmployee(kCode);
		// 給与伝票新規作成
		Payment payment = getPayment(kCode, targetMonth, employee.getSlipCode());
		// 勤怠情報及びアップロード情報取得
		List<DetailTimeDto> listPiPayment = piPaymentDao.find(kCode, targetMonth);
		// パラメータMap作成
		Map<String, String> mapParams = new HashMap<String, String>();
		// パラメータ追加(勤怠情報及びアップロード情報)
		addParams(mapParams, listPiPayment);
		// パラメータ追加(単価情報)
		addParams(mapParams, employee.getUnitAmount());
		// 明細値計算
		payment.calcDetails(mapParams);
		// 給与調整
		adjustPayment(employee, payment, mapParams);
		// 通勤手当計算
		calcCommutation(employee, payment, mapParams);
		// 法定控除計算
		calcLegalDeduction(employee, payment);
		// 合計計算
		calcTotal(employee, payment);
		// ヘッダー情報設定
		payment.setPayDate(getPayDate(listPiPayment));
		// 給与明細登録
		pdPayTimeDao     .insert(payment.getTimeList     ());
		pdPayAllowanceDao.insert(payment.getAllowanceList());
		pdPayDeductionDao.insert(payment.getDeductionList());
		pdPayBreakdownDao.insert(payment.getBreakdownList());
		pdPaySumDao      .insert(payment.getSumList      ());
		// 給与ヘッダー登録
		if (phPaymentDao.findForKey(kCode, targetMonth) == null) {
			phPaymentDao.insert(payment.getHeader(), false);
		} else {
			phPaymentDao.update(payment.getHeader(), false);
		}
	}
	
	/**
	 * パラメータを追加する。
	 * @param mapParams 追加先パラメータMap
	 * @param listParams パラメータリスト
	 */
	private void addParams(Map<String, String> mapParams, List<?> listParams) {
		for (Iterator<?> it = listParams.iterator(); it.hasNext();) {
			Object obj = it.next();
			if (obj instanceof DetailTimeDto) {
				DetailTimeDto param = (DetailTimeDto)obj;
				mapParams.put(param.getDetailCode(), String.valueOf(param.getAmount()));
				continue;
			}
			if (obj instanceof CmUnitAmountDto) {
				CmUnitAmountDto param = (CmUnitAmountDto)obj;
				mapParams.put(param.getUnitType(), String.valueOf(param.getUnitAmount()));
				continue;
			}
		}
	}
	
	/**
	 * パラメータを取得する。
	 * @param mapParams 追加先パラメータMap
	 * @param listParams パラメータリスト
	 */
	private float getParam(Map<String, String> mapParams, String detailCode) {
		String value = mapParams.get(detailCode);
		if (value == null || !ValidateUtility.chkNumeric(value)) {
			return 0F;
		}
		return Float.parseFloat(value);
	}
	
	/**
	 * 給与を調整する。
	 * @param employee  社員情報
	 * @param payment   給与伝票
	 */
	private void adjustPayment(
			Employee employee, Payment payment, Map<String, String> mapParams
	) {
		// 基本給調整(給与区分)
		int base = payment.getAllowanceAmount(PayrollConst.DTL_BASE);
		if (employee.getPaymentType() == PayrollConst.PAYMENT_DAY) {
			base = base * (int)getParam(mapParams, PayrollConst.DTL_SNISSU);
		} else if (employee.getPaymentType() == PayrollConst.PAYMENT_HOUR) {
			base = (int)MospUtility.round(
					base * getParam(mapParams, PayrollConst.DTL_KINMUJI) / 60,
					0, BigDecimal.ROUND_CEILING);
		}
		payment.setAllowanceAmount(PayrollConst.DTL_BASE, base);
	}
	
	/**
	 * 通勤手当を計算する。
	 * @param employee  社員情報
	 * @param payment   給与伝票
	 * @param mapParams パラメータMap
	 */
	private void calcCommutation(
			Employee employee, Payment payment, Map<String, String> mapParams
	) {
		// 変数宣言
		int commuteAmount = 0;	// 支給通勤手当
		int monthlyAmount = 0;	// 当月通勤手当
		int monthlyTaxed  = 0;	// 当月課税通勤費
		// 給与情報取得
		for (Iterator<CmCommutationDto> it = employee.getCommutation().iterator(); it.hasNext();) {
			// 変数宣言
			int amount = 0    ;	// 個別当月通勤手当
			int taxed  = 0    ;	// 個別当月課税通勤費
			// データ取得
			CmCommutationDto cmCommutationDto = it.next();
			// 当月通勤手当取得
			if (cmCommutationDto.getPaymentInterval().equals("1")) {
				// 支給間隔：毎月
				amount = cmCommutationDto.getCommuteAmount();
				taxed  = cmCommutationDto.getCommuteTaxed ();
				// 当月通勤手当及び当月課税通勤費加算
				monthlyAmount += amount;
				monthlyTaxed  += taxed ;
				// 支給通勤手当加算
				commuteAmount += amount;
			} else if (cmCommutationDto.getPaymentInterval().equals("5")) {
				// 支給間隔：毎日(通勤費×出勤日数(休出含む))
				amount = cmCommutationDto.getCommuteAmount() * (int)getParam(mapParams, PayrollConst.DTL_SNISSU);
				taxed  = cmCommutationDto.getCommuteTaxed () * (int)getParam(mapParams, PayrollConst.DTL_SNISSU);
				// 当月通勤手当及び当月課税通勤費加算
				monthlyAmount += amount;
				monthlyTaxed  += taxed ;
				// 支給通勤手当加算
				commuteAmount += amount;
			} else {
				// 支給間隔：2ヶ月、3ヶ月、6ヶ月
				int interval     ;	// 支給間隔
				int payStartMonth;	// 支給開始月
				int currentMonth ;	// 計算対象月
				// 支給間隔取得
				if (cmCommutationDto.getPaymentInterval().equals("2")) {
					interval = 2;
				} else if (cmCommutationDto.getPaymentInterval().equals("3")) {
					interval = 3;
				} else {
					interval = 6;
				}
				// 支給開始月取得
				payStartMonth = MospUtility.getMonth(cmCommutationDto.getPayStartMonth());
				// 計算対象月取得
				currentMonth  = MospUtility.getMonth(payment.getCalcMonth());
				// 個別当月通勤手当設定
				amount = (int)MospUtility.round(
						cmCommutationDto.getCommuteAmount() / (double)interval, 0, BigDecimal.ROUND_FLOOR);
				// 個別当月課税通勤費設定
				taxed  = (int)MospUtility.round(
						cmCommutationDto.getCommuteTaxed () / (double)interval, 0, BigDecimal.ROUND_FLOOR);
				// 通勤手当支給月判断
				if (isAllowMonth(interval, payStartMonth, currentMonth)) {
					// 当月通勤手当、当月課税通勤費再設定
					amount += cmCommutationDto.getCommuteAmount() - amount * interval;
					taxed  += cmCommutationDto.getCommuteTaxed () - taxed  * interval;
				}
				// 当月通勤手当及び当月課税通勤費加算
				monthlyAmount += amount;
				monthlyTaxed  += taxed ;
				// 支給通勤手当加算
				if (cmCommutationDto.getLumpMonthlyType().equals("1")) {
					// 一括区分：一括
					if (isAllowMonth(interval, payStartMonth, currentMonth)) {
						// 支給月の場合
						commuteAmount += cmCommutationDto.getCommuteAmount();
					}
				} else {
					// 一括区分：月割
					commuteAmount += amount;
				}
			}
			// 交通用具利用者の限度額超過による課税通勤費
			if (cmCommutationDto.getCommuteType().equals(PayrollConst.COMMUTE_TYPE_CAR)) {
				float distance = cmCommutationDto.getCommuteDistance();
				// 課税通勤費取得
				monthlyTaxed += LegalUtility.getTaxationForCarCommute(
						amount - taxed, distance);
			}
		}
		// 通勤手当の限度額超過による課税通勤費
		monthlyTaxed  += LegalUtility.getTaxationForCommutation(monthlyAmount - monthlyTaxed);
		// 給与情報設定
		payment.setCommutation(commuteAmount);
		payment.setCommuteTax (monthlyTaxed );
	}
	
	/**
	 * 支給月を判定する。
	 * @param paymetInterval 支給間隔
	 * @param payStartMonth  支給開始月
	 * @param currentMonth   判定対象月
	 * @return 支給月判定結果(true：支給月、false：支給月でない)
	 */
	private boolean isAllowMonth(
			int paymetInterval, int payStartMonth, int currentMonth
	) {
		int allowMonth = payStartMonth;
		do {
			allowMonth += paymetInterval;
			if (12 < allowMonth) {
				allowMonth -= 12;
			}
			if (currentMonth == allowMonth) {
				return true;
			}
		} while (payStartMonth != allowMonth);
		return false;
	}
	
	/**
	 * 法定控除を計算する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 * @throws Exception
	 */
	private void calcLegalDeduction(Employee employee, Payment payment) throws Exception {
		// 社会保険料計算
		calcSocialInsurance(employee, payment);
		// 所得税計算
		calcIncomeTax(employee, payment);
		// 住民税設定
		setResidenceTax(employee, payment);
	}
	
	/**
	 * 社会保険料を計算する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 * @throws Exception 
	 */
	protected void calcSocialInsurance(Employee employee, Payment payment) throws Exception {
		// 社会保険個人負担料率取得準備
		float ratioHealthPremium   = 0;	// 健康保険料率 
		float ratioNursingPremium  = 0;	// 介護保険料率
		float ratioPensionPremium  = 0;	// 厚生年金料率
		float ratioFundPremium     = 0;	// 厚生年金基金料率
		float ratioUnemployPremium = 0;	// 雇用保険料率
		String fundParticipation   = LegalConst.FUND_PARTICIPATION_OFF;	// 厚生年金基金加入区分
		// 社会保険個人負担料率取得
		// 要事業所対応 ↓---------------------------------------------------------------------
		CmCompanyDao cmCompanyDao = new CmCompanyDao();
		cmCompanyDao.initDao(cfg, cmd, aspUser, user, conn);
		CmCompanyDto cmCompanyDto = cmCompanyDao.findAll();
		if (cmCompanyDto != null) {
			ratioHealthPremium   = cmCompanyDto.getHealthPremiumRate  ();	// 健康保険料率  
			ratioNursingPremium  = cmCompanyDto.getNursingPremiumRate ();	// 介護保険料率
			ratioPensionPremium  = cmCompanyDto.getPensionPremiumRate ();	// 厚生年金料率
			ratioFundPremium     = cmCompanyDto.getFundPremiumRate    ();	// 厚生年金基金料率
			ratioUnemployPremium = cmCompanyDto.getUnemployPremiumRate();	// 雇用保険料率
			fundParticipation    = cmCompanyDto.getFundParticipation  ();	// 厚生年金基金加入区分
		}
		// 要事業所対応 ↑---------------------------------------------------------------------
		int health   = 0;	// 健康保険料
		int nursing  = 0;	// 介護保険料
		int pension  = 0;	// 厚生年金料
		int fund     = 0;	// 厚生年金基金料
		int unemploy = 0;	// 雇用保険料
		// 健康保険料計算
		health = LegalUtility.calcPremium(
				employee.getHealthPayment(), ratioHealthPremium, BigDecimal.ROUND_HALF_UP);
		// 介護保険計算対象確認
		if(needNursingInsurance(employee, payment)) {
			// 介護保険料計算
			nursing = LegalUtility.calcPremium(
					employee.getHealthPayment(), ratioNursingPremium, BigDecimal.ROUND_HALF_UP);
		}
		// 厚生年金料計算
		pension = LegalUtility.calcPremium(employee.getPensionPayment(),
				ratioPensionPremium, BigDecimal.ROUND_HALF_DOWN);
		// 厚生年金基金料計算(厚生年金基金加入区分が「加入」の場合)
		if (fundParticipation.equals(LegalConst.FUND_PARTICIPATION_ON)) {
			fund = LegalUtility.calcPremium(employee.getPensionPayment(),
					ratioFundPremium, BigDecimal.ROUND_HALF_UP);
		}
		// 雇用保険計算対象額計算
		payment.calcUnemployInsuration();
		// 雇用保険料計算(雇用保険区分が「計算する」の場合)
		if (employee.getCalcUnemployType().equals(LegalConst.CALC_UNEMPLOY)) {
			unemploy = LegalUtility.calcPremium(payment.getInsureUnemploy(),
					ratioUnemployPremium / 1000, BigDecimal.ROUND_HALF_DOWN);
		}
		// 社会保険料設定
		payment.setHealthInsurance  (health  );
		payment.setNursingInsurance (nursing );
		payment.setPensionInsurance (pension );
		payment.setPensionFund      (fund    );
		payment.setEmployeeInsurance(unemploy);
		payment.calcSocInsTotal();
	}
	
	/**
	 * 介護保険計算要否を判断する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 * @return 介護保険計算要否
	 * @throws Exception 
	 */
	private boolean needNursingInsurance(
			Employee employee, Payment payment
	) throws Exception {
		// 介護保険料計算(介護保険区分が「対象」の場合)
		if (employee.getNursingInsType().equals(LegalConst.NURSING_INS_MANUAL)) {
			return true;
		}
		// 年齢で計算対象か判断(介護保険区分が「対象(自動)」の場合)
		if (employee.getNursingInsType().equals(LegalConst.NURSING_INS_AUTO)) {
			// 計算年月最終日+1日取得
			Date toDate = PayrollUtility.addDay(getEndDate(payment.getCalcMonth()), 1);
			// 年齢取得(対象年月最終日+1日で判断)
			int age = employee.getAge(toDate);
			// 40歳以上65歳未満は計算対象
			if (age >= 40 && age < 65) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 所得税を計算する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 */
	protected void calcIncomeTax(Employee employee, Payment payment) {
		// 扶養等の数取得
		byte dependent = employee.getDependent();
		// 課税区分取得
		String incomeTaxType = employee.getIncomeTaxType();
		// 課税対象額計算
		payment.calcTaxation();
		// 社会保険合計計算
		payment.calcSocInsTotal();
		// 社会保険料等控除後の給与等の金額取得
		int afterDeduction = payment.getAfterDeduction();
		// 所得税額計算及び設定
		int incomeTax = LegalUtility.calcIncomeTax(incomeTaxType, afterDeduction, dependent);
		// 扶養等の数設定
		payment.setDependent(dependent);
		// 所得税設定
		payment.setIncomeTax(incomeTax);
	}
	
	/**
	 * 住民税を設定する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 */
	private void setResidenceTax(Employee employee, Payment payment) {
		int residenceTax = 0;
		switch (MospUtility.getMonth(payment.getCalcMonth())) {
			case 6:
				residenceTax = employee.getJuneAmount();
				break;
			case 7:
				residenceTax = employee.getJulyAmount    ();
				break;
			case 8:
				residenceTax = employee.getAugustAmount  ();
				break;
			case 9:
				residenceTax = employee.getAugustAmount  ();
				break;
			case 10:
				residenceTax = employee.getOctoberAmount ();
				break;
			case 11:
				residenceTax = employee.getNovemberAmount();
				break;
			case 12:
				residenceTax = employee.getDecemberAmount();
				break;
			case 1:
				residenceTax = employee.getJanuaryAmount ();
				break;
			case 2:
				residenceTax = employee.getFebruaryAmount();
				break;
			case 3:
				residenceTax = employee.getMarchAmount   ();
				break;
			case 4:
				residenceTax = employee.getAprilAmount   ();
				break;
			case 5:
				residenceTax = employee.getMayAmount     ();
				break;
			default:
				residenceTax = 0;
		}
		payment.setResidenceTax(residenceTax);
	}
	
	/**
	 * 合計を計算する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 */
	protected void calcTotal(Employee employee, Payment payment) {
		payment.calcUnemployInsuration();
		payment.calcSocInsTotal();
		payment.calcTaxation();
		payment.setIncomeTax(payment.getDeductionAmount(PayrollConst.DTL_INCOME_TAX));
		payment.calcAllowance();
		payment.calcDeduction();
		payment.calcPaymentTotal();
		calcDivideAccounts(employee, payment);
	}
	
	/**
	 * 各銀行への振込額及び現金支給額を計算する。
	 * @param employee 社員情報
	 * @param payment  給与伝票
	 */
	private void calcDivideAccounts(Employee employee, Payment payment) {
		// 変数宣言
		int bank1Amount = 0;
		int bank2Amount = 0;
		int cashAmount  = 0;
		// 支給方法情報取得(支給方法連番順)
		List<CmPaymentMeansDto> list = employee.getPaymentMeans();
		// 差引支給額取得
		int total = payment.getPaymentTotal();
		// 支給情報毎の処理
		int currentAmount = total;
		for (Iterator<CmPaymentMeansDto> it = list.iterator(); it.hasNext();) {
			CmPaymentMeansDto dto = it.next();
			// 振分金額取得
			int amount = getdividedAmount(dto, total, currentAmount);
			// 振分金額設定
			if (dto.getPaymentMeans().equals(PayrollConst.PAYMENT_MEANS_ACCOUNT)) {
				// 振込の場合
				if (dto.getMeansSequence() == PayrollConst.PAYMENT_MEANS_SEQ_1
						|| dto.getMeansSequence() == PayrollConst.PAYMENT_MEANS_SEQ_3) {
					bank1Amount += amount;
				} else {
					bank2Amount += amount;
				}
			} else {
				// 振込以外(現金)の場合
				cashAmount += amount;
			}
			// 残額計算
			currentAmount -= amount;
		}
		// 残額を現金に設定
		cashAmount += currentAmount;
		// 給与情報設定
		payment.setBank1PayAmount(bank1Amount);
		payment.setBank2PayAmount(bank2Amount);
		payment.setCashPayAmount (cashAmount );
	}
	
	/**
	 * 振分支給額取得
	 * @param dto           支給方法DTO
	 * @param totalAmount   振分総額
	 * @param currentAmount 振分元金額(残額)
	 */
	private int getdividedAmount(
			CmPaymentMeansDto dto, int totalAmount, int currentAmount
	) {
		// 支給情報が設定されていない場合
		if (dto == null) {
			return 0;
		}
		// 全額の場合
		if (dto.getAmountType().equals(PayrollConst.AMOUNT_TYPE_ALL)) {
			return currentAmount;
		}
		// 残額の場合
		if (dto.getAmountType().equals(PayrollConst.AMOUNT_TYPE_REST)) {
			return currentAmount;
		}
		// 振分支給額
		int dividingAmount = 0;
		// 金額区分毎の処理
		if (dto.getAmountType().equals(PayrollConst.AMOUNT_TYPE_AMOUNT)) {
			// 定額の場合
			dividingAmount = dto.getFixedAmount();
		} else if (dto.getAmountType().equals(PayrollConst.AMOUNT_TYPE_RATIO)) {
			// 定率の場合
			// 振分総額 * 振分率 / 100
			dividingAmount = (int)MospUtility.round(
					dto.getFixedAmount() / 100 * totalAmount, 0, BigDecimal.ROUND_HALF_UP);
		}
		// 振分元金額以上の場合
		if (dividingAmount > currentAmount) {
			dividingAmount = currentAmount;
		}
		return dividingAmount;
	}
	
	/**
	 * 明細値を取得する。<br>
	 * @param list       明細情報
	 * @param detailCode 明細コード
	 * @return 明細値
	 */
	private float getTimeAmount(List<DetailTimeDto> list, String detailCode) {
		for (Iterator<DetailTimeDto> it = list.iterator(); it.hasNext();) {
			DetailTimeDto dto = it.next();
			if (dto.getDetailCode().equals(detailCode)) {
				return dto.getAmount();
			}
		}
		return 0F;
	}
	
	/**
	 * 給与支給日を取得する。<br>
	 * @param list 明細情報
	 * @return 給与支給日
	 */
	private Date getPayDate(List<DetailTimeDto> list) {
		int year  = (int)getTimeAmount(list, PayrollConst.DTL_SNEN );
		int month = (int)getTimeAmount(list, PayrollConst.DTL_STUKI);
		int day   = (int)getTimeAmount(list, PayrollConst.DTL_SHI  );
		return getDate(year, month, day);
	}
	
	/**
	 * 年プルダウンアイテムを取得する。<br>
	 * @param startYear 開始年
	 * @param endYear   終了年
	 * @return 年プルダウンアイテム
	 */
	protected String[][] getYearArray(int startYear, int endYear) {
		int start = startYear;
		int end   = endYear  ;
		if (startYear > endYear) {
			start = endYear  ;
			end   = startYear;
		}
		String[][] aryYear = new String[end - start + 1][2];
		int i = 0;
		int index = 0;
		for (i = start; i <= end; i++) {
			aryYear[index][0] = String.valueOf(i);
			aryYear[index][1] = String.valueOf(i);
			index++;
		}
		return aryYear;
	}
	
	/**
	 * 月プルダウンアイテムを取得する。<br>
	 * @return 月プルダウンアイテム
	 */
	protected String[][] getMonthArray() {
		String[][] aryMonth = new String[12][2];
		for (int i = 0; i < 12; i++) {
			aryMonth[i][0] = String.valueOf(i + 1);
			aryMonth[i][1] = String.valueOf(i + 1);
		}
		return aryMonth;
	}
	
	/**
	 * 日プルダウンアイテムを取得する。<br>
	 * @return 日プルダウンアイテム
	 */
	protected String[][] getDayArray() {
		String[][] aryDay = new String[31][2];
		for (int i = 0; i < 31; i++) {
			aryDay[i][0] = String.valueOf(i + 1);
			aryDay[i][1] = String.valueOf(i + 1);
		}
		return aryDay;
	}
	
	/**
	 * 日付を取得する。<br>
	 * @param year  年
	 * @param month 月
	 * @param day   日
	 * @return 日付
	 * @throws MospException 
	 */
	public Date getDate(int year,  int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR        , year     );
		cal.set(Calendar.MONTH       , month - 1);
		cal.set(Calendar.DAY_OF_MONTH, day      );
		cal.set(Calendar.HOUR_OF_DAY , 0        );
		cal.set(Calendar.MINUTE      , 0        );
		cal.set(Calendar.SECOND      , 0        );
		cal.set(Calendar.MILLISECOND , 0        );
		return cal.getTime();
	}
	
	/**
	 * コード名称を取得する。
	 * @param code  取得対象コード
	 * @param array 取得元配列
	 * @return コード名称
	 */
	protected String getCodeName(String code, String[][] array) {
		String codeName = code;
		if (array == null) {
			return codeName;
		}
		for (int i = 0; i < array.length; i++) {
			if (code.equals(array[i][0])) {
				codeName = array[i][1];
				break;
			}
		}
		return codeName;
	}

	/**
	 * 銀行データ→JavaScriptファイル変換。<br>
	 * @param path     出力ファイルパス
	 * @param isCreate ファイル出力可否フラグ
	 * @throws Exception 
	 */
	public void convFinanceData(
			String path,
			boolean isCreate
	) throws Exception {
		// ファイルを取得
		File file = new File(path);
		// ファイルが存在しない場合またはisCreateがtrueの場合
		if (!file.exists() | isCreate) {
			FileOutputStream fos = new FileOutputStream(path, false);
			OutputStreamWriter osw = new OutputStreamWriter(fos, "Windows-31J");
			StringBuffer sb = new StringBuffer();
			// 銀行名列名設定
			setJSArray(sb);
	        osw.write(sb.toString());
			osw.close();
			fos.close();
		}
	}

	/**
	 * 銀行名、支店名配列設定
	 * @throws Exception 
	 */
	public void setJSArray(StringBuffer sb) throws Exception {
		// DAOの準備
		CmBankDao dao = new CmBankDao();
		dao.initDao(cfg, cmd, aspUser, user, conn);
		// 情報の取得
		List<CmBankDto> listBank = dao.findAll();
		// 銀行名
		String[] aryJsBankCode = new String[listBank.size()];
		String[]aryJsBankName  = new String[listBank.size()];
		for (int i = 0; i < listBank.size(); i++) {
			CmBankDto cmBankDto = listBank.get(i);
			aryJsBankCode[i] = cmBankDto.getBankCode();
			aryJsBankName[i] = cmBankDto.getBankName();
		}
		// DAOの準備
		CmBranchDao cmBranchDao = new CmBranchDao();
		cmBranchDao.initDao(cfg, cmd, aspUser, user, conn);
		// 情報の取得
		List<CmBranchDto> listBranch = cmBranchDao.findAll();
		// 配列の作成
		// 支店名
		String[] aryJsBraBankCode = new String[listBranch.size()];
		String[] aryJsBranchCode  = new String[listBranch.size()];
		String[] aryJsBranchName  = new String[listBranch.size()];
		for (int i = 0; i < listBranch.size(); i++) {
			aryJsBraBankCode[i] = listBranch.get(i).getBankCode  ();
			aryJsBranchCode [i] = listBranch.get(i).getBranchCode();
			aryJsBranchName [i] = listBranch.get(i).getBranchName();
		}
		// 銀行名、支店名配列設定
		setJSBankName(sb, aryJsBankCode, aryJsBankName, aryJsBraBankCode, aryJsBranchCode, aryJsBranchName);
	}

	/**
	 * JSへ譲渡する銀行名等設定
	 * @param sb
	 */
	private void setJSBankName(StringBuffer sb, String[] aryJsBankCode, String[] aryJsBankName
			, String[] aryJsBraBankCode, String[] aryJsBranchCode, String[] aryJsBranchName
	) {
		// 銀行コード
		if (aryJsBankCode.length == 0) {
			sb.append("aryJsBankCode = new Array();");
			sb.append(MospConst.LINE_SEPARATOR);
		} else {
			sb.append("aryJsBankCode = new Array(");
			// 設定なし配列をJSへ
			for (int i = 0; i < aryJsBankCode.length; i++) {
				if (i == aryJsBankCode.length - 1) {
					sb.append("\"" + aryJsBankCode[i] + "\"" + ");");
				} else {
					sb.append("\"" + aryJsBankCode[i] + "\"" + ", ");
				}
			}
			sb.append(MospConst.LINE_SEPARATOR);
		}
		// 銀行名
		if (aryJsBankName.length == 0) {
			sb.append("aryJsBankName = new Array();");
			sb.append(MospConst.LINE_SEPARATOR);
		} else {
			sb.append("aryJsBankName = new Array(");
			// 設定なし配列をJSへ
			for (int i = 0; i < aryJsBankName.length; i++) {
				if (i == aryJsBankName.length - 1) {
					sb.append("\"" + aryJsBankName[i] + "\"" + ");");
				} else {
					sb.append("\"" + aryJsBankName[i] + "\"" + ", ");
				}
			}
			sb.append(MospConst.LINE_SEPARATOR);

		}
		
		// 支店銀行コード
		if (aryJsBraBankCode.length == 0) {
			sb.append("aryJsBraBankCode = new Array();");
			sb.append(MospConst.LINE_SEPARATOR);
		} else {
			sb.append("aryJsBraBankCode = new Array(");
			// 設定なし配列をJSへ
			for (int i = 0; i < aryJsBraBankCode.length; i++) {
				if (i == aryJsBraBankCode.length - 1) {
					sb.append("\"" + aryJsBraBankCode[i] + "\"" + ");");
				} else {
					sb.append("\"" + aryJsBraBankCode[i] + "\"" + ", ");
				}
			}
			sb.append(MospConst.LINE_SEPARATOR);
		}
		
		// 支店コード
		if (aryJsBranchCode.length == 0) {
			sb.append("aryJsBranchCode = new Array();");
			sb.append(MospConst.LINE_SEPARATOR);
		} else {
			sb.append("aryJsBranchCode = new Array(");
			// 設定なし配列をJSへ
			for (int i = 0; i < aryJsBranchCode.length; i++) {
				if (i == aryJsBranchCode.length - 1) {
					sb.append("\"" + aryJsBranchCode[i] + "\"" + ");");
				} else {
					sb.append("\"" + aryJsBranchCode[i] + "\"" + ", ");
				}
			}
			sb.append(MospConst.LINE_SEPARATOR);
		}
		
		// 支店名
		if (aryJsBranchName.length == 0) {
			sb.append("aryJsBranchName = new Array();");
			sb.append(MospConst.LINE_SEPARATOR);
		} else {
			sb.append("aryJsBranchName = new Array(");
			// 設定なし配列をJSへ
			for (int i = 0; i < aryJsBranchName.length; i++) {
				if (i == aryJsBranchName.length - 1) {
					sb.append("\"" + aryJsBranchName[i] + "\"" + ");");
				} else {
					sb.append("\"" + aryJsBranchName[i] + "\"" + ", ");
				}
			}
		}
		sb.append(MospConst.LINE_SEPARATOR);
	}

	/**
	 * 会社銀行情報取得
	 * @return 設定リスト
	 * @throws NoSuchFieldException 
	 * @throws IllegalAccessException 
	 * @throws SQLException 
	 */
	protected String[][] getCoBankArray()
		throws SQLException, IllegalAccessException, NoSuchFieldException {
		// DAOの宣言
		CmCoBankDao cmCoBankDao = new CmCoBankDao();
		cmCoBankDao.initDao(cfg, cmd, aspUser, user, conn);
		// データの取得
		List<CmCoBankDto> list = cmCoBankDao.findAvailable();
		// データの整形
		String[][] array = new String[list.size()][2];
		for (int i = 0; i < list.size(); i++) {
			CmCoBankDto dto = list.get(i);
			array[i][0] = dto.getCoBankCode();
			array[i][1] = dto.getCoBankCode();
		}
		return array;
	}
	
	/**
	 * デフォルト日付を取得する。
	 * @return デフォルト日付
	 */
	private Date getDefaultDate() {
		return getDate(1901, 1, 1);
	}
	
	/**
	 * 計算月取得
	 * @return 計算月
	 * @throws Exception
	 */
	protected Date getCalcCurrent() throws Exception {
		EndCalcVo vo = (EndCalcVo)getVo();
		// 対象年月取得
		Date targetMonth = vo.getCalcDate();
		// DAO準備
		PtCurrentDao dao = new PtCurrentDao(cfg, cmd, aspUser, user, conn);
		//dao.initDao(cfg, cmd, aspUser, user, conn);
		PtCurrentDto dto = dao.findForKey(PayrollConst.OFFICE_ALL, PayrollConst.CALC_PAYMENT, targetMonth);
		if (dto != null) {
			return dto.getCalcDate();
		}
		return new Date();
	}
	
	
	/**
	 * 年の初日を取得する。
	 * @param year 対象年
	 * @return 年の初日
	 */
	protected Date getFirstDateOfTheYear(int year) {
		return getDate(year, 1, 1);
	}
	
	/**
	 * 年の最終日を取得する。
	 * @param year 対象年
	 * @return 年の最終日
	 */
	protected Date getLastDateOfTheYear(int year) {
		return getDate(year, 12, 31);
	}
	
}
