Files
cobol-tna-system/JavaSrc/src/Kin03ExpMain.java
T

418 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 → 类 Kin03ExpMainPGMパターン:キーブレイク集計)
*
* 使用DBSQLite: data/kin.db):
* C1: LEAVE_RECORDS(STATUS='1') → 日別展開元
* C2: HOLIDAY_CALENDAR → 休日テーブル
* 使用文件(SELECT ASSIGN → 路径常量):
* W01OUTFIL(KIN02W01.DAT LEAVE-DAILY F80B) → W01OUT_FILECOPY KIN02REC → dto.Kin02Rec
* 使用SUBCALL → 类#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-TABLEOCCURS 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 W01OUTFILCOPY 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;
}
/**
* 段落 1200HDINNSORC2 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 OPENFILE 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
}
}
/**
* 段落 1100C1INITSORC1 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-EMPEOF またはキー変化までループ
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-1LocalDate が月末/閏年を処理)
}
}
/**
* 段落 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);
}
/**
* 段落 9999ABDSORABEND処理(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異常は無視
}
}
}