Add JavaSrc: cobol-tna-system COBOL->Java conversion (ZAN01-06/KIN01-09/KYU01-09 + 变换规则 doc)

This commit is contained in:
opencode
2026-08-29 20:50:08 +08:00
parent c9ea370397
commit f99f6696a6
71 changed files with 15350 additions and 0 deletions
+417
View File
@@ -0,0 +1,417 @@
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異常は無視
}
}
}