418 lines
15 KiB
Java
418 lines
15 KiB
Java
import java.io.BufferedReader;
|
||
import java.io.FileInputStream;
|
||
import java.io.FileOutputStream;
|
||
import java.io.IOException;
|
||
import java.io.InputStreamReader;
|
||
import java.io.OutputStreamWriter;
|
||
import java.io.PrintWriter;
|
||
import java.io.UncheckedIOException;
|
||
import java.sql.Connection;
|
||
import java.sql.DriverManager;
|
||
import java.sql.PreparedStatement;
|
||
import java.sql.ResultSet;
|
||
import java.sql.SQLException;
|
||
import java.time.DayOfWeek;
|
||
import java.time.LocalDate;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.time.format.DateTimeParseException;
|
||
import java.util.TreeSet;
|
||
|
||
import dto.Kin02Rec;
|
||
import dto.ZandatacPar;
|
||
import dto.ZanendacPar;
|
||
import dto.ZanmsgacPar;
|
||
import util.ConvUtil;
|
||
|
||
/**
|
||
* ============================================================
|
||
* 【COBOL→Java】KIN03EXP 休暇日別展開処理
|
||
* ============================================================
|
||
* PROGRAM-ID. KIN03EXP → 类 Kin03ExpMain(PGMパターン:キーブレイク集計)
|
||
*
|
||
* 使用DB(SQLite: data/kin.db):
|
||
* C1: LEAVE_RECORDS(STATUS='1') → 日別展開元
|
||
* C2: HOLIDAY_CALENDAR → 休日テーブル
|
||
* 使用文件(SELECT ASSIGN → 路径常量):
|
||
* W01OUTFIL(KIN02W01.DAT LEAVE-DAILY F80B) → W01OUT_FILE(COPY KIN02REC → dto.Kin02Rec)
|
||
* 使用SUB(CALL → 类#execute):
|
||
* SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
|
||
* 段落对应:
|
||
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100C1INITSOR→initC1()
|
||
* 1100C1FETCHSOR→fetchC1() 1200HDINNSOR→loadHolidays() 2000MAJSOR→majPara()
|
||
* 2100-PROCESS-EMP→majPara内 2200-EXPAND-DATE→expandDate() 2300-DATE-ADD-1→LocalDate.plusDays
|
||
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
|
||
*
|
||
* ※COBOLはカーソル/SQLエラーを EOF 扱い(ABENDしない)。ABENDは SUB01DAT失敗・W01 OPEN失敗のみ。
|
||
* ※INTEGER-OF-DATE/MOD 7 の曜日判定は Java LocalDate.getDayOfWeek() に読替(土日=週末除外、意味等価)。
|
||
* ※SEARCH ALL(ASC KEY) は TreeSet.contains に読替(昇順ロード維持、結果等価)。
|
||
* ※2300-DATE-ADD-1 の月末/閏年計算は LocalDate.plusDays(1) に読替(意味等価)。
|
||
*/
|
||
public class Kin03ExpMain {
|
||
|
||
// ==================== コンスタント領域 CNSARA ====================
|
||
|
||
private static final String CST_PRGIDX = "KIN03EXP";
|
||
private static final int CST_MSGSTR = 1;
|
||
private static final int CST_MSGFIN = 2;
|
||
private static final int CST_MSGSUBEEK = 5;
|
||
private static final int CST_MSGIINKES = 6;
|
||
private static final int CST_MSGOUTKES = 7;
|
||
private static final int CST_MSGKEYINF = 33;
|
||
private static final int CST_KN0002 = 2;
|
||
private static final int CST_ABD999 = 999;
|
||
|
||
// ==================== ファイル・DB定義 ====================
|
||
|
||
/** SELECT W01OUTFIL ASSIGN TO "KIN02W01.DAT". */
|
||
private static final String W01OUT_FILE = "data/KIN02W01.DAT";
|
||
/** EXEC SQL CONNECT TO 'data/kin.db'. */
|
||
private static final String DB_URL = "jdbc:sqlite:data/kin.db";
|
||
|
||
private static final DateTimeFormatter YMD = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||
|
||
// ==================== カウンタ領域 ====================
|
||
|
||
private long cunR01Inn;
|
||
private long cunW01Out;
|
||
|
||
// ==================== 作業領域 ====================
|
||
|
||
private String wrkR01Eof = "0";
|
||
/** 03 WRK-BFR-EMP-ID PIC X(008). キーブレイク用 */
|
||
private String wrkBfrEmpId = "";
|
||
/** 03 CUN-EMP-SUB PIC S9(009) COMP-3. 社員別小計 */
|
||
private long empSub;
|
||
/** 直近SQLCODE(デバッグ用、ABEND非対象) */
|
||
private int sqlcode;
|
||
|
||
// ==================== DBホスト変数(C1 FETCH 結果) ====================
|
||
|
||
private int sqlApplId;
|
||
private String sqlEmpId = "";
|
||
private String sqlLeaveType = "";
|
||
private String sqlStartDate = "";
|
||
private String sqlStartTime = "";
|
||
private String sqlEndDate = "";
|
||
private String sqlEndTime = "";
|
||
|
||
// ==================== 休日テーブル ====================
|
||
|
||
/** WRK-HOLIDAY-TABLE(OCCURS 1 TO 366, ASCENDING KEY)。SEARCH ALL → TreeSet。 */
|
||
private final TreeSet<String> hdSet = new TreeSet<String>();
|
||
/** 03 WRK-HD-COUNT PIC 9(004). */
|
||
private int hdCount;
|
||
|
||
// ==================== サブプログラム連絡領域 ====================
|
||
|
||
private final ZandatacPar d01UbsPar = new ZandatacPar();
|
||
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
|
||
private final ZanendacPar e01AbdPar = new ZanendacPar();
|
||
|
||
// ==================== 入出力レコード ====================
|
||
|
||
/** 01 W01OUTREC.(FD W01OUTFIL)COPY KIN02REC → W01 */
|
||
private final Kin02Rec w01 = new Kin02Rec();
|
||
|
||
// ==================== ハンドル ====================
|
||
|
||
private Connection conn;
|
||
private PreparedStatement psC1;
|
||
private ResultSet rsC1;
|
||
private PreparedStatement psC2;
|
||
private ResultSet rsC2;
|
||
private PrintWriter w01OutFil;
|
||
|
||
// ==================== PROCEDURE DIVISION ====================
|
||
|
||
public static void main(String[] args) {
|
||
new Kin03ExpMain().run();
|
||
}
|
||
|
||
private void run() {
|
||
try {
|
||
initPara();
|
||
while (!"1".equals(wrkR01Eof)) { // PERFORM 2000MAJSOR UNTIL WRK-R01EOF='1'
|
||
majPara();
|
||
}
|
||
termPara();
|
||
} finally {
|
||
closeAll();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 段落 1000ITTSOR:初期処理。
|
||
* 開始/コンパイル時刻→WRK初期化→DB接続→運用日付→休日読込→W01 OPEN→C1初回FETCH。
|
||
*/
|
||
private void initPara() {
|
||
msgOutPara(CST_MSGSTR, null, null);
|
||
|
||
String compiled = java.time.LocalDateTime.now()
|
||
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
|
||
|
||
resetWork();
|
||
|
||
// DB接続(失敗時は conn=null → 以降のカーソルが EOF 扱い、ABENDしない)
|
||
try {
|
||
Class.forName("org.sqlite.JDBC");
|
||
conn = DriverManager.getConnection(DB_URL);
|
||
conn.setAutoCommit(false);
|
||
} catch (ClassNotFoundException | SQLException e) {
|
||
conn = null; // COBOL同様、CONNECT/カーソル失敗は EOF として処理
|
||
}
|
||
|
||
// 運用日付取得 CALL 'SUB01DAT'
|
||
d01UbsPar.reset();
|
||
Sub01DatSub.execute(d01UbsPar);
|
||
if (d01UbsPar.getD01FkiCod() != 0) {
|
||
// IF D01FKICOD NOT = ZERO → メッセージ + ABEND(999)
|
||
msgOutPara(CST_MSGSUBEEK, "SUB01DAT",
|
||
ConvUtil.padLeftZero(d01UbsPar.getD01FkiCod(), 4));
|
||
abendPara();
|
||
}
|
||
|
||
loadHolidays(); // C2: HOLIDAY_CALENDAR 全件ロード
|
||
openW01(); // W01 OPEN(失敗時は ABEND)
|
||
initC1(); // C1 DECLARE/OPEN/初回FETCH
|
||
}
|
||
|
||
private void resetWork() {
|
||
cunR01Inn = 0;
|
||
cunW01Out = 0;
|
||
wrkBfrEmpId = "";
|
||
empSub = 0;
|
||
sqlcode = 0;
|
||
hdCount = 0;
|
||
}
|
||
|
||
/**
|
||
* 段落 1200HDINNSOR:C2 HOLIDAY_CALENDAR 読込。
|
||
* PERFORM UNTIL SQLCODE NOT = 0 → WRK-HD-COUNT++、WRK-HD-DATE格納。
|
||
* エラー時は件数0(COBOLはループ終了のみ)。
|
||
*/
|
||
private void loadHolidays() {
|
||
if (conn == null) {
|
||
return;
|
||
}
|
||
try (PreparedStatement ps = conn.prepareStatement(
|
||
"SELECT HOLIDAY_DATE FROM HOLIDAY_CALENDAR ORDER BY HOLIDAY_DATE")) {
|
||
try (ResultSet rs = ps.executeQuery()) {
|
||
while (rs.next()) {
|
||
hdSet.add(rs.getString("HOLIDAY_DATE"));
|
||
hdCount++;
|
||
}
|
||
}
|
||
} catch (SQLException e) {
|
||
// エラー時は取得済み分まで(COBOLは PERFORM UNTIL SQLCODE NOT=0 で終了)
|
||
sqlcode = e.getErrorCode();
|
||
}
|
||
}
|
||
|
||
/** W01 OPEN(FILE STATUS 異常時は ABEND 999)。 */
|
||
private void openW01() {
|
||
try {
|
||
w01OutFil = new PrintWriter(
|
||
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), "UTF-8"), true);
|
||
} catch (IOException e) {
|
||
abendPara(); // IF WS-W01-STATUS NOT = '00' → ABEND
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 段落 1100C1INITSOR:C1 DECLARE/OPEN/初回FETCH。
|
||
* SQLCODE=0 → CUN-R01INN++、 ELSE → WRK-R01EOF='1'。
|
||
*/
|
||
private void initC1() {
|
||
if (conn == null) {
|
||
wrkR01Eof = "1";
|
||
return;
|
||
}
|
||
try {
|
||
psC1 = conn.prepareStatement(
|
||
"SELECT APPLICATION_ID, EMP_ID, LEAVE_TYPE, START_DATE, "
|
||
+ " START_TIME, END_DATE, END_TIME"
|
||
+ " FROM LEAVE_RECORDS WHERE STATUS = '1'"
|
||
+ " ORDER BY EMP_ID, START_DATE");
|
||
rsC1 = psC1.executeQuery();
|
||
fetchC1();
|
||
} catch (SQLException e) {
|
||
sqlcode = e.getErrorCode();
|
||
wrkR01Eof = "1"; // カーソルエラーは EOF 扱い
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 段落 1100C1FETCHSOR / 1100C1INITSOR 内 FETCH。
|
||
* SQLCODE=0 → ホスト変数格納 + CUN-R01INN++、 ELSE → WRK-R01EOF='1'。
|
||
*/
|
||
private void fetchC1() {
|
||
if (rsC1 == null) {
|
||
wrkR01Eof = "1";
|
||
return;
|
||
}
|
||
try {
|
||
if (rsC1.next()) {
|
||
sqlApplId = rsC1.getInt("APPLICATION_ID");
|
||
sqlEmpId = rsC1.getString("EMP_ID");
|
||
sqlLeaveType = rsC1.getString("LEAVE_TYPE");
|
||
sqlStartDate = rsC1.getString("START_DATE");
|
||
sqlStartTime = rsC1.getString("START_TIME");
|
||
sqlEndDate = rsC1.getString("END_DATE");
|
||
sqlEndTime = rsC1.getString("END_TIME");
|
||
cunR01Inn++;
|
||
} else {
|
||
wrkR01Eof = "1";
|
||
}
|
||
} catch (SQLException e) {
|
||
sqlcode = e.getErrorCode();
|
||
wrkR01Eof = "1"; // フェッチエラーは EOF 扱い(COBOL準拠)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 段落 2000MAJSOR:主処理(社員番号キーブレイク)。
|
||
* SQL-EMP-ID→WRK-BFR-EMP-ID、1社員分処理(2100)→社員別小計出力。
|
||
*/
|
||
private void majPara() {
|
||
wrkBfrEmpId = sqlEmpId; // キー保存
|
||
empSub = 0; // CUN-EMP-SUB 初期化
|
||
|
||
// 2100-PROCESS-EMP:EOF またはキー変化までループ
|
||
while (!"1".equals(wrkR01Eof) && sqlEmpId.equals(wrkBfrEmpId)) {
|
||
expandDate(); // 2200-EXPAND-DATE
|
||
fetchC1(); // 次レコード FETCH
|
||
}
|
||
|
||
// 社員別小計出力(キーブレイク)
|
||
msgOutPara(CST_MSGKEYINF, wrkBfrEmpId, Long.toString(empSub), "EMP SUB");
|
||
}
|
||
|
||
/**
|
||
* 段落 2200-EXPAND-DATE:開始日〜終了日を日別展開。
|
||
* 週末(Sat/Sun)・休日を除外し、非週末かつ非休日で W01 出力。
|
||
*/
|
||
private void expandDate() {
|
||
LocalDate cur;
|
||
LocalDate end;
|
||
try {
|
||
cur = LocalDate.parse(sqlStartDate, YMD);
|
||
end = LocalDate.parse(sqlEndDate, YMD);
|
||
} catch (DateTimeParseException e) {
|
||
return; // 日付解析不可は当該申請をスキップ(COBOLは 0/SPACE で CONTINUE 相当)
|
||
}
|
||
|
||
while (!cur.isAfter(end)) {
|
||
DayOfWeek dow = cur.getDayOfWeek();
|
||
if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
|
||
// 非週末:休日テーブル検索
|
||
String ymd = cur.format(YMD);
|
||
if (!hdSet.contains(ymd)) {
|
||
w01.reset();
|
||
w01.setEmpId(wrkBfrEmpId); // MOVE WRK-BFR-EMP-ID
|
||
w01.setDate(Integer.parseInt(ymd)); // MOVE WRK-DATE-ALPHA
|
||
w01.setLeaveType(sqlLeaveType); // MOVE SQL-LEAVE-TYPE
|
||
w01.setStartTime(Integer.parseInt(sqlStartTime)); // MOVE SQL-START-TIME
|
||
w01.setEndTime(Integer.parseInt(sqlEndTime)); // MOVE SQL-END-TIME
|
||
w01.setApplId(sqlApplId); // MOVE NUMVAL(SQL-APPL-ID)
|
||
writeW01();
|
||
empSub++;
|
||
}
|
||
}
|
||
cur = cur.plusDays(1); // 2300-DATE-ADD-1(LocalDate が月末/閏年を処理)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 段落 3000STPSOR:終了処理。
|
||
* CURSOR CLOSE→W01 CLOSE→件数メッセージ×3→終了メッセージ。
|
||
*/
|
||
private void termPara() {
|
||
// *** 件数メッセージ出力
|
||
msgOutPara(CST_MSGIINKES, "LEAVE_RECORDS", Long.toString(cunR01Inn));
|
||
msgOutPara(CST_MSGOUTKES, "KIN02W01", Long.toString(cunW01Out));
|
||
msgOutPara(CST_MSGKEYINF, "HOLIDAYS LOADED", Long.toString(hdCount));
|
||
|
||
// *** 終了メッセージ出力
|
||
msgOutPara(CST_MSGFIN, null, null);
|
||
}
|
||
|
||
/**
|
||
* 段落 4000MSGOUTSOR:メッセージ編集出力処理。
|
||
* M00UMKDATS22-03(1:1)/-04(1:1) に '2'、-05 にプログラムIDを設定し SUB02MSG を呼ぶ。
|
||
*/
|
||
private void msgOutPara(final int msgCod, final String p1, final String p2) {
|
||
msgOutPara(msgCod, p1, p2, null);
|
||
}
|
||
|
||
private void msgOutPara(final int msgCod, final String p1, final String p2, final String p3) {
|
||
m00MhoPar.reset();
|
||
m00MhoPar.setM00MsgCod(msgCod);
|
||
if (p1 != null) {
|
||
m00MhoPar.setM00Umkdats2201(p1);
|
||
}
|
||
if (p2 != null) {
|
||
m00MhoPar.setM00Umkdats2202(p2);
|
||
}
|
||
if (p3 != null) {
|
||
m00MhoPar.setM00Umkdats2203(p3);
|
||
}
|
||
String p3cur = m00MhoPar.getM00Umkdats2203();
|
||
String newP3 = (p3cur.length() == 0) ? ConvUtil.padRight(Integer.toString(CST_KN0002), 30)
|
||
: ("2" + p3cur.substring(1));
|
||
m00MhoPar.setM00Umkdats2203(newP3);
|
||
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
|
||
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
|
||
Sub02MsgSub.execute(m00MhoPar);
|
||
}
|
||
|
||
/**
|
||
* 段落 9999ABDSOR:ABEND処理(ABENDコード 999)。
|
||
*/
|
||
private void abendPara() {
|
||
e01AbdPar.reset();
|
||
e01AbdPar.setE01AbdCod(CST_ABD999);
|
||
Sub03EndSub.execute(e01AbdPar); // System.exit(999)
|
||
}
|
||
|
||
// ==================== ファイル操作補助 ====================
|
||
|
||
/** WRITE W01OUTREC./ADD 1 TO CUN-W01OUT. */
|
||
private void writeW01() {
|
||
w01OutFil.println(w01.toRecordLine());
|
||
cunW01Out++;
|
||
}
|
||
|
||
/** CURSOR / W01 / DB 接続のクローズ(finally からも呼ばれ、冪等)。 */
|
||
private void closeAll() {
|
||
closeQuiet(rsC1);
|
||
closeQuiet(psC1);
|
||
closeQuiet(rsC2);
|
||
closeQuiet(psC2);
|
||
closeQuiet(w01OutFil);
|
||
closeQuiet(conn);
|
||
rsC1 = null;
|
||
psC1 = null;
|
||
rsC2 = null;
|
||
psC2 = null;
|
||
w01OutFil = null;
|
||
conn = null;
|
||
}
|
||
|
||
private static void closeQuiet(final Object handle) {
|
||
try {
|
||
if (handle instanceof ResultSet) {
|
||
((ResultSet) handle).close();
|
||
} else if (handle instanceof PreparedStatement) {
|
||
((PreparedStatement) handle).close();
|
||
} else if (handle instanceof PrintWriter) {
|
||
((PrintWriter) handle).close();
|
||
} else if (handle instanceof Connection) {
|
||
((Connection) handle).close();
|
||
}
|
||
} catch (Exception e) {
|
||
// CLOSE異常は無視
|
||
}
|
||
}
|
||
}
|