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
+127
View File
@@ -0,0 +1,127 @@
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* ============================================================
* 【COBOL→Java 标准例】数据库访问(EXEC SQL → JDBC
* ============================================================
* 对应关系:
* EXEC SQL CONNECT TO ... END-EXEC → DriverManager.getConnection(url, user, pass)
* EXEC SQL DECLARE C1 CURSOR FOR SELECT ... → conn.prepareStatement(sql)
* EXEC SQL OPEN C1 → rs = ps.executeQuery()
* EXEC SQL FETCH C1 INTO :WS-X → rs.next() + rs.getXxx(...)
* EXEC SQL CLOSE C1 → try-with-resources 自动 close
* EXEC SQL INSERT/UPDATE ... :HOST-VAR END-EXEC → ps.setXxx(i, hostVar) + executeUpdate()
* EXEC SQL COMMIT END-EXEC → conn.commit()
* EXEC SQL ROLLBACK END-EXEC → conn.rollback()
* EXEC SQL WHENEVER SQLERROR ... → try-catch (SQLException)
*
* 运行前提:lib 目录下放置所用数据库的 JDBC 驱动 jar,例如:
* DB2 : db2jcc4.jar (com.ibm.db2.jcc.DB2Driver)
* Oracle : ojdbc8.jar (oracle.jdbc.OracleDriver)
* PostgreSQL : postgresql.jar (org.postgresql.Driver)
* 单独运行:java -cp "out;lib/*" DbSample
*/
public class DbSample {
/** CONNECT 参数(按实际环境修改) */
private static final String URL = "jdbc:db2://localhost:50000/TESTDB";
private static final String USER = "db2inst1";
private static final String PASS = "password";
public static void main(String[] args) {
Connection conn = null;
try {
// EXEC SQL CONNECT TO TESTDB USER db2inst1 USING *** END-EXEC
conn = DriverManager.getConnection(URL, USER, PASS);
conn.setAutoCommit(false); // 批处理程序的标准做法:显式事务控制
selectCursorPara(conn); // 游标读取
updatePara(conn); // 单行更新
// EXEC SQL COMMIT END-EXEC
conn.commit();
System.out.println("[db] NORMAL END (COMMIT)");
} catch (SQLException e) {
// EXEC SQL WHENEVER SQLERROR GO TO ERROR-PROC END-EXEC
System.out.println("[db] SQLERROR code=" + e.getErrorCode() + " msg=" + e.getMessage());
rollbackQuiet(conn);
} finally {
closeQuiet(conn);
}
}
/**
* 游标读取段落。
* COBOL:
* DECLARE C1 CURSOR FOR
* SELECT EMP_ID, EMP_NAME, EMP_SALARY
* FROM TBL_EMP
* WHERE EMP_DEPT = :WS-DEPT
* OPEN C1
* PERFORM UNTIL SQLCODE NOT = ZERO
* FETCH C1 INTO :WS-EMP-ID, :WS-EMP-NAME, :WS-SALARY
* END-PERFORM
* CLOSE C1
*/
private static void selectCursorPara(final Connection conn) throws SQLException {
String sql = "SELECT EMP_ID, EMP_NAME, EMP_SALARY FROM TBL_EMP WHERE EMP_DEPT = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "10"); // 宿主变量 :WS-DEPT → setXxx
try (ResultSet rs = ps.executeQuery()) { // OPEN C1
while (rs.next()) { // FETCH 循环(SQLCODE=100 即结束)
String empId = rs.getString("EMP_ID"); // :WS-EMP-ID
String empName = rs.getString("EMP_NAME"); // :WS-EMP-NAME
BigDecimal salary = rs.getBigDecimal("EMP_SALARY"); // :WS-SALARY
System.out.println("[db] " + empId + " " + empName + " " + salary.toPlainString());
}
} // CLOSE C1(自动)
}
}
/**
* 单行更新段落(宿主变量 → setXxx)。
* COBOL:
* EXEC SQL
* UPDATE TBL_EMP
* SET EMP_SALARY = :WS-NEW-SALARY
* WHERE EMP_ID = :WS-EMP-ID
* END-EXEC
*/
private static void updatePara(final Connection conn) throws SQLException {
String sql = "UPDATE TBL_EMP SET EMP_SALARY = ? WHERE EMP_ID = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setBigDecimal(1, new BigDecimal("40000.00")); // :WS-NEW-SALARY
ps.setString(2, "00001"); // :WS-EMP-ID
int updCnt = ps.executeUpdate(); // 更新件数(SQLERRD(3) 相当)
System.out.println("[db] UPDATE COUNT=" + updCnt);
}
}
/** EXEC SQL ROLLBACK END-EXEC(异常时) */
private static void rollbackQuiet(final Connection conn) {
if (conn != null) {
try {
conn.rollback();
System.out.println("[db] ROLLBACK DONE");
} catch (SQLException e) {
System.out.println("[db] ROLLBACK FAILED: " + e.getMessage());
}
}
}
private static void closeQuiet(final Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.out.println("[db] CLOSE FAILED: " + e.getMessage());
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* ============================================================
* 【COBOL→Java】ファイル入出力ヘルパ(JDK8 互換)
* ============================================================
* JDK8 は FileReader/FileWriter の (String, Charset) コンストラクタを持たないため、
* InputStreamReader/OutputStreamWriter 経由で UTF-8 固定長処理を提供する。
* デフォルトパッケージに配置し、各 ZAN メインから import 不要で利用可能。
*/
public final class FileIo {
private FileIo() {
}
public static BufferedReader openReader(final String path) throws IOException {
return new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
}
public static BufferedWriter openWriter(final String path) throws IOException {
return new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(path), StandardCharsets.UTF_8));
}
}
+114
View File
@@ -0,0 +1,114 @@
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.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import dto.EmpRec;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java 标准例】顺序文件处理
* ============================================================
* COBOL 与 Java 的对应关系:
* SELECT EMPFILE ASSIGN TO SYS001 → 文件路径常量 INP_FILE
* FD EMPFILE. 01 EMPREC. → EmpRec DTO + substring 拆分
* OPEN INPUT EMPFILE → new BufferedReader(...)
* READ EMPFILE AT END MOVE 'Y' TO EOF-FLG → line = br.readLine(); line == null 即 AT END
* CLOSE EMPFILE → try-with-resources 自动 close
* OPEN OUTPUT REPFILE → new PrintWriter(...)
* WRITE REP-REC → pw.println(...)
*
* 文件编码统一为 UTF-8(原 COBOL 为系统代码页时,转换时需确认)。
*/
public class FileSample {
/** SELECT EMPFILE ASSIGN TO ... */
private static final String INP_FILE = "data/EMPFILE.txt";
private static final String OUT_FILE = "data/REPOUT.txt";
public static void execute() {
List<EmpRec> list = readFilePara();
writeFilePara(list);
}
/**
* 读入处理段落。
* COBOL:
* READ-EMP-PARA.
* PERFORM UNTIL EOF-FLG = 'Y'
* READ EMPFILE AT END MOVE 'Y' TO EOF-FLG
* NOT AT END PERFORM REC-PROC-PARA
* END-PERFORM.
*/
private static List<EmpRec> readFilePara() {
List<EmpRec> list = new ArrayList<EmpRec>();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(INP_FILE), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) { // AT END → readLine() == null
list.add(parseRec(line));
}
} catch (IOException e) {
// FILE STATUS 35(文件不存在)等 → 异常处理
throw new UncheckedIOException(e);
}
return list;
}
/**
* 记录拆分:01 层各 05 项按字节位置 substring(固定长记录的标准转换方式)。
* 01 EMPREC.
* 05 EMP-ID PIC X(5). → [0,5)
* 05 EMP-NAME PIC X(20). → [5,25)
* 05 EMP-DEPT PIC X(2). → [25,27)
* 05 EMP-SALARY PIC 9(7)V99. → [27,36) 隐含小数2位
*/
private static EmpRec parseRec(final String rec) {
EmpRec r = new EmpRec();
r.setEmpId(rec.substring(0, 5));
r.setEmpName(rec.substring(5, 25).trim());
r.setEmpDept(rec.substring(25, 27));
r.setEmpSalary(ConvUtil.toDecimal(rec.substring(27, 36), 2));
return r;
}
/**
* 报表输出段落。
* COBOL:
* WRITE-REP-PARA.
* OPEN OUTPUT REPFILE
* PERFORM UNTIL EOF-FLG = 'Y' (逐件 WRITE + 集计)
* CLOSE REPFILE.
*/
private static void writeFilePara(final List<EmpRec> list) {
BigDecimal wsTotal = BigDecimal.ZERO; // 05 WS-TOTAL PIC 9(7)V99.
int wsCnt = 0; // 05 WS-CNT PIC 9(3).
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(OUT_FILE), "UTF-8"))) {
pw.println("*** EMP SALARY REPORT ***");
for (EmpRec r : list) {
// WRITE REP-RECPIC 编辑:padRight 对应 X(20) 定长化)
pw.println(ConvUtil.padRight(r.getEmpId(), 6)
+ ConvUtil.padRight(r.getEmpName(), 20)
+ ConvUtil.padRight(r.getEmpDept(), 4)
+ r.getEmpSalary().setScale(2).toPlainString());
// ADD EMP-SALARY TO WS-TOTAL. / ADD 1 TO WS-CNT.
wsTotal = wsTotal.add(r.getEmpSalary());
wsCnt++;
}
pw.println("CNT=" + ConvUtil.padLeftZero(wsCnt, 3)
+ " TOTAL=" + wsTotal.setScale(2).toPlainString());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
System.out.println("[file] report output: " + OUT_FILE);
}
}
+647
View File
@@ -0,0 +1,647 @@
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import dto.Kin01Rec;
import dto.Kin05Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanchkacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KIN01INP 休暇申請CSV取込・検証処理
* ============================================================
* PROGRAM-ID. KIN01INP → 类 Kin01InpMainPGMパターン:振り分け)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R01INNFIL(KIN01R01 CSV入力 F80B) → R01INN_FILE
* W01OUTFIL(KIN01W01 WORK-LEAVE F80B)→ W01OUT_FILECOPY KIN01REC → dto.Kin01Rec
* W02OUTFIL(KIN01W02 ERROR-LOG V200B)→ W02OUT_FILECOPY KIN05REC → dto.Kin05Rec
* 使用SUBCALL → 类#execute):
* SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub / SUB04CHK→Sub04ChkSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01InnPara()
* 2000MAJSOR→majPara() 2010CSVSOR→csvPara() 2020LEAVSERSOR→leavserPara()
* 2030VALIDATESOR→validatePara() 2040CANCELSOR→cancelPara() 2050ERRORSOR→errorPara()
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 与原代码的差异(详见 doc\conv\KIN01INP_对照表.md 疑问·回答记录):
* #Q1 字段数判定按设计意图(split后字段数≠8),TALLYING分隔符计数仅用于错误消息
* #Q2 有効申請のW01出力は APPL-ID=0(原代码为CSV值,以设计书为准)
* #Q3 FUNCTION WHEN-COMPILED → 程序启动时刻代替
* #Q5 複合条件チェック改用 WRK-CSV-* 项目(原代码误用W01项目恒不成立)
*/
public class Kin01InpMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KIN01INP'. */
private static final String CST_PRGIDX = "KIN01INP";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. サブエラー */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-STAT-1 PIC X(001) VALUE '1'. */
private static final String CST_STAT_1 = "1";
/** 03 CNS-STAT-9 PIC X(001) VALUE '9'. */
private static final String CST_STAT_9 = "9";
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KIN01R01.CSV入力) */
private static final String R01INN_FILE = "data/KIN01R01.csv";
/** SELECT W01OUTFIL ASSIGN TO KIN01W01.WORK-LEAVE出力) */
private static final String W01OUT_FILE = "data/KIN01W01.txt";
/** SELECT W02OUTFIL ASSIGN TO KIN01W02.ERROR-LOG出力) */
private static final String W02OUT_FILE = "data/KIN01W02.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-U06 PIC 9(008). 運用日付 */
private int wrkU06;
/** 03 WRK-R01EOF PIC X(001). 読込フラグ */
private String wrkR01Eof = "0";
/** 03 WRK-COMMA-CNT PIC 9(002) COMP. UNSTRING TALLYING(分隔符计数) */
private int wrkCommaCnt;
/** 03 WRK-LT-FOUND PIC X(001). 休暇種別検索結果 */
private String wrkLtFound = "0";
/** 03 WRK-ERR-TYPE PIC X(001). エラー種別 F/L/S */
private String wrkErrType = "";
/** 03 WRK-CSV-APPL-ID PIC X(009). */
private String wrkCsvApplId = "";
/** 03 WRK-CSV-EMP-ID PIC X(008). */
private String wrkCsvEmpId = "";
/** 03 WRK-CSV-START-DATE PIC X(008). */
private String wrkCsvStartDate = "";
/** 03 WRK-CSV-START-TIME PIC X(004). */
private String wrkCsvStartTime = "";
/** 03 WRK-CSV-END-DATE PIC X(008). */
private String wrkCsvEndDate = "";
/** 03 WRK-CSV-END-TIME PIC X(004). */
private String wrkCsvEndTime = "";
/** 03 WRK-CSV-LEAVE-TYPE PIC X(002). */
private String wrkCsvLeaveType = "";
/** 03 WRK-CSV-STATUS PIC X(001). REDEFINES数値+88条件 */
private String wrkCsvStatus = "";
/** 88 WRK-STATUS-ACTIVE VALUE 1.REDEFINES WRK-STATUS-NUM の88条件) */
private boolean isStatusActive() {
return CST_STAT_1.equals(wrkCsvStatus);
}
/** 88 WRK-STATUS-CANCEL VALUE 9. */
private boolean isStatusCancel() {
return CST_STAT_9.equals(wrkCsvStatus);
}
/**
* 03 WRK-LEAVE-TYPE-TABLE.05 WRK-LT-ENTRY OCCURS 4 TIMES07 WRK-LT-CODE PIC X(002).
* OCCURS → 配列。INDEXED BY WRK-LT-IDX → ループ変数。
*/
private final String[] wrkLtCode = new String[4];
/**
* 03 WRK-DEMO-AREA PIC 9(008). REDEFINES 3视图(同一領域・複数型)。
* 主存储=数值视图;文字视图/分组视图以方法形式提供(REDEFINES 标准转换方式)。
*/
private int wsDemoArea;
/** MOVE x TO WRK-DEMO-TYPE(前4桁分组视图写入)。 */
private void setDemoType(final int v) {
wsDemoArea = (wsDemoArea % 10000) + v * 10000;
}
/** MOVE x TO WRK-DEMO-VALUE(后4桁分组视图写入)。 */
private void setDemoValue(final int v) {
wsDemoArea = (wsDemoArea / 10000) * 10000 + v;
}
/** WRK-DEMO-ALPHA REDEFINES WRK-DEMO-AREA PIC X(008). 文字视图。 */
private String demoAlphaView() {
return ConvUtil.padLeftZero(wsDemoArea, 8);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANDATAC.SUB01DAT用) */
private final ZandatacPar d01UbsPar = new ZandatacPar();
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
/** COPY ZANCHKAC.SUB04CHK用) */
private final ZanchkacPar c01ChkPar = new ZanchkacPar();
// ==================== 出力レコード(FILE SECTION → DTO ====================
/** 01 W01OUTREC.FD W01OUTFIL */
private final Kin01Rec w01 = new Kin01Rec();
/** 01 W02OUTREC.FD W02OUTFIL */
private final Kin05Rec w02 = new Kin05Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
// ※OPENが初期処理・CLOSEが終了処理と別段落のため、try-with-resources でなく
// フィールド保持+termPara()で明示 close を採用(OPEN/CLOSE 対応を優先)。
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
/** FD R01INNFIL の 01 R01INNREC. 03 R01LINE PIC X(80).(現在レコードバッファ) */
private String r01Line = "";
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE
* コマンドライン引数で上書き可(テスト時等に任意パスを指定)。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(String[] args) {
// 入力ファイルパス:引数指定があれば上書き、なければ R01INN_FILECOBOL ASSIGN 映射を維持)
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kin01InpMain(inputFile).run(); // PERFORM 0000MAJCOLSOR 相当
}
/** デフォルトコンストラクタ:入力パス=R01INN_FILESELECT ASSIGN 対応)。 */
public Kin01InpMain() {
this(R01INN_FILE);
}
/** 引数付コンストラクタ:入力ファイルパスを指定(テスト等で使用)。 */
public Kin01InpMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!"1".equals(wrkR01Eof)) { // PERFORM 2000MAJSOR UNTIL WRK-R01EOF = '1'
majPara();
}
termPara();
} finally {
// 确保所有路径(正常+异常)均关闭文件,防止资源泄漏
closeFiles();
}
// GOBACK → main 正常返回
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→種別テーブル設定→運用日付取得→OPEN→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※疑问#Q3:WHEN-COMPILED→启动时刻代替
String compiled = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
wrkU06 = 0;
wrkCommaCnt = 0;
wrkLtFound = "0";
wrkErrType = "";
wrkCsvApplId = "";
wrkCsvEmpId = "";
wrkCsvStartDate = "";
wrkCsvStartTime = "";
wrkCsvEndDate = "";
wrkCsvEndTime = "";
wrkCsvLeaveType = "";
wrkCsvStatus = "";
wsDemoArea = 0;
// *** 休暇種別テーブル設定 MOVE '01'..'04' TO WRK-LT-CODE(1..4).
wrkLtCode[0] = "01"; // 年休/年假
wrkLtCode[1] = "02"; // 事假
wrkLtCode[2] = "03"; // 因公特批假
wrkLtCode[3] = "04"; // 病欠
// *** 運用日付取得 CALL 'SUB01DAT' USING D01UBSPAR.
d01UbsPar.reset(); // INITIALIZE D01UBSPAR.
Sub01DatSub.execute(d01UbsPar);
if (d01UbsPar.getD01FkiCod() != 0) {
// IF D01FKICOD NOT = ZERO → メッセージ出力後 ABEND
msgOutPara(CST_MSGSUBEEK, "SUB01DAT",
ConvUtil.padLeftZero(d01UbsPar.getD01FkiCod(), 4)); // S9(4) COMP→4桁表示
abendPara();
}
// MOVE D01UBSUDATE TO WRK-U06.
wrkU06 = d01UbsPar.getD01UbsUdate();
// *** 入出力ファイル OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL.
openFiles();
// *** #R01#を読み込み(1回目)
readR01InnPara();
}
/**
* 段落 1100R01INNSOR##R01##読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR01InnPara() {
try {
String line = r01InnFil.readLine();
if (line == null) {
// AT END: MOVE '1' TO WRK-R01EOF.
wrkR01Eof = "1";
} else {
// NOT AT END: ADD 1 TO CUN-R01INN.(レコードバッファへ格納)
r01Line = line;
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* CSV分解→種別検索→エラー判定(IF/ELSE連鎖による振り分け)→次レコード読込。
*/
private void majPara() {
// *** CSV分解
csvPara();
// *** 休暇種別テーブル検索
leavserPara();
// *** エラー判定(フィールド数/休暇種別)
if (fieldCount() != 8) {
// ※疑问#Q1:設計書どおり「フィールド数≠8」で判定(TALLYING値はメッセージ用に保持)
wrkErrType = "F";
errorPara();
} else if (!"1".equals(wrkLtFound)) {
wrkErrType = "L";
errorPara();
} else {
// *** ステータス判定(IF/ELSE連鎖+88条件名)
if (isStatusActive()) {
validatePara();
} else if (isStatusCancel()) {
cancelPara();
} else {
wrkErrType = "S";
errorPara();
}
}
// *** 次のレコード読込
readR01InnPara();
}
/** 現在レコードのCSVフィールド数(疑问#Q1:設計意図どおり項目数で判定するための補助)。 */
private int fieldCount() {
return r01Line.split(",", -1).length;
}
/**
* 段落 2010CSVSORCSV分解処理。
* UNSTRING DELIMITED BY ',' INTO 8項目 TALLYING IN WRK-COMMA-CNT.
*/
private void csvPara() {
// MOVE ZERO TO WRK-COMMA-CNT.INITIALIZE 各CSV項目.
wrkCommaCnt = 0;
wrkCsvApplId = "";
wrkCsvEmpId = "";
wrkCsvStartDate = "";
wrkCsvStartTime = "";
wrkCsvEndDate = "";
wrkCsvEndTime = "";
wrkCsvLeaveType = "";
wrkCsvStatus = "";
// UNSTRING R01INNREC DELIMITED BY ',' INTO ... split(-1)で空項目保持)
String[] tokens = r01Line.split(",", -1);
// 先頭8項目を受信領域へ(PIC X(n) 左詰め・右空白埋め=UNSTRING転送規則)
wrkCsvApplId = ConvUtil.padRight(tok(tokens, 0), 9);
wrkCsvEmpId = ConvUtil.padRight(tok(tokens, 1), 8);
wrkCsvStartDate = ConvUtil.padRight(tok(tokens, 2), 8);
wrkCsvStartTime = ConvUtil.padRight(tok(tokens, 3), 4);
wrkCsvEndDate = ConvUtil.padRight(tok(tokens, 4), 8);
wrkCsvEndTime = ConvUtil.padRight(tok(tokens, 5), 4);
wrkCsvLeaveType = ConvUtil.padRight(tok(tokens, 6), 2);
wrkCsvStatus = ConvUtil.padRight(tok(tokens, 7), 1);
// TALLYING IN WRK-COMMA-CNT:分隔符个数(8項目なら7)。※判定には使用しない(#Q1)
wrkCommaCnt = tokens.length - 1;
}
/** UNSTRING受信領域への転送補助(項目不足時は空文字=未転送扱い)。 */
private static String tok(final String[] tokens, final int idx) {
return idx < tokens.length ? tokens[idx] : "";
}
/**
* 段落 2020LEAVSERSOR:休暇種別テーブル検索処理。
* SEARCH WRK-LT-ENTRY VARYING WRK-LT-IDX ... のマッピング。
*/
private void leavserPara() {
// MOVE '0' TO WRK-LT-FOUND.SET WRK-LT-IDX TO 1.
wrkLtFound = "0";
for (int idx = 0; idx < wrkLtCode.length; idx++) { // SEARCH VARYING
// WHEN WRK-LT-CODE(WRK-LT-IDX) = WRK-CSV-LEAVE-TYPE
if (wrkLtCode[idx].equals(wrkCsvLeaveType)) {
wrkLtFound = "1";
break; // SEARCH一致で脱出
}
}
// AT END: CONTINUE(見つからなければそのまま)
}
/**
* 段落 2030VALIDATESOR:有効申請処理。
* SUB04CHKによる5項目チェック→複合条件→W01出力。
* ※段落内の GO TO 2030VALIDATESOR-EXT は return にマッピング(GO TO 方针)。
*/
private void validatePara() {
// *** W01レコード初期化 INITIALIZE W01OUTREC.
w01.reset();
// --- SUB04CHKで社員番号チェック ---
c01ChkPar.reset(); // INITIALIZE C01CHKPAR.
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvEmpId, 80)); // MOVE ... TO C01CHKDAT.
c01ChkPar.setC01ChkTyp("EMPID"); // MOVE 'EMPID' TO C01CHKTYP.
Sub04ChkSub.execute(c01ChkPar); // CALL 'SUB04CHK' USING C01CHKPAR.
if (c01ChkPar.getC01ChkRrc() != 0) {
outputItemError("EMP-ID ERROR EMP=" + wrkCsvEmpId);
return; // GO TO 2030VALIDATESOR-EXT 相当
}
// --- SUB04CHKで開始日付チェック ---
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvStartDate, 80));
c01ChkPar.setC01ChkTyp("DATE");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() != 0) {
outputItemError("START-DATE ERROR EMP=" + wrkCsvEmpId
+ " DATE=" + wrkCsvStartDate);
return;
}
// --- SUB04CHKで開始時刻チェック ---
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvStartTime, 80));
c01ChkPar.setC01ChkTyp("TIME");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() != 0) {
outputItemError("START-TIME ERROR EMP=" + wrkCsvEmpId
+ " TIME=" + wrkCsvStartTime);
return;
}
// --- SUB04CHKで終了日付チェック ---
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvEndDate, 80));
c01ChkPar.setC01ChkTyp("DATE");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() != 0) {
outputItemError("END-DATE ERROR EMP=" + wrkCsvEmpId
+ " DATE=" + wrkCsvEndDate);
return;
}
// --- SUB04CHKで終了時刻チェック ---
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvEndTime, 80));
c01ChkPar.setC01ChkTyp("TIME");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() != 0) {
outputItemError("END-TIME ERROR EMP=" + wrkCsvEmpId
+ " TIME=" + wrkCsvEndTime);
return;
}
// *** 複合条件デモ(AND+OR+3段ネスト)
// ※疑问#Q5:原代码引用W01项目(MOVE前で全てZERO、恒不成立)→
// 設計書意図どおり WRK-CSV-* 项目を使用するよう修正。
int sDate = Integer.parseInt(wrkCsvStartDate);
int eDate = Integer.parseInt(wrkCsvEndDate);
int sTime = Integer.parseInt(wrkCsvStartTime);
int eTime = Integer.parseInt(wrkCsvEndTime);
if (sDate != 0 && eDate != 0 && sTime != 0) {
if (sDate > eDate || (sDate == eDate && sTime >= eTime)) {
setDemoType(10); // MOVE 10 TO WRK-DEMO-TYPE.
} else {
setDemoType(20); // MOVE 20 TO WRK-DEMO-TYPE.
}
}
// *** W01出力(新規/変更)
// ※疑问#Q2:設計書「有効時APPL-ID=0」→ INITIALIZE直後の0のまま移動しない
// (原代码は MOVE WRK-CSV-APPL-ID TO W01APPL-ID)。
w01.setEmpId(wrkCsvEmpId); // MOVE ... TO W01EMP-ID.
w01.setLeaveType(wrkCsvLeaveType); // MOVE ... TO W01LEAVE-TYPE.
w01.setStartDate(Integer.parseInt(wrkCsvStartDate)); // MOVE ... TO W01START-DATE.
w01.setStartTime(Integer.parseInt(wrkCsvStartTime)); // MOVE ... TO W01START-TIME.
w01.setEndDate(Integer.parseInt(wrkCsvEndDate)); // MOVE ... TO W01END-DATE.
w01.setEndTime(Integer.parseInt(wrkCsvEndTime)); // MOVE ... TO W01END-TIME.
w01.setStatus(wrkCsvStatus); // MOVE ... TO W01STATUS.
writeW01(); // WRITE W01OUTREC.
}
/**
* 項目チェックNG時の ERROR-LOG 出力(2030内5箇所の共通パターン)。
* MOVE '01' TO W02ERR-CATEGORY.STRING ... INTO W02ERR-DETAIL.WRITE W02OUTREC.
*/
private void outputItemError(final String detail) {
w02.reset(); // INITIALIZE W02OUTREC.
w02.setErrCategory(1); // MOVE '01' TO W02ERR-CATEGORY.
w02.setErrDetail(detail); // STRING ... INTO W02ERR-DETAIL.
writeW02(); // WRITE W02OUTREC.ADD 1 TO CUN-W02OUT.
}
/**
* 段落 2040CANCELSOR:取消申請処理。
* 取消レコードをW01出力(検証は行わない、APPL-ID=CSV値)。
*/
private void cancelPara() {
// *** W01レコード初期化 INITIALIZE W01OUTREC.
w01.reset();
// *** W01出力(取消: APPL-ID=CSV値, STATUS='9')
w01.setApplId(Integer.parseInt(wrkCsvApplId)); // MOVE ... TO W01APPL-ID.
w01.setEmpId(wrkCsvEmpId);
w01.setLeaveType(wrkCsvLeaveType);
w01.setStartDate(Integer.parseInt(wrkCsvStartDate));
w01.setStartTime(Integer.parseInt(wrkCsvStartTime));
w01.setEndDate(Integer.parseInt(wrkCsvEndDate));
w01.setEndTime(Integer.parseInt(wrkCsvEndTime));
w01.setStatus(wrkCsvStatus);
writeW01();
}
/**
* 段落 2050ERRORSOR:エラー処理。
* EVALUATE WRK-ERR-TYPEF/L/S/OTHER)によりメッセージ編集しW02出力。
*/
private void errorPara() {
// *** W02レコード初期化 INITIALIZE W02OUTREC.MOVE '01' TO W02ERR-CATEGORY.
w02.reset();
w02.setErrCategory(1);
// *** エラー種別判定 EVALUATE WRK-ERR-TYPE ... END-EVALUATE.
switch (wrkErrType) {
case "F":
setDemoType(1); // MOVE 1 TO WRK-DEMO-TYPE.
setDemoValue(wrkCommaCnt); // MOVE WRK-COMMA-CNT TO WRK-DEMO-VALUE.
w02.setErrDetail("FIELD COUNT ERROR CNT=" + demoAlphaView());
break;
case "L":
setDemoType(2);
setDemoValue(0);
w02.setErrDetail("INVALID LEAVE TYPE=" + wrkCsvLeaveType
+ " EMP=" + wrkCsvEmpId + " ERR=" + demoAlphaView());
break;
case "S":
setDemoType(3);
setDemoValue(0);
w02.setErrDetail("INVALID STATUS=" + wrkCsvStatus
+ " EMP=" + wrkCsvEmpId + " ERR=" + demoAlphaView());
break;
default:
setDemoType(9);
setDemoValue(0);
w02.setErrDetail("UNKNOWN ERROR EMP=" + wrkCsvEmpId
+ " ERR=" + demoAlphaView());
break;
}
writeW02(); // WRITE W02OUTREC.ADD 1 TO CUN-W02OUT.
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
// *** 入出力ファイル CLOSE R01INNFIL W01OUTFIL W02OUTFIL.
closeFiles();
// *** 入出力ファイル件数出力(数値→X(30)転送は9桁ゼロ埋め表示形)
msgOutPara(CST_MSGIINKES, "KIN01R01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGOUTKES, "KIN01W01", ConvUtil.padLeftZero(cunW01Out, 9));
msgOutPara(CST_MSGOUTKES, "KIN01W02", ConvUtil.padLeftZero(cunW02Out, 9));
// *** 終了メッセージ出力
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) {
m00MhoPar.reset(); // INITIALIZE M00MHOPAR.
m00MhoPar.setM00MsgCod(msgCod); // MOVE ... TO M00MSGCOD.
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1); // MOVE ... TO M00UMKDATS22-01.
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2); // MOVE ... TO M00UMKDATS22-02.
}
// MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1).(参照修改→先頭1桁上書き、残り29桁はSPACE)
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX); // MOVE CNS-PRGIDX TO ...-05.
Sub02MsgSub.execute(m00MhoPar); // CALL 'SUB02MSG' USING M00MHOPAR.
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999); // MOVE CNS-ABD999 TO E01ABDCOD.
Sub03EndSub.execute(e01AbdPar); // CALL 'SUB03END'.
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), "UTF-8"));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), "UTF-8"));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), "UTF-8"));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** WRITE W01OUTREC.ADD 1 TO CUN-W01OUT. */
private void writeW01() {
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/** WRITE W02OUTREC.ADD 1 TO CUN-W02OUT. */
private void writeW02() {
w02OutFil.println(w02.toRecordLine());
cunW02Out++;
}
/** CLOSE R01INNFIL W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
r01InnFil = null;
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+532
View File
@@ -0,0 +1,532 @@
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.LocalDateTime;
import java.time.format.DateTimeFormatter;
import dto.Kin01Rec;
import dto.Kin05Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KIN02UPD 休暇申請DB更新処理
* ============================================================
* PROGRAM-ID. KIN02UPD → 类 Kin02UpdMainPGMパターン:DB更新)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R01INNFIL(KIN01W01 WORK-LEAVE F80B) → R01INN_FILECOPY KIN01REC → dto.Kin01Rec
* W01OUTFIL(KIN02W01 ERROR-LOG V200B) → W01OUT_FILECOPY KIN05REC → dto.Kin05Rec
* LEAVE_RECORDSSQLite: data/kin.db
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01()
* 2000MAJSOR→majPara() 2100PROCSOR→procPara() 2110INSERTSOR→insertPara()
* 2120UPDATESOR→updatePara() 2130DELETESOR→deletePara() 3000STPSOR→termPara()
* 4000MSGOUTSOR→msgOutPara() 9100DBERRSOR→dbErrPara() 9999ABDSOR→abendPara()
*
* EXEC SQL → JDBC 映射(详见 DbSample 标准例):
* CONNECT TO 'data/kin.db' → DriverManager.getConnection("jdbc:sqlite:data/kin.db")
* SELECT MAX+1 → PreparedStatement.executeQuery()
* INSERT/DELETE → PreparedStatement.executeUpdate()
* COMMIT / ROLLBACK → conn.commit() / conn.rollback()
*/
public class Kin02UpdMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KIN02UPD'. */
private static final String CST_PRGIDX = "KIN02UPD";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. サブエラー */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-STAT-1 PIC X(001) VALUE '1'. */
private static final String CST_STAT_1 = "1";
/** 03 CNS-STAT-9 PIC X(001) VALUE '9'. */
private static final String CST_STAT_9 = "9";
// ==================== ファイル・DB定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KIN01W01.WORK-LEAVE入力 F80B */
private static final String R01INN_FILE = "data/KIN01W01.txt";
/** SELECT W01OUTFIL ASSIGN TO KIN02W01.ERROR-LOG出力 V200B */
private static final String W01OUT_FILE = "data/KIN02W01.txt";
/** EXEC SQL CONNECT TO 'data/kin.db'SQLite JDBC URL */
private static final String DB_URL = "jdbc:sqlite:data/kin.db";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-DBXINS PIC S9(009) COMP-3 VALUE ZERO. */
private long cunDbxIns;
/** 03 CUN-DBXDEL PIC S9(009) COMP-3 VALUE ZERO. */
private long cunDbxDel;
/** 03 CUN-DBXUPD PIC S9(009) COMP-3 VALUE ZERO. */
private long cunDbxUpd;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-R01EOF PIC X(001). 読込フラグ */
private String wrkR01Eof = "0";
/** 03 WS-APPL-ID PIC 9(009). */
private int wsApplId;
/** 03 WS-NEXT-APPL-ID PIC 9(009). 新規ID生成用 */
private int wsNextApplId;
/** 03 WS-EMP-ID PIC X(008). */
private String wsEmpId = "";
/** 03 WS-LEAVE-TYPE PIC X(002). */
private String wsLeaveType = "";
/** 03 WS-START-DATE PIC X(008). */
private String wsStartDate = "";
/** 03 WS-START-TIME PIC X(004). */
private String wsStartTime = "";
/** 03 WS-END-DATE PIC X(008). */
private String wsEndDate = "";
/** 03 WS-END-TIME PIC X(004). */
private String wsEndTime = "";
/** 03 WS-STATUS PIC X(001). */
private String wsStatus = "";
/** 03 WRK-SQLCODE-DISP 用:直近SQLCODE */
private int sqlcode;
/** 直近SQLエラーメッセージ(ログ用・非必須) */
private String sqlErrMsg = "";
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** 01 R01INNREC.FD R01INNFILCOPY KIN01REC → R01 */
private final Kin01Rec r01 = new Kin01Rec();
/** 01 W01OUTREC.FD W01OUTFILCOPY KIN05REC → W01 */
private final Kin05Rec w01 = new Kin05Rec();
// ==================== ファイル・DBハンドル ====================
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private Connection conn;
/** FD R01INNFIL の 01 R01INNREC(現在レコードバッファ)。 */
private String r01Line = "";
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE
* コマンドライン引数で上書き可(テスト時等に任意パスを指定)。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(String[] args) {
// 入力ファイルパス:引数指定があれば上書き、なければ R01INN_FILECOBOL ASSIGN 映射を維持)
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kin02UpdMain(inputFile).run(); // PERFORM 0000MAJCOLSOR 相当
}
/** デフォルトコンストラクタ:入力パス=R01INN_FILESELECT ASSIGN 対応)。 */
public Kin02UpdMain() {
this(R01INN_FILE);
}
/** 引数付コンストラクタ:入力ファイルパスを指定(テスト等で使用)。 */
public Kin02UpdMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!"1".equals(wrkR01Eof)) { // PERFORM 2000MAJSOR UNTIL WRK-R01EOF = '1'
majPara();
}
termPara();
} finally {
// 确保所有路径(正常+异常)均关闭文件,防止资源泄漏
closeFiles();
}
// GOBACK → main 正常返回
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→DB接続→OPEN(R01/W01)→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※KIN01INP#Q3 同様:WHEN-COMPILED→起動時刻代替
String compiled = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
resetWork();
// *** DB接続 EXEC SQL CONNECT TO 'data/kin.db'
try {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection(DB_URL);
conn.setAutoCommit(false); // 批处理程序:显式事务控制
} catch (ClassNotFoundException | SQLException e) {
sqlcode = (e instanceof SQLException) ? ((SQLException) e).getErrorCode() : -1;
sqlErrMsg = e.getMessage();
// 接続失敗 → DBエラー処理(接続未確立のため ROLLBACK/W01書込は保護付き)
dbErrPara();
}
// *** R01ファイルOPEN(入力)+ W01ファイルOPEN(出力)
openFiles();
// *** 最大APPLICATION_ID取得(新規ID生成用)
wsNextApplId = selectMaxApplId();
// *** R01を初回読込(1100R01INNSOR)
readR01();
}
/** INITIALIZE WRKARA 相当(数値→0、文字→SPACE)。 */
private void resetWork() {
wsApplId = 0;
wsNextApplId = 0;
wsEmpId = "";
wsLeaveType = "";
wsStartDate = "";
wsStartTime = "";
wsEndDate = "";
wsEndTime = "";
wsStatus = "";
sqlcode = 0;
sqlErrMsg = "";
cunR01Inn = 0;
cunDbxIns = 0;
cunDbxDel = 0;
cunDbxUpd = 0;
cunW01Out = 0;
}
/**
* 段落 1100R01INNSOR##R01##読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR01() {
try {
String line = r01InnFil.readLine();
if (line == null) {
// AT END: MOVE '1' TO WRK-R01EOF.
wrkR01Eof = "1";
} else {
// NOT AT END: R01解析→バッファ格納、ADD 1 TO CUN-R01INN.
r01.fromRecordLine(line);
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
} catch (NumberFormatException e) {
// 数値項目の非数値 → COBOL S0C7 相当。ABEND 扱い。
sqlcode = -1;
sqlErrMsg = e.getMessage();
dbErrPara();
}
}
/**
* 段落 2000MAJSOR:主処理。
* レコード処理→次レコード読込。
*/
private void majPara() {
// *** レコード処理
procPara();
// *** EVALUATE TRUE による分岐
if (CST_STAT_1.equals(wsStatus) && wsApplId == 0) {
insertPara(); // WHEN STATUS='1' AND APPL-ID=0
} else if (CST_STAT_1.equals(wsStatus) && wsApplId > 0) {
updatePara(); // WHEN STATUS='1' AND APPL-ID>0
} else if (CST_STAT_9.equals(wsStatus)) {
deletePara(); // WHEN STATUS='9'
} else {
// WHEN OTHER: CONTINUE(何も処理しない)
}
// *** 次レコード読込
readR01();
}
/**
* 段落 2100PROCSORR01各項目→ホスト変数(WS-*) MOVE。
* 数値項目は int→CHAR(8/4) 変換(padLeftZero で先頭ゼロ維持)。
*/
private void procPara() {
wsApplId = r01.getApplId();
wsEmpId = r01.getEmpId();
wsLeaveType = r01.getLeaveType();
wsStartDate = ConvUtil.padLeftZero(r01.getStartDate(), 8); // CHAR(8) YYYYMMDD
wsStartTime = ConvUtil.padLeftZero(r01.getStartTime(), 4); // CHAR(4) HHMM
wsEndDate = ConvUtil.padLeftZero(r01.getEndDate(), 8);
wsEndTime = ConvUtil.padLeftZero(r01.getEndTime(), 4);
wsStatus = r01.getStatus();
}
/**
* 段落 2110INSERTSOR:新規登録(INSERT)。
* APPLICATION_ID = 生成ID(WS-NEXT-APPL-ID) を明示挿入(COBOL/使用資源一覧準拠)。
*/
private void insertPara() {
String sql = "INSERT INTO LEAVE_RECORDS "
+ "(APPLICATION_ID, EMP_ID, LEAVE_TYPE, START_DATE, START_TIME, "
+ " END_DATE, END_TIME, STATUS) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
execUpdate(sql, wsNextApplId, wsEmpId, wsLeaveType,
wsStartDate, wsStartTime, wsEndDate, wsEndTime, wsStatus);
wsNextApplId++; // ADD 1 TO WS-NEXT-APPL-ID
cunDbxIns++; // ADD 1 TO CUN-DBXINS
}
/**
* 段落 2120UPDATESOR:変更(DELETE+INSERT)。
* APPLICATION_ID = レコード自身の WS-APPL-ID で DELETE 後、同一 ID で INSERT。
*/
private void updatePara() {
execUpdate("DELETE FROM LEAVE_RECORDS WHERE APPLICATION_ID = ?", wsApplId);
String sql = "INSERT INTO LEAVE_RECORDS "
+ "(APPLICATION_ID, EMP_ID, LEAVE_TYPE, START_DATE, START_TIME, "
+ " END_DATE, END_TIME, STATUS) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
execUpdate(sql, wsApplId, wsEmpId, wsLeaveType,
wsStartDate, wsStartTime, wsEndDate, wsEndTime, wsStatus);
cunDbxUpd++; // ADD 1 TO CUN-DBXUPD
}
/**
* 段落 2130DELETESOR:取消(DELETE)。
*/
private void deletePara() {
execUpdate("DELETE FROM LEAVE_RECORDS WHERE APPLICATION_ID = ?", wsApplId);
cunDbxDel++; // ADD 1 TO CUN-DBXDEL
}
/**
* EXEC SQL INSERT/DELETE の実行+SQLCODE監視。
* SQLCODE≠0 → DBエラー処理(9100DBERRSOR)(内部で ROLLBACK+ABEND するため復帰しない)。
*/
private void execUpdate(final String sql, final Object... params) {
try (PreparedStatement ps = conn.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]); // APPLICATION_ID=Integer / 他=String(CHAR)
}
ps.executeUpdate();
} catch (SQLException e) {
sqlcode = e.getErrorCode();
sqlErrMsg = e.getMessage();
dbErrPara(); // 復帰しない(ABENDで終了)
}
}
/**
* 初期処理の SELECT MAX(APPLICATION_ID)+1。
* SQLCODE≠0 の場合、COBOL どおり 0 を返す(ABEND しない)。
*/
private int selectMaxApplId() {
try (PreparedStatement ps = conn.prepareStatement(
"SELECT COALESCE(MAX(APPLICATION_ID), 0) + 1 FROM LEAVE_RECORDS")) {
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getInt(1);
}
}
} catch (SQLException e) {
sqlcode = e.getErrorCode();
return 0; // IF SQLCODE NOT = 0 MOVE ZERO TO WS-NEXT-APPL-ID
}
return 0;
}
/**
* 段落 3000STPSOR:終了処理。
* COMMIT→CLOSE→件数メッセージ×5→終了メッセージ。
*/
private void termPara() {
// *** COMMIT EXEC SQL COMMIT WORK
try {
if (conn != null) {
conn.commit();
}
} catch (SQLException e) {
sqlcode = e.getErrorCode();
sqlErrMsg = e.getMessage();
dbErrPara();
return;
}
// *** 入出力ファイル CLOSE R01INNFIL W01OUTFIL.
closeFiles();
// *** 件数メッセージ出力(COBOL の MSGCOD 割当てに準拠)
msgOutPara(CST_MSGIINKES, "KIN01W01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGIINKES, "INS", ConvUtil.padLeftZero(cunDbxIns, 9));
msgOutPara(CST_MSGIINKES, "UPD", ConvUtil.padLeftZero(cunDbxUpd, 9));
msgOutPara(CST_MSGOUTKES, "DEL", ConvUtil.padLeftZero(cunDbxDel, 9));
msgOutPara(CST_MSGOUTKES, "KIN02W01", ConvUtil.padLeftZero(cunW01Out, 9));
// *** 終了メッセージ出力
msgOutPara(CST_MSGFIN, null, null);
}
/**
* 段落 9100DBERRSORDBエラー処理。
* ROLLBACK→ERROR-LOG出力(W01OUTFIL)→エラーメッセージ→ABEND(9999ABDSOR)。
*/
private void dbErrPara() {
// *** ROLLBACK EXEC SQL ROLLBACK WORK
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ignore) {
// ROLLBACK失敗は無視(原程序も未管理)
}
}
// *** ERROR-LOG出力(W01OUTFIL が OPEN されている場合のみ)
if (w01OutFil != null) {
w01.reset(); // INITIALIZE W01OUTREC.
w01.setErrCategory(1); // MOVE '01' TO W01ERR-CATEGORY.
w01.setErrDetail("KIN02UPD SQLCODE=" + sqlcodeDisp()
+ " APPL-ID=" + wsApplId); // STRING ... INTO W01ERR-DETAIL.
writeW01(); // WRITE W01OUTREC.ADD 1 TO CUN-W01OUT.
}
// *** エラーメッセージ出力(4000MSGOUTSOR 経由)
msgOutPara(CST_MSGSUBEEK, "KIN02UPD SQL ERROR",
sqlcodeDisp(), Integer.toString(wsApplId));
// *** ABEND処理SUB呼出(9999ABDSOR)
abendPara();
}
/** WRK-SQLCODE-DISP PIC +9(009) 相当:符号付9桁ゼロ埋め。 */
private String sqlcodeDisp() {
String sign = sqlcode < 0 ? "-" : "+";
return sign + ConvUtil.padLeftZero(Math.abs((long) sqlcode), 9);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999); // MOVE CNS-ABD999 TO E01ABDCOD.
Sub03EndSub.execute(e01AbdPar); // CALL 'SUB03END'System.exit(999)
}
/**
* 段落 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(); // INITIALIZE M00MHOPAR.
m00MhoPar.setM00MsgCod(msgCod); // MOVE ... TO M00MSGCOD.
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1); // MOVE ... TO M00UMKDATS22-01.
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2); // MOVE ... TO M00UMKDATS22-02.
}
if (p3 != null) {
m00MhoPar.setM00Umkdats2203(p3); // MOVE ... TO M00UMKDATS22-03.
}
// MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1).(参照修改→先頭1桁上書き、残り29桁は維持)
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); // MOVE CNS-PRGIDX TO ...-05.
Sub02MsgSub.execute(m00MhoPar); // CALL 'SUB02MSG' USING M00MHOPAR.
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL.autoFlush で ABEND 前でも書込が失われない) */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), "UTF-8"));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), "UTF-8"), true);
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** WRITE W01OUTREC.ADD 1 TO CUN-W01OUT. */
private void writeW01() {
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/** CLOSE R01INNFIL W01OUTFIL.DB接続は finally で別途 close */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
r01InnFil = null;
w01OutFil = null;
closeQuiet(conn);
conn = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
} else if (handle instanceof Connection) {
((Connection) handle).close();
}
} catch (Exception e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+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異常は無視
}
}
}
+521
View File
@@ -0,0 +1,521 @@
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import dto.Kin04Rec;
import dto.Kin05Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanchkacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KIN04CHK 打刻項目チェック処理
* ============================================================
* PROGRAM-ID. KIN04CHK → 类 Kin04ChkMainPGMパターン:項目チェック(重複なし))
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R01INNFIL(KIN04R01 CSV入力 F80B) → R01INN_FILE
* W01OUTFIL(KIN04W01 EDITED-PUNCH F80B) → W01OUT_FILECOPY KIN04REC → dto.Kin04Rec
* W02OUTFIL(KIN04W02 ERROR-LOG V200B) → W02OUT_FILECOPY KIN05REC → dto.Kin05Rec
* 使用SUBCALL → 类#execute):
* SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub / SUB04CHK→Sub04ChkSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01Para()
* 2000MAJSOR→majPara() 2010CSVSOR→csvPara() 2021EMPIDSOR→empIdPara()
* 2020VALIDATESOR→validatePara() 2050NORMSOR→normPara() 2090ERRORSOR→errorPara()
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 与原代码的差异(详见 doc\conv\KIN04CHK_对照表.md 疑问·回答记录):
* #Q1 WRK-COMMA-CNT は UNSTRING TALLYING(受信フィールド数)=フィールド数と解釈。
* 設計書「フィールド数=6」に従い tokens.length==6 で判定、CNT=は実フィールド数を表示。
* #Q2 FUNCTION WHEN-COMPILED → 程序启动时刻代替。
*/
public class Kin04ChkMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KIN04CHK'. */
private static final String CST_PRGIDX = "KIN04CHK";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. サブエラー */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-ERR-PUNCH PIC 9(002) VALUE 02. 打刻エラー区分 */
private static final int CST_ERR_PUNCH = 2;
/** 03 CNS-FIELD-COUNT-6 PIC 9(002) VALUE 6. 期待フィールド数 */
private static final int CST_FIELD_COUNT_6 = 6;
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KIN04R01.CSV入力) */
private static final String R01INN_FILE = "data/KIN04R01.csv";
/** SELECT W01OUTFIL ASSIGN TO KIN04W01.EDITED-PUNCH出力) */
private static final String W01OUT_FILE = "data/KIN04W01.dat";
/** SELECT W02OUTFIL ASSIGN TO KIN04W02.ERROR-LOG出力) */
private static final String W02OUT_FILE = "data/KIN04W02.dat";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-R01EOF PIC X(001). 読込フラグ */
private String wrkR01Eof = "0";
/** 03 WRK-COMMA-CNT PIC 9(002) COMP. UNSTRING TALLYING(フィールド数) */
private int wrkCommaCnt;
/** 03 WRK-CSV-EMP-ID PIC X(008). */
private String wrkCsvEmpId = "";
/** 03 WRK-CSV-DATE PIC X(008). */
private String wrkCsvDate = "";
/** 03 WRK-CSV-TIME-IN PIC X(004). */
private String wrkCsvTimeIn = "";
/** 03 WRK-CSV-TIME-OUT PIC X(004). */
private String wrkCsvTimeOut = "";
/** 03 WRK-CSV-DEPT-ID PIC X(004). */
private String wrkCsvDeptId = "";
/** 03 WRK-CSV-TERMINAL PIC X(006). */
private String wrkCsvTerminal = "";
/** 03 WRK-TIME-IN-NUM PIC 9(004). */
private int wrkTimeInNum;
/** 03 WRK-TIME-OUT-NUM PIC 9(004). */
private int wrkTimeOutNum;
/** 03 WRK-COMMA-DISP PIC 9(002). エラー表示用 */
private int wrkCommaDisp;
/** 03 WRK-EMPID-OK PIC X(001). 社員番号チェック結果 */
private String wrkEmpIdOk = "0";
/** 88 WRK-EMPID-IS-OK VALUE '1'. */
private boolean isEmpIdOk() {
return "1".equals(wrkEmpIdOk);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANDATAC.SUB01DAT用) */
private final ZandatacPar d01UbsPar = new ZandatacPar();
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
/** COPY ZANCHKAC.SUB04CHK用) */
private final ZanchkacPar c01ChkPar = new ZanchkacPar();
// ==================== 出力レコード(FILE SECTION → DTO ====================
/** 01 W01OUTREC.FD W01OUTFIL */
private final Kin04Rec w01 = new Kin04Rec();
/** 01 W02OUTREC.FD W02OUTFIL */
private final Kin05Rec w02 = new Kin05Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
/** FD R01INNFIL の 01 R01INNREC. 03 R01LINE PIC X(80).(現在レコードバッファ) */
private String r01Line = "";
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE
* コマンドライン引数で上書き可(テスト時等に任意パスを指定)。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(String[] args) {
// 入力ファイルパス:引数指定があれば上書き、なければ R01INN_FILECOBOL ASSIGN 映射を維持)
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kin04ChkMain(inputFile).run(); // PERFORM 0000MAJCOLSOR 相当
}
/** デフォルトコンストラクタ:入力パス=R01INN_FILESELECT ASSIGN 対応)。 */
public Kin04ChkMain() {
this(R01INN_FILE);
}
/** 引数付コンストラクタ:入力ファイルパスを指定(テスト等で使用)。 */
public Kin04ChkMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!"1".equals(wrkR01Eof)) { // PERFORM 2000MAJSOR UNTIL WRK-R01EOF = '1'
majPara();
}
termPara();
} finally {
// 确保所有路径(正常+异常)均关闭文件,防止资源泄漏
closeFiles();
}
// GOBACK → main 正常返回
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→運用日付取得→OPEN→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※疑问#Q2:WHEN-COMPILED→启动时刻代替
String compiled = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
initWrk();
// *** 運用日付取得 CALL 'SUB01DAT' USING D01UBSPAR.
d01UbsPar.reset(); // INITIALIZE D01UBSPAR.
Sub01DatSub.execute(d01UbsPar);
if (d01UbsPar.getD01FkiCod() != 0) {
// IF D01FKICOD NOT = ZERO → メッセージ出力後 ABEND
msgOutPara(CST_MSGSUBEEK, "SUB01DAT",
ConvUtil.padLeftZero(d01UbsPar.getD01FkiCod(), 4)); // S9(4) COMP→4桁表示
abendPara();
}
// *** 入出力ファイル OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL.
openFiles();
// *** #R01#を読み込み(1回目)
readR01Para();
}
/** INITIALIZE WRKARA 相当。 */
private void initWrk() {
wrkR01Eof = "0";
wrkCommaCnt = 0;
wrkCsvEmpId = "";
wrkCsvDate = "";
wrkCsvTimeIn = "";
wrkCsvTimeOut = "";
wrkCsvDeptId = "";
wrkCsvTerminal = "";
wrkTimeInNum = 0;
wrkTimeOutNum = 0;
wrkCommaDisp = 0;
wrkEmpIdOk = "0";
}
/**
* 段落 1100R01INNSOR##R01##読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR01Para() {
try {
String line = r01InnFil.readLine();
if (line == null) {
// AT END: MOVE '1' TO WRK-R01EOF.
wrkR01Eof = "1";
} else {
// NOT AT END: ADD 1 TO CUN-R01INN.(レコードバッファへ格納)
r01Line = line;
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* CSV分解→社員番号チェック→IF多重ネスト(句)4段階チェック→次レコード読込。
*/
private void majPara() {
// *** CSV分解
csvPara();
// *** 社員番号チェック
empIdPara();
// *** 項目チェック(IF多重ネスト 句)
validatePara();
// *** 次のレコード読込
readR01Para();
}
/**
* 段落 2010CSVSORCSV分解処理。
* UNSTRING DELIMITED BY ',' INTO 6項目 TALLYING IN WRK-COMMA-CNT.
*/
private void csvPara() {
// MOVE ZERO TO WRK-COMMA-CNT.INITIALIZE 各CSV項目.
wrkCommaCnt = 0;
wrkCsvEmpId = "";
wrkCsvDate = "";
wrkCsvTimeIn = "";
wrkCsvTimeOut = "";
wrkCsvDeptId = "";
wrkCsvTerminal = "";
// UNSTRING R01INNREC DELIMITED BY ',' INTO ... split(-1)で空項目保持)
String[] tokens = r01Line.split(",", -1);
// 先頭6項目を受信領域へ(PIC X(n) 左詰め・右空白埋め=UNSTRING転送規則)
wrkCsvEmpId = ConvUtil.padRight(tok(tokens, 0), 8);
wrkCsvDate = ConvUtil.padRight(tok(tokens, 1), 8);
wrkCsvTimeIn = ConvUtil.padRight(tok(tokens, 2), 4);
wrkCsvTimeOut = ConvUtil.padRight(tok(tokens, 3), 4);
wrkCsvDeptId = ConvUtil.padRight(tok(tokens, 4), 4);
wrkCsvTerminal = ConvUtil.padRight(tok(tokens, 5), 6);
// TALLYING IN WRK-COMMA-CNT:受信フィールド数=tokens.length(疑问#Q1)。
wrkCommaCnt = tokens.length;
}
/** UNSTRING受信領域への転送補助(項目不足時は空文字=未転送扱い)。 */
private static String tok(final String[] tokens, final int idx) {
return idx < tokens.length ? tokens[idx] : "";
}
/**
* 段落 2021EMPIDSOR:社員番号チェック処理。
* SUB04CHK('EMPID')により社員番号の妥当性チェックを実施する。
*/
private void empIdPara() {
// MOVE '0' TO WRK-EMPID-OK.
wrkEmpIdOk = "0";
// IF WRK-COMMA-CNT = CNS-FIELD-COUNT-6(フィールド数=6)のときのみ実施
if (wrkCommaCnt == CST_FIELD_COUNT_6) {
// INITIALIZE C01CHKPAR.MOVE ... TO C01CHKDAT.MOVE 'EMPID' TO C01CHKTYP.
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvEmpId, 80));
c01ChkPar.setC01ChkTyp("EMPID");
Sub04ChkSub.execute(c01ChkPar); // CALL 'SUB04CHK' USING C01CHKPAR.
if (c01ChkPar.getC01ChkRrc() == 0) {
wrkEmpIdOk = "1"; // MOVE '1' TO WRK-EMPID-OK.
}
}
}
/**
* 段落 2020VALIDATESOR:項目チェック(IF多重ネスト 句)。
* 第1段階: フィールド数 + 社員番号 → 第2段階: 日付 → 第3段階: 出勤時刻/退勤時刻
* → 第4段階: 出勤<退勤 の4段階ネストチェックを句で記述。
*/
private void validatePara() {
// *** 第1段階: フィールド数 + 社員番号必須
if (wrkCommaCnt == CST_FIELD_COUNT_6 && isEmpIdOk()) {
// *** 第2段階: 日付チェック(SUB04CHK)
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvDate, 80));
c01ChkPar.setC01ChkTyp("DATE");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() == 0) {
// *** 第3段階: 出勤時刻チェック(SUB04CHK)
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvTimeIn, 80));
c01ChkPar.setC01ChkTyp("TIME");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() == 0) {
// *** 第3段階: 退勤時刻チェック(逆順)→ここで退勤時刻をチェック
c01ChkPar.reset();
c01ChkPar.setC01ChkDat(ConvUtil.padRight(wrkCsvTimeOut, 80));
c01ChkPar.setC01ChkTyp("TIME");
Sub04ChkSub.execute(c01ChkPar);
if (c01ChkPar.getC01ChkRrc() == 0) {
// *** 第4段階: 出勤<退勤チェック
// MOVE WRK-CSV-TIME-IN TO WRK-TIME-IN-NUM.MOVE WRK-CSV-TIME-OUT TO WRK-TIME-OUT-NUM.
wrkTimeInNum = Integer.parseInt(wrkCsvTimeIn);
wrkTimeOutNum = Integer.parseInt(wrkCsvTimeOut);
if (wrkTimeInNum < wrkTimeOutNum) {
normPara(); // PERFORM 2050NORMSOR.
} else {
errorPara(); // PERFORM 2090ERRORSOR.
}
} else {
errorPara();
}
} else {
errorPara();
}
} else {
errorPara();
}
} else {
errorPara();
}
}
/**
* 段落 2050NORMSOR:正常出力処理。
* WORKING-STORAGEに編集し、WRITE FROMでW01(EDITED-PUNCH)に出力。
*/
private void normPara() {
// *** WORKING-STORAGEに編集(O1* 項目へ MOVE
// ここでは直接 W01OUTREC へ編集(WRITE FROM WS-W01REC と等価)。
w01.reset(); // INITIALIZE W01OUTREC.
w01.setEmpId(wrkCsvEmpId); // MOVE ... TO O1EMP-ID.
w01.setWorkDate(wrkCsvDate); // MOVE ... TO O1WORK-DATE.
w01.setStrTime(wrkCsvTimeIn); // MOVE ... TO O1STR-TIME.
w01.setEndTime(wrkCsvTimeOut); // MOVE ... TO O1END-TIME.
w01.setDeptId(wrkCsvDeptId); // MOVE ... TO O1DEPT-ID.
w01.setTerminal(wrkCsvTerminal); // MOVE ... TO O1TERMINAL.
writeW01(); // WRITE W01OUTREC FROM WS-W01REC.
}
/**
* 段落 2090ERRORSOR:エラー処理。
* エラーレコードをW02(ERROR-LOG)に出力(ERR-CATEGORY='02')。
*/
private void errorPara() {
// *** W02レコード初期化 INITIALIZE W02OUTREC.
w02.reset();
w02.setErrCategory(CST_ERR_PUNCH); // MOVE CNS-ERR-PUNCH TO W02ERR-CATEGORY.
// *** エラー種別判定
if (wrkCommaCnt != CST_FIELD_COUNT_6) {
// フィールド数不一致
wrkCommaDisp = wrkCommaCnt; // MOVE WRK-COMMA-CNT TO WRK-COMMA-DISP.
w02.setErrDetail("FIELD COUNT ERR CNT="
+ ConvUtil.padLeftZero(wrkCommaDisp, 2));
} else if (wrkCsvEmpId.trim().isEmpty()) {
// 社員番号が空
w02.setErrDetail("EMP-ID EMPTY");
} else {
// その他バリデーションエラー
w02.setErrDetail("VALIDATION ERR EMP=" + wrkCsvEmpId
+ " DATE=" + wrkCsvDate
+ " TI=" + wrkCsvTimeIn
+ " TO=" + wrkCsvTimeOut);
}
writeW02(); // WRITE W02OUTREC.ADD 1 TO CUN-W02OUT.
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
// *** 入出力ファイル CLOSE R01INNFIL W01OUTFIL W02OUTFIL.
closeFiles();
// *** 入出力ファイル件数出力(数値→X(30)転送は9桁ゼロ埋め表示形)
msgOutPara(CST_MSGIINKES, "KIN04R01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGOUTKES, "KIN04W01", ConvUtil.padLeftZero(cunW01Out, 9));
msgOutPara(CST_MSGOUTKES, "KIN04W02", ConvUtil.padLeftZero(cunW02Out, 9));
// *** 終了メッセージ出力
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) {
m00MhoPar.reset(); // INITIALIZE M00MHOPAR.
m00MhoPar.setM00MsgCod(msgCod); // MOVE ... TO M00MSGCOD.
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1); // MOVE ... TO M00UMKDATS22-01.
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2); // MOVE ... TO M00UMKDATS22-02.
}
// MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1).(参照修改→先頭1桁上書き、残り29桁はSPACE)
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX); // MOVE CNS-PRGIDX TO ...-05.
Sub02MsgSub.execute(m00MhoPar); // CALL 'SUB02MSG' USING M00MHOPAR.
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999); // MOVE CNS-ABD999 TO E01ABDCOD.
Sub03EndSub.execute(e01AbdPar); // CALL 'SUB03END'.
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), "UTF-8"));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), "UTF-8"));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), "UTF-8"));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** WRITE W01OUTREC.ADD 1 TO CUN-W01OUT. */
private void writeW01() {
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/** WRITE W02OUTREC.ADD 1 TO CUN-W02OUT. */
private void writeW02() {
w02OutFil.println(w02.toRecordLine());
cunW02Out++;
}
/** CLOSE R01INNFIL W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
r01InnFil = null;
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+566
View File
@@ -0,0 +1,566 @@
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import dto.Kin02Rec;
import dto.Kin03Rec;
import dto.Kin04Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KIN05MAT 打刻休暇照合処理
* ============================================================
* PROGRAM-ID. KIN05MAT → 类 Kin05MatMainPGMパターン:マッチング(1:N))
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R01INNFIL(KIN04W01 EDITED-PUNCH F80B) → R01INN_FILECOPY KIN04REC → dto.Kin04Rec
* R02INNFIL(KIN02W01 LEAVE-DAILY F80B) → R02INN_FILECOPY KIN02REC → dto.Kin02Rec
* W01OUTFIL(KIN05W01 KIN-LEAVE F160B)→ W01OUT_FILECOPY KIN03REC → dto.Kin03Rec
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01Para()
* 1200R02INNSOR→readR02Para() 2000MAJSOR→majPara() 2020MATCHSOR→matchPara()
* 2030PRIOSOR→prioSor() 2100R01ONLYSOR→r01OnlyPara() 2200R02ONLYSOR→r02OnlyPara()
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 与原代码的差异(详见 doc\conv\KIN05MAT_对照表.md 疑问·回答记录):
* #Q1 照合キー(EMP-ID+DATE)はグループ項目のバイト比較と等価なため、文字列連結で比較。
* #Q2 優先順位選定(2030PRIOSOR)は COBOL の EVALUATE TRUE を if/else で忠実に再現。
* #Q3 COBOL は WRK-BEST-* を 2020MATCHSOR と 1000ITTSOR のみ初期化し、
* R01単独出力(2100R01ONLYSOR)ではリセットしないため、前件マッチの休暇情報が
* 継続する不具合がある(設計書 2-1/3-5「R01単独→LEAVE-TYPE='99'」と矛盾)。
* ⇒ Java は【設計書準拠】とし、每条 R01 処理前(majPara先頭)に WRK-BEST-* を
* '99'/0 に初期化する(resetBest())。COBOL の不整合は本ファイル内コメントで指摘。
* #Q4 FUNCTION WHEN-COMPILED → 程序启动时刻代替。
*/
public class Kin05MatMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KIN05MAT'. */
private static final String CST_PRGIDX = "KIN05MAT";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-NO-LEAVE PIC X(002) VALUE '99'. 休暇なし */
private static final String CST_NO_LEAVE = "99";
/** 03 CNS-LEAVE-01 PIC X(002) VALUE '01'. */
private static final String CST_LEAVE_01 = "01";
/** 03 CNS-LEAVE-02 PIC X(002) VALUE '02'. */
private static final String CST_LEAVE_02 = "02";
/** 03 CNS-LEAVE-03 PIC X(002) VALUE '03'. */
private static final String CST_LEAVE_03 = "03";
/** 03 CNS-LEAVE-04 PIC X(002) VALUE '04'. */
private static final String CST_LEAVE_04 = "04";
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KIN04W01.EDITED-PUNCH入力) */
private static final String R01INN_FILE = "data/KIN04W01.dat";
/** SELECT R02INNFIL ASSIGN TO KIN02W01.LEAVE-DAILY入力) */
private static final String R02INN_FILE = "data/KIN02W01.dat";
/** SELECT W01OUTFIL ASSIGN TO KIN05W01.KIN-LEAVE出力) */
private static final String W01OUT_FILE = "data/KIN05W01.dat";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-R02INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR02Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WS-R01-STATUS PIC X(002).OPENチェック用、Javaは例外で代替) */
private String wsR01Status = "00";
/** 03 WS-R02-STATUS PIC X(002). */
private String wsR02Status = "00";
/** 03 WS-W01-STATUS PIC X(002). */
private String wsW01Status = "00";
/** 03 WRK-R01EOF PIC X(001). 読込フラグ */
private String wrkR01Eof = "0";
/** 03 WRK-R02EOF PIC X(001). 読込フラグ */
private String wrkR02Eof = "0";
/** 03 WRK-R01KEYEMP-ID 8 + DATE 8 */
private String wrkR01Key = "";
/** 03 WRK-R02KEYEMP-ID 8 + DATE 8 */
private String wrkR02Key = "";
/** 03 WRK-BEST-TYPE PIC X(002). 選定休暇種別 */
private String wrkBestType = CST_NO_LEAVE;
/** 03 WRK-BEST-STR-TIME PIC 9(004). */
private int wrkBestStrTime;
/** 03 WRK-BEST-END-TIME PIC 9(004). */
private int wrkBestEndTime;
/** 03 WRK-BEST-APPL-ID PIC 9(009). */
private int wrkBestApplId;
/** 03 WRK-BEST-FOUND PIC X(001). 選定済フラグ */
private String wrkBestFound = "0";
/** 88 WRK-BEST-IS-FOUND VALUE '1'. */
private boolean isBestFound() {
return "1".equals(wrkBestFound);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** FD R01INNFIL の 01 R01INNREC.(現在レコードバッファ) */
private final Kin04Rec r01 = new Kin04Rec();
/** FD R02INNFIL の 01 R02OUTREC.(現在レコードバッファ) */
private final Kin02Rec r02 = new Kin02Rec();
/** FD W01OUTFIL の 01 W01OUTREC. */
private final Kin03Rec w01 = new Kin03Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private BufferedReader r02InnFil;
private PrintWriter w01OutFil;
/** 入力ファイルパス(R01/R02/W01)。引数で上書き可。 */
private final String r01InnFile;
private final String r02InnFile;
private final String w01OutFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(String[] args) {
final String r1 = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
final String r2 = (args != null && args.length > 1) ? args[1] : R02INN_FILE;
final String w1 = (args != null && args.length > 2) ? args[2] : W01OUT_FILE;
new Kin05MatMain(r1, r2, w1).run(); // PERFORM 0000MAJCOLSOR 相当
}
/** 引数付コンストラクタ:各ファイルパスを指定(テスト等で使用)。 */
public Kin05MatMain(final String r01InnFile, final String r02InnFile, final String w01OutFile) {
this.r01InnFile = r01InnFile;
this.r02InnFile = r02InnFile;
this.w01OutFile = w01OutFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL R01-EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!"1".equals(wrkR01Eof)) { // PERFORM 2000MAJSOR UNTIL WRK-R01-EOF
majPara();
}
termPara();
} finally {
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→BEST領域初期化→OPEN→OPENチェック→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※疑问#Q4:WHEN-COMPILED→启动时刻代替
String compiled = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
wrkR01Eof = "0";
wrkR02Eof = "0";
wrkR01Key = "";
wrkR02Key = "";
// *** BEST領域初期化(R01のみ出力時に使用)
wrkBestFound = "0"; // MOVE '0' TO WRK-BEST-FOUND.
wrkBestType = CST_NO_LEAVE; // MOVE CNS-NO-LEAVE TO WRK-BEST-TYPE.
wrkBestStrTime = 0; // MOVE ZERO TO WRK-BEST-STR-TIME.
wrkBestEndTime = 0; // MOVE ZERO TO WRK-BEST-END-TIME.
wrkBestApplId = 0; // MOVE ZERO TO WRK-BEST-APPL-ID.
// *** 入出力ファイル OPEN INPUT R01/R02 / OUTPUT W01.
openFiles();
// *** OPENチェック(STATUS≠'00' → ABEND 999
if (!"00".equals(wsR01Status) || !"00".equals(wsR02Status) || !"00".equals(wsW01Status)) {
abendPara();
}
// *** R01/R02を読み込み
readR01Para();
readR02Para();
}
/**
* 段落 1100R01INNSOR##R01##読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR01Para() {
try {
String line = r01InnFil.readLine();
if (line == null) {
wrkR01Eof = "1"; // AT END: MOVE '1' TO WRK-R01EOF.
} else {
r01.fromRecordLine(line); // FD R01INNREC への転送.
cunR01Inn++; // ADD 1 TO CUN-R01INN.
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 1200R02INNSOR##R02##読込処理。
* READ AT END / NOT AT END のマッピング. AT END→EOF、NOT AT END→件数++.
*/
private void readR02Para() {
try {
String line = r02InnFil.readLine();
if (line == null) {
wrkR02Eof = "1"; // AT END: MOVE '1' TO WRK-R02EOF.
} else {
r02.fromRecordLine(line); // FD R02OUTREC への転送.
cunR02Inn++; // ADD 1 TO CUN-R02INN.
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理(マッチング制御)。
* R01キー設定→R02キー設定(有効時)→EVALUATE TRUE で3分岐。
*/
private void majPara() {
// *** 設計書準拠:每条打刻(R01)は独立に判定。COBOL(KIN05MAT.cbl)は
// WRK-BEST-* を 2020MATCHSOR と 1000ITTSOR のみ初期化し、
// R01単独出力(2100R01ONLYSOR)ではリセットしないため、前件マッチの
// 休暇情報が継続する不具合がある。Java は設計書意図(='99')どおり、
// 每条 R01 処理前に WRK-BEST-* を初期化する。
resetBest();
// *** R01キー設定
setR01Key();
// *** R02キー設定(有効な場合)
if (!"1".equals(wrkR02Eof)) {
setR02Key();
}
// *** EVALUATE TRUE マッチング3分岐(COBOL の WHEN 順序を if/else で再現)
if ("1".equals(wrkR02Eof)) {
// WHEN WRK-R02-EOF → R01のみ出力+R01次読込
r01OnlyPara();
readR01Para();
} else if (wrkR01Key.compareTo(wrkR02Key) < 0) {
// WHEN WRK-R01KEY < WRK-R02KEY → R01のみ出力+R01次読込
r01OnlyPara();
readR01Para();
} else if (wrkR01Key.compareTo(wrkR02Key) == 0) {
// WHEN WRK-R01KEY = WRK-R02KEY → マッチング処理+R01次読込
matchPara();
readR01Para();
} else {
// WHEN WRK-R01KEY > WRK-R02KEY → R02のみスキップ+R02次読込
r02OnlyPara();
readR02Para();
}
}
/** WRK-R01KEY = R01EMP-ID(8) + R01WORK-DATE(8)(グループ比較と等価)。 */
private void setR01Key() {
wrkR01Key = r01.getEmpId() + r01.getWorkDate();
}
/** WRK-R02KEY = R02EMP-ID(8) + R02DATE(8)(グループ比較と等価)。 */
private void setR02Key() {
wrkR02Key = r02.getEmpId() + ConvUtil.padLeftZero(r02.getDate(), 8);
}
/**
* WRK-BEST-* 初期化('99'/0)。
* 設計書(KIN05MAT)準拠:每条打刻(R01)の処理前に実施し、前件マッチの
* 休暇情報が継続しないよう保証する。COBOL は本初期化を 2020MATCHSOR と
* 1000ITTSOR のみに記述し R01単独出力で漏れたため、無休假打刻に前件の
* 休暇種別が残る不具合がある(#Q3)。
*/
private void resetBest() {
wrkBestFound = "0"; // MOVE '0' TO WRK-BEST-FOUND.
wrkBestType = CST_NO_LEAVE; // MOVE CNS-NO-LEAVE TO WRK-BEST-TYPE.
wrkBestStrTime = 0; // MOVE ZERO TO WRK-BEST-STR-TIME.
wrkBestEndTime = 0; // MOVE ZERO TO WRK-BEST-END-TIME.
wrkBestApplId = 0; // MOVE ZERO TO WRK-BEST-APPL-ID.
}
/**
* 段落 2020MATCHSOR:マッチング処理(N:1ループレ)。
* 一致キーの全R02を読込み、最適休暇を選定した上で KIN-LEAVE 出力。
* ※ majPara() 先頭の resetBest() で既に '99'/0 初期化済だが、COBOL 互換の
* 位置でも再初期化(冪等)。設計書準拠の挙動は majPara() 側で保証される。
*/
private void matchPara() {
// *** 選定領域初期化(majPara() での初期化と同一内容)
resetBest();
// *** N:1ループ 一致するR02を全件処理
while (true) {
if ("1".equals(wrkR02Eof)) {
break; // WRK-R02-EOF
}
if (!wrkR01Key.equals(wrkR02Key)) {
break; // WRK-R01KEY < WRK-R02KEY OR WRK-R01KEY > WRK-R02KEY
}
// *** ベスト選定
if (!isBestFound()) {
// 初回 → ベストとして保持
wrkBestFound = "1"; // MOVE '1' TO WRK-BEST-FOUND.
wrkBestType = r02.getLeaveType();
wrkBestStrTime = r02.getStartTime();
wrkBestEndTime = r02.getEndTime();
wrkBestApplId = r02.getApplId();
} else {
prioSor(r02.getLeaveType(), r02.getStartTime(),
r02.getEndTime(), r02.getApplId());
}
// *** R02次読込
readR02Para();
// *** R02キー再設定(有効な場合)
if (!"1".equals(wrkR02Eof)) {
setR02Key();
}
}
// *** KIN-LEAVE出力(R01のみ出力サブルーチンを共用)
r01OnlyPara();
}
/**
* 段落 2030PRIOSOR:優先順位判定。
* 優先順位 01 > 02 > 03 > 04。同種別は APPL-ID の小さい方を採用。
* COBOL の EVALUATE TRUE を if/else で忠実に再現(WHEN OTHER→CONTINUE)。
*/
private void prioSor(final String r02Type, final int r02Str,
final int r02End, final int r02Appl) {
// EVALUATE TRUER02LEAVE-TYPE で分岐)
if (CST_LEAVE_01.equals(r02Type)) {
if (!CST_LEAVE_01.equals(wrkBestType)) {
wrkBestType = r02Type;
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
} else if (r02Appl < wrkBestApplId) {
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else if (CST_LEAVE_02.equals(r02Type)) {
if (CST_LEAVE_01.equals(wrkBestType)) {
// CONTINUE01 が優先)
} else if (CST_LEAVE_02.equals(wrkBestType)) {
if (r02Appl < wrkBestApplId) {
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else {
wrkBestType = r02Type;
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else if (CST_LEAVE_03.equals(r02Type)) {
if (CST_LEAVE_01.equals(wrkBestType)) {
// CONTINUE
} else if (CST_LEAVE_02.equals(wrkBestType)) {
// CONTINUE
} else if (CST_LEAVE_03.equals(wrkBestType)) {
if (r02Appl < wrkBestApplId) {
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else {
wrkBestType = r02Type;
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else if (CST_LEAVE_04.equals(r02Type)) {
if (CST_LEAVE_01.equals(wrkBestType)) {
// CONTINUE
} else if (CST_LEAVE_02.equals(wrkBestType)) {
// CONTINUE
} else if (CST_LEAVE_03.equals(wrkBestType)) {
// CONTINUE
} else if (CST_LEAVE_04.equals(wrkBestType)) {
if (r02Appl < wrkBestApplId) {
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else {
wrkBestType = r02Type;
wrkBestStrTime = r02Str;
wrkBestEndTime = r02End;
wrkBestApplId = r02Appl;
}
} else {
// WHEN OTHER → CONTINUE(未知種別は無視)
}
}
/**
* 段落 2100R01ONLYSOR:R01のみ出力(またはマッチ結果出力)。
* 打刻情報(1-80B)+休暇情報(81-160B、WRK-BEST-* を使用)を KIN-LEAVE に出力。
*/
private void r01OnlyPara() {
// *** KIN-LEAVEレコード編集
w01.reset(); // INITIALIZE W01OUTREC.
// *** 打刻情報(1-80B)
w01.setEmpId(r01.getEmpId()); // MOVE R01EMP-ID TO W01EMP-ID.
w01.setWorkDate(r01.getWorkDate()); // MOVE R01WORK-DATE TO W01WORK-DATE.
w01.setStrTime(r01.getStrTime()); // MOVE R01STR-TIME TO W01STR-TIME.
w01.setEndTime(r01.getEndTime()); // MOVE R01END-TIME TO W01END-TIME.
w01.setDeptId(r01.getDeptId()); // MOVE R01DEPT-ID TO W01DEPT-ID.
w01.setTerminal(r01.getTerminal()); // MOVE R01TERMINAL TO W01TERMINAL.
// *** 休暇情報(81-160B)
w01.setLeaveType(wrkBestType); // MOVE WRK-BEST-TYPE TO W01LEAVE-TYPE.
w01.setLeaveStrTime(wrkBestStrTime); // MOVE WRK-BEST-STR-TIME TO W01LEAVE-STR-TIME.
w01.setLeaveEndTime(wrkBestEndTime); // MOVE WRK-BEST-END-TIME TO W01LEAVE-END-TIME.
w01.setLeaveHours(0); // MOVE ZERO TO W01LEAVE-HOURS.
w01.setApplicationId(wrkBestApplId); // MOVE WRK-BEST-APPL-ID TO W01APPLICATION-ID.
writeW01(); // WRITE W01OUTREC.ADD 1 TO CUN-W01OUT.
}
/**
* 段落 2200R02ONLYSOR:R02のみスキップ(該当打刻のない休暇レコードは出力しない)。
*/
private void r02OnlyPara() {
// PERFORM 1200R02INNSOR は呼出元(majPara)で実施済(CONTINUE)。
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
// *** 入出力ファイル CLOSE
closeFiles();
// *** 入出力ファイル件数出力(数値→X(30)転送は9桁ゼロ埋め表示形)
msgOutPara(CST_MSGIINKES, "KIN04W01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGIINKES, "KIN02W01", ConvUtil.padLeftZero(cunR02Inn, 9));
msgOutPara(CST_MSGOUTKES, "KIN05W01", ConvUtil.padLeftZero(cunW01Out, 9));
// *** 終了メッセージ出力
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) {
m00MhoPar.reset(); // INITIALIZE M00MHOPAR.
m00MhoPar.setM00MsgCod(msgCod); // MOVE ... TO M00MSGCOD.
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1); // MOVE ... TO M00UMKDATS22-01.
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2); // MOVE ... TO M00UMKDATS22-02.
}
// MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1).(参照修改→先頭1桁上書き、残り29桁はSPACE)
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX); // MOVE CNS-PRGIDX TO ...-05.
Sub02MsgSub.execute(m00MhoPar); // CALL 'SUB02MSG' USING M00MHOPAR.
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999); // MOVE CNS-ABD999 TO E01ABDCOD.
Sub03EndSub.execute(e01AbdPar); // CALL 'SUB03END'.
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL/R02INNFIL / OUTPUT W01OUTFIL(失敗時は STATUS='99'→ABEND)。 */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), "UTF-8"));
r02InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r02InnFile), "UTF-8"));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(w01OutFile), "UTF-8"));
} catch (IOException e) {
// OPEN失敗相当:FILE STATUS ≠ '00'(後続の OPENチェックで ABEND 999
wsR01Status = "99";
wsR02Status = "99";
wsW01Status = "99";
}
}
/** WRITE W01OUTREC.ADD 1 TO CUN-W01OUT. */
private void writeW01() {
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/** CLOSE R01INNFIL R02INNFIL W01OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(r02InnFil);
closeQuiet(w01OutFil);
r01InnFil = null;
r02InnFil = null;
w01OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+371
View File
@@ -0,0 +1,371 @@
import dto.Kin06Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
/**
* ============================================================
* 【COBOL→Java】KIN06CLD 出勤日カレンダー生成処理
* ============================================================
* PROGRAM-ID. KIN06CLD → 类 Kin06CldMain
* PGMパターン:レイアウト編集のみ(GETPUT)
* 機能概要:PARM(YEARMONTH=YYYYMM) 指定年月の出勤日カレンダーを生成する。
* 在籍社員(EMP_MASTER.STATUS='1') × 当月1日〜月末日をループし、
* 休日判定(祝日テーブル線形探索 + 曜日判定)を行い、出勤日のみ
* WORK-DAY-FILE(KIN06W01.DAT) に出力する。
*
* ※COBOL構造は main で受け取る引数(args)を YEARMONTH= 解析に使用。
* COBOL「ACCEPT WRK-CMDLINE FROM COMMAND-LINE」はコマンドライン全体を取得するが、
* 本Java実装では args を空白連結した文字列から "YEARMONTH=" をスキャンする。
*/
public class Kin06CldMain {
/** CNS-PRGIDX VALUE 'KIN06CLD'. */
private static final String CNS_PRGIDX = "KIN06CLD";
/** CNS-MSGSTR VALUE 001(開始). */
private static final int CNS_MSGSTR = 1;
/** CNS-MSGFIN VALUE 002(終了). */
private static final int CNS_MSGFIN = 2;
/** CNS-MSGOUTKES VALUE 007(出力件数). */
private static final int CNS_MSGOUTKES = 7;
/** CNS-MSGKEYINF VALUE 033(コンパイル日時). */
private static final int CNS_MSGKEYINF = 33;
/** CNS-KN0002 VALUE 2(メッセージ置換項目定数). */
private static final String CNS_KN0002 = "2";
/** CNS-ABD999 VALUE 999. */
private static final int CNS_ABD999 = 999;
/** SELECT OUTRECFIL ASSIGN TO "data/KIN06W01.DAT". */
private static final String OUTREC_FILE = "data/KIN06W01.DAT";
/** 月日数テーブル(MONTH-DAYS / MONTH-DAY OCCURS 12、0-indexed. */
private static final int[] MONTH_DAYS = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
// ---- ワークエリア(WRKARA)相当 ----
private int wrkYear;
private int wrkMonth;
private int wrkLastDay;
/** CUN-OUTREC PIC S9(009) COMP-3. */
private long cunOutrec;
/** WRK-YEARMONTHPARM解析値 YYYYMM、祝日SELECTのLIKEフィルタに使用). */
private String wrkYearMonth;
/** 休日セット(HOLIDAY-TABLE 相当、祝日文字列 YYYYMMDD を保持). */
private final Set<String> holidaySet = new HashSet<>();
/** メッセージ編集出力SR用連絡領域(ZANMSGAC. */
private final ZanmsgacPar msgPar = new ZanmsgacPar();
/** ABEND処理SR用連絡領域(ZANENDAC. */
private final ZanendacPar endPar = new ZanendacPar();
/** 出力ファイルライター. */
private BufferedWriter outrecFil;
/** DB接続. */
private Connection conn;
public static void main(final String[] args) {
new Kin06CldMain().run(args);
}
private void run(final String[] args) {
initProc(args);
majProc();
stpProc();
}
// *****************************************************************
// 初期処理 1000ITTSOR
// *****************************************************************
private void initProc(final String[] args) {
// 開始メッセージ出力(MSG 001)
msgOutPara(CNS_MSGSTR, "", "", "");
// コンパイル日時出力(MSG 033)
msgOutPara(CNS_MSGKEYINF, "COMPILED", "", "");
// PARM解析(1100PARMSOR
parmProc(args);
// DB接続
try {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection("jdbc:sqlite:data/kin.db");
} catch (Exception e) {
throw new RuntimeException("DB接続失敗: " + e.getMessage(), e);
}
// 休日データ読込(1200HOLIDAYSOR
holidayProc();
// 出力ファイルOPEN
try {
outrecFil = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(OUTREC_FILE), StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("出力ファイルOPEN失敗: " + e.getMessage(), e);
}
// 社員CURSOR OPEN1300EMPOPNSOR
empOpenProc();
}
// *****************************************************************
// PARM解析 1100PARMSOR
// *****************************************************************
private void parmProc(final String[] args) {
// COBOL: ACCEPT WRK-CMDLINE FROM COMMAND-LINE → 本実装は args 連結文字列を使用
StringBuilder cmdLine = new StringBuilder();
for (int i = 0; i < args.length; i++) {
if (i > 0) {
cmdLine.append(' ');
}
cmdLine.append(args[i]);
}
String line = cmdLine.toString();
// "YEARMONTH=" をスキャン(COBOL: PERFORM VARYING で先頭10桁一致を検索)
int pos = line.indexOf("YEARMONTH=");
boolean found = pos >= 0;
String yearMonthStr = "";
if (found) {
int valStart = pos + "YEARMONTH=".length();
if (valStart + 6 <= line.length()) {
yearMonthStr = line.substring(valStart, valStart + 6);
} else {
yearMonthStr = line.substring(valStart);
}
}
// PARM必須チェック(COBOL: IF NOT WRK-PARM-FOUND → ABEND 999
if (!found || yearMonthStr.length() != 6) {
abend();
return;
}
// 年・月分解(COBOL: WRK-YEARMONTH(1:4)=YEAR, (5:2)=MONTH
int yearMonth;
try {
yearMonth = Integer.parseInt(yearMonthStr);
} catch (NumberFormatException e) {
// 非数値 → 月チェックで ABEND 相当
abend();
return;
}
wrkYear = yearMonth / 100;
wrkMonth = yearMonth % 100;
wrkYearMonth = yearMonthStr;
// 月チェック(1-12)COBOL: IF WRK-MONTH < 1 OR > 12 → ABEND 999
if (wrkMonth < 1 || wrkMonth > 12) {
abend();
return;
}
// 月の日数計算(COBOL: MOVE MONTH-DAY(WRK-MONTH) TO WRK-LAST-DAY
wrkLastDay = MONTH_DAYS[wrkMonth - 1];
// 閏年判定(2月のみ)COBOL: MOD(YEAR,400)=0 OR (MOD(YEAR,4)=0 AND MOD(YEAR,100)!=0)
if (wrkMonth == 2) {
if ((wrkYear % 400 == 0) || (wrkYear % 4 == 0 && wrkYear % 100 != 0)) {
wrkLastDay = 29;
}
}
}
// *****************************************************************
// 休日データ読込 1200HOLIDAYSOR
// *****************************************************************
private void holidayProc() {
// 設計書: SELECT HOLIDAY_DATE FROM HOLIDAY_CALENDAR
// WHERE HOLIDAY_DATE LIKE 'YYYYMM%'(対象月のみ)
// ガード: 対象月祝日が50件未満の場合のみ ADD(テーブル溢れ防止)
String sql = "SELECT HOLIDAY_DATE FROM HOLIDAY_CALENDAR "
+ "WHERE HOLIDAY_DATE LIKE ? ORDER BY HOLIDAY_DATE";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, wrkYearMonth + "%");
try (ResultSet rs = ps.executeQuery()) {
int count = 0;
while (rs.next()) {
if (count >= 50) {
break;
}
String holidayDate = rs.getString("HOLIDAY_DATE");
if (holidayDate != null) {
holidaySet.add(holidayDate.trim());
count++;
}
}
}
} catch (Exception e) {
throw new RuntimeException("休日読込失敗: " + e.getMessage(), e);
}
}
// *****************************************************************
// 社員CURSOR OPEN 1300EMPOPNSOR
// *****************************************************************
private void empOpenProc() {
// COBOL: SELECT EMP_ID FROM EMP_MASTER WHERE STATUS='1' ORDER BY EMP_ID
// 初回FETCH → SQLCODE != 0 なら EMP-EOF='1'
String sql = "SELECT EMP_ID FROM EMP_MASTER WHERE STATUS = '1' ORDER BY EMP_ID";
try {
empStmt = conn.prepareStatement(sql);
empRs = empStmt.executeQuery();
if (!empRs.next()) {
empEof = true;
}
} catch (Exception e) {
throw new RuntimeException("社員CURSOR OPEN失敗: " + e.getMessage(), e);
}
}
// 社員CURSOR用ステートメント・結果セット
private PreparedStatement empStmt;
private ResultSet empRs;
/** WRK-EMP-EOF PIC X(001) VALUE '1' → empEof=true. */
private boolean empEof;
// *****************************************************************
// 主処理 2000MAJSOR(社員EOFまで繰り返す)
// *****************************************************************
private void majProc() {
while (!empEof) {
final String hvEmpId;
try {
hvEmpId = empRs.getString("EMP_ID");
} catch (Exception e) {
throw new RuntimeException("社員取得失敗: " + e.getMessage(), e);
}
// 当月1日〜月末日をループ
for (int wrkDay = 1; wrkDay <= wrkLastDay; wrkDay++) {
// 日付文字列構築(COBOL: WRK-DATE-STR = YEAR*10000 + MONTH*100 + DAY
int wrkDateStr = wrkYear * 10000 + wrkMonth * 100 + wrkDay;
String wrkDateStrX = ConvUtil.padLeftZero(wrkDateStr, 8);
// 曜日計算(COBOL: INTEGER-OF-DATE / MOD(7) → 本実装は LocalDate.getDayOfWeek で等価)
// COBOL: 0=日,1=月…6=土 → OUTDAY-OF-WEEK: 0→7, 1..6 そのまま
// Java : DayOfWeek.getValue() = 1=月…7=日(上記変換と同一)
LocalDate ld = LocalDate.of(wrkYear, wrkMonth, wrkDay);
int dayOfWeek = ld.getDayOfWeek().getValue();
DayOfWeek dow = ld.getDayOfWeek();
// 休日判定(COBOL: PERFORM VARYING 線形探索 → Set.contains で等価)
boolean isHoliday = holidaySet.contains(wrkDateStrX);
// 出勤日のみ出力(土曜=6 または 日曜=7 または 祝日 → スキップ)
if (isHoliday || dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY) {
// CONTINUE(スキップ)
} else {
Kin06Rec outRec = new Kin06Rec();
outRec.setEmpId(hvEmpId);
outRec.setDate(wrkDateStr);
outRec.setDayOfWeek(dayOfWeek);
outRec.setFiller("");
writeOutrec(outRec.toRecordLine());
cunOutrec++;
}
}
// 次の社員をFETCHCOBOL: FETCH C_EMP INTO :HV-EMP-ID
try {
if (!empRs.next()) {
empEof = true;
}
} catch (Exception e) {
throw new RuntimeException("社員FETCH失敗: " + e.getMessage(), e);
}
}
}
// *****************************************************************
// 終了処理 3000STPSOR
// *****************************************************************
private void stpProc() {
// カーソルCLOSE
try {
if (empRs != null) {
empRs.close();
}
if (empStmt != null) {
empStmt.close();
}
} catch (Exception ignore) {
// クローズ失敗は無視(COBOL同様)
}
// DB切断
try {
if (conn != null) {
conn.close();
}
} catch (Exception ignore) {
// 無視
}
// 出力ファイルCLOSE
try {
if (outrecFil != null) {
outrecFil.close();
}
} catch (Exception ignore) {
// 無視
}
// 出力件数メッセージ(MSG 007: P1=KIN06W01, P2=件数)
msgOutPara(CNS_MSGOUTKES, "KIN06W01", ConvUtil.padLeftZero(cunOutrec, 9), "");
// 終了メッセージ出力(MSG 002)
msgOutPara(CNS_MSGFIN, "", "", "");
}
// *****************************************************************
// メッセージ編集出力 4000MSGOUTSORSUB02MSG 呼出)
// *****************************************************************
private void msgOutPara(final int msgCod, final String p1, final String p2, final String p3) {
msgPar.reset();
msgPar.setM00MsgCod(msgCod);
// COBOL: MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1) / -04(1:1) / PRGIDX TO -05
msgPar.setM00Umkdats2203(CNS_KN0002);
msgPar.setM00Umkdats2204(CNS_KN0002);
msgPar.setM00Umkdats2205(CNS_PRGIDX);
msgPar.setM00Umkdats2201(p1);
msgPar.setM00Umkdats2202(p2);
Sub02MsgSub.execute(msgPar);
}
/** WRITE OUTREC. */
private void writeOutrec(final String line) {
try {
outrecFil.write(line);
outrecFil.newLine();
} catch (Exception e) {
throw new RuntimeException("出力失敗: " + e.getMessage(), e);
}
}
/** ABEND処理(COBOL: MOVE 999 TO E01ABDCOD, CALL 'SUB03END'. */
private void abend() {
endPar.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(endPar);
}
}
+450
View File
@@ -0,0 +1,450 @@
import dto.Kin02Rec;
import dto.Kin03Rec;
import dto.Kin05Rec;
import dto.Kin06Rec;
import dto.Kin07Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import dto.ZantimacPar;
import util.ConvUtil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* ============================================================
* 【COBOL→Java】KIN07DAI 日別勤怠計算処理
* ============================================================
* PROGRAM-ID. KIN07DAI → 类 Kin07DaiMain
* PGMパターン:マッチング(1:N) + EVALUATE 4パターン分岐
* 機能概要:WORK-DAY-FILE(R01)を主駆動に、KIN-LEAVE(R02)とLEAVE-DAILY(R03)を
* 社員番号+日付キーで照合し、4パターン(A/B/C/D)に分岐して
* 日別勤怠記録(DAILY-RECORD / KIN07W01.DAT)を出力する。
* 休暇時間はランチ除外 + 0.1h切上丸め(SUB05TIM)。
*
* ※入力ファイルは「社員番号+日付 昇順」でソート済みであること(設計書前提)。
*/
public class Kin07DaiMain {
/** CNS-PRGIDX VALUE 'KIN07DAI'. */
private static final String CNS_PRGIDX = "KIN07DAI";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final String CNS_KN0002 = "2";
/** CNS-ABD999 VALUE 999. */
private static final int CNS_ABD999 = 999;
/** 休暇種別定数 */
private static final String CNS_LEAVE_01 = "01";
private static final String CNS_LEAVE_02 = "02";
private static final String CNS_LEAVE_03 = "03";
private static final String CNS_LEAVE_04 = "04";
/** 時刻定数(分) */
private static final int CNS_LUNCH_START_MIN = 720; // 12:00
private static final int CNS_LUNCH_END_MIN = 780; // 13:00
/** CNS-DAILY-HOURS 8.0h → 0.1h単位整数 = 80 */
private static final int CNS_DAILY_HOURS_TENTH = 80;
/** CNS-RND-MODE-UP = 10.1h切上) */
private static final int CNS_RND_MODE_UP = 1;
/** 入力ファイル(R01=WORK-DAY-FILE=KIN06CLD出力 / R02=KIN-LEAVE / R03=LEAVE-DAILY. */
private static final String R01_FILE = "data/KIN06W01.DAT";
private static final String R02_FILE = "data/KIN07R02.DAT";
private static final String R03_FILE = "data/KIN07R03.DAT";
/** 出力ファイル(W01=DAILY-RECORD 200B. */
private static final String W01_FILE = "data/KIN07W01.DAT";
/** 出力ファイル(W02=ERROR-LOG / KIN05REC / VB 200B. */
private static final String W02_FILE = "data/KIN07E01.DAT";
// ---- カウンタ ----
private long cunR01Inn;
private long cunR02Inn;
private long cunR03Inn;
private long cunW01Out;
private long cunW02Out;
private final ZanmsgacPar msgPar = new ZanmsgacPar();
private final ZanendacPar endPar = new ZanendacPar();
private BufferedWriter w01OutFil;
private BufferedWriter w02OutFil;
/** 主処理で使用する入力リスト(initProc で一括読込)。 */
private List<Kin03Rec> r02List;
private List<Kin02Rec> r03List;
public static void main(final String[] args) {
new Kin07DaiMain().run();
}
private void run() {
List<Kin06Rec> r01List = initProc();
majProc(r01List);
stpProc();
}
// *****************************************************************
// 初期処理 1000ITTSOR
// *****************************************************************
private List<Kin06Rec> initProc() {
// 開始メッセージ
msgOutPara(CNS_MSGSTR, "", "", "");
// コンパイル日時
msgOutPara(CNS_MSGKEYINF, "COMPILED", "", "");
// 入力ファイル読込(R01/R02/R03
List<Kin06Rec> r01List = readR01();
r02List = readR02();
r03List = readR03();
// 出力ファイルOPEN
try {
w01OutFil = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(W01_FILE), StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("出力ファイルOPEN失敗: " + e.getMessage(), e);
}
// W02(ERROR-LOG) OPEN(設計書使用ファイル一覧 No.5)
try {
w02OutFil = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(W02_FILE), StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("W02出力ファイルOPEN失敗: " + e.getMessage(), e);
}
return r01List;
}
// *****************************************************************
// 主処理 2000MAJSORR01駆動マージ)
// *****************************************************************
private void majProc(final List<Kin06Rec> r01List) {
int r02Idx = 0;
int r03Idx = 0;
int r02Size = r02List.size();
int r03Size = r03List.size();
for (final Kin06Rec r01 : r01List) {
cunR01Inn++;
String r01Key = buildKey(r01.getEmpId(), r01.getDate());
// 積算・編集領域初期化
int wrkTimeIn = 0;
int wrkTimeOut = 0;
int annualH = 0;
int personalH = 0;
int officialH = 0;
int sickH = 0;
int pattern = 4; // PATTERN-D
// ---- R02: 追越レコードをスキップ ----
while (r02Idx < r02Size) {
String r02Key = buildKey(r02List.get(r02Idx).getEmpId(),
parseIntDate(r02List.get(r02Idx).getWorkDate()));
if (r02Key.compareTo(r01Key) >= 0) {
break;
}
r02Idx++;
cunR02Inn++;
}
// ---- R02 マッチ判定 ----
if (r02Idx < r02Size) {
String r02Key = buildKey(r02List.get(r02Idx).getEmpId(),
parseIntDate(r02List.get(r02Idx).getWorkDate()));
if (r02Key.equals(r01Key)) {
Kin03Rec r02 = r02List.get(r02Idx);
wrkTimeIn = ConvUtil.parseIntSafe(r02.getStrTime(), 0);
wrkTimeOut = ConvUtil.parseIntSafe(r02.getEndTime(), 0);
pattern = 2; // PATTERN-B
r02Idx++;
cunR02Inn++;
}
}
// ---- R03: 追越レコードをスキップ ----
while (r03Idx < r03Size) {
String r03Key = buildKey(r03List.get(r03Idx).getEmpId(),
r03List.get(r03Idx).getDate());
if (r03Key.compareTo(r01Key) >= 0) {
break;
}
r03Idx++;
cunR03Inn++;
}
// ---- R03 マッチループ(0〜N件の全休暇を処理)----
if (r03Idx < r03Size) {
String r03Key = buildKey(r03List.get(r03Idx).getEmpId(),
r03List.get(r03Idx).getDate());
if (r03Key.equals(r01Key)) {
while (r03Idx < r03Size) {
Kin02Rec r03 = r03List.get(r03Idx);
String k3 = buildKey(r03.getEmpId(), r03.getDate());
if (!k3.equals(r01Key)) {
break;
}
int lvH = calcLeaveHours(r03.getStartTime(), r03.getEndTime());
String lt = r03.getLeaveType();
if (CNS_LEAVE_01.equals(lt)) {
annualH += lvH;
} else if (CNS_LEAVE_02.equals(lt)) {
personalH += lvH;
} else if (CNS_LEAVE_03.equals(lt)) {
officialH += lvH;
} else if (CNS_LEAVE_04.equals(lt)) {
sickH += lvH;
} else {
// 未知休暇種別 → 設計書に規定なし。COBOL 同様に無視(W02 非出力、休暇時間へ計上せず)。
}
r03Idx++;
cunR03Inn++;
}
if (pattern == 2) {
pattern = 1; // PATTERN-A(打刻+休暇)
} else {
pattern = 3; // PATTERN-C(休暇のみ)
}
}
}
// ---- EVALUATE 4パターン分岐 → 出力 ----
switch (pattern) {
case 1:
writeW01(r01, wrkTimeIn, wrkTimeOut, annualH, personalH, officialH, sickH, 0);
break;
case 2:
writeW01(r01, wrkTimeIn, wrkTimeOut, 0, 0, 0, 0, 0);
break;
case 3:
// 打刻なし、休暇時間は8h上限
if (annualH > CNS_DAILY_HOURS_TENTH) {
annualH = CNS_DAILY_HOURS_TENTH;
}
if (personalH > CNS_DAILY_HOURS_TENTH) {
personalH = CNS_DAILY_HOURS_TENTH;
}
if (officialH > CNS_DAILY_HOURS_TENTH) {
officialH = CNS_DAILY_HOURS_TENTH;
}
if (sickH > CNS_DAILY_HOURS_TENTH) {
sickH = CNS_DAILY_HOURS_TENTH;
}
writeW01(r01, 0, 0, annualH, personalH, officialH, sickH, 0);
break;
case 4:
default:
// 打刻なし、未申請欠勤=8.0h
writeW01(r01, 0, 0, 0, 0, 0, 0, CNS_DAILY_HOURS_TENTH);
break;
}
}
}
// *****************************************************************
// 終了処理 3000STPSOR
// *****************************************************************
private void stpProc() {
try {
if (w01OutFil != null) {
w01OutFil.close();
}
} catch (Exception ignore) {
// 無視
}
try {
if (w02OutFil != null) {
w02OutFil.close();
}
} catch (Exception ignore) {
// 無視
}
// 入力件数出力(MSG 006
msgOutPara(CNS_MSGIINKES, "KIN07R01", ConvUtil.padLeftZero(cunR01Inn, 9), "");
msgOutPara(CNS_MSGIINKES, "KIN07R02", ConvUtil.padLeftZero(cunR02Inn, 9), "");
msgOutPara(CNS_MSGIINKES, "KIN07R03", ConvUtil.padLeftZero(cunR03Inn, 9), "");
// 出力件数出力(MSG 007
msgOutPara(CNS_MSGOUTKES, "KIN07W01", ConvUtil.padLeftZero(cunW01Out, 9), "");
// W02 出力件数(エラー件数)
msgOutPara(CNS_MSGOUTKES, "KIN07E01", ConvUtil.padLeftZero(cunW02Out, 9), "");
// 終了メッセージ
msgOutPara(CNS_MSGFIN, "", "", "");
}
// *****************************************************************
// 休暇時間計算 5000CALCLVSOR
// 開始/終了時刻(HHMM) → ランチ除外 → 0.1h切上丸め(SUB05TIM)
// 戻り値: 0.1h単位整数(tenth)
// *****************************************************************
private int calcLeaveHours(final int startTime, final int endTime) {
int startHour = startTime / 100;
int startMin = startTime % 100;
int endHour = endTime / 100;
int endMin = endTime % 100;
int startMinTotal = startHour * 60 + startMin;
int endMinTotal = endHour * 60 + endMin;
int totalMin = endMinTotal - startMinTotal;
// ランチ除外(12:00-13:00 = 720-780分)
int lunchOverlap = 0;
if (endMinTotal > CNS_LUNCH_START_MIN && startMinTotal < CNS_LUNCH_END_MIN) {
int lo = Math.min(endMinTotal, CNS_LUNCH_END_MIN);
int hi = Math.max(startMinTotal, CNS_LUNCH_START_MIN);
lunchOverlap = lo - hi;
}
totalMin -= lunchOverlap;
// 時間(小数)に変換(9(4)V9(1) への COMPUTE は 0.1h で切捨)
int tenth = totalMin / 6; // floor(totalMin / 60 * 10) = floor(totalMin / 6)
// SUB05TIM 呼出(0.1h切上)
ZantimacPar tp = new ZantimacPar();
tp.setT01Timhrs(tenth / 10.0);
tp.setT01Timrrc(CNS_RND_MODE_UP);
Sub05TimSub.execute(tp);
return (int) Math.round(tp.getT01Timout() * 10.0);
}
// *****************************************************************
// DAILY-RECORD 出力 7000W01OUTSOR
// *****************************************************************
private void writeW01(final Kin06Rec r01, final int timeIn, final int timeOut,
final int annualH, final int personalH, final int officialH,
final int sickH, final int absentH) {
Kin07Rec out = new Kin07Rec();
out.setEmpId(r01.getEmpId());
out.setDate(r01.getDate());
out.setTimeIn(timeIn);
out.setTimeOut(timeOut);
out.setAnnualH(annualH);
out.setPersonalH(personalH);
out.setOfficialH(officialH);
out.setSickH(sickH);
out.setAbsentH(absentH);
out.setFiller("");
try {
w01OutFil.write(out.toRecordLine());
w01OutFil.newLine();
cunW01Out++;
} catch (Exception e) {
throw new RuntimeException("出力失敗: " + e.getMessage(), e);
}
}
// *****************************************************************
// ユーティリティ
// *****************************************************************
private String buildKey(final String empId, final int date) {
return (empId == null ? "" : empId) + ConvUtil.padLeftZero(date, 8);
}
private int parseIntDate(final String dateStr) {
return ConvUtil.parseIntSafe(dateStr, 0);
}
private List<Kin06Rec> readR01() {
List<Kin06Rec> list = new ArrayList<>();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(R01_FILE), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
Kin06Rec rec = new Kin06Rec();
rec.fromRecordLine(line);
list.add(rec);
}
} catch (Exception e) {
throw new RuntimeException("R01読込失敗(" + R01_FILE + "): " + e.getMessage(), e);
}
return list;
}
private List<Kin03Rec> readR02() {
List<Kin03Rec> list = new ArrayList<>();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(R02_FILE), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
Kin03Rec rec = new Kin03Rec();
rec.fromRecordLine(line);
list.add(rec);
}
} catch (Exception e) {
throw new RuntimeException("R02読込失敗(" + R02_FILE + "): " + e.getMessage(), e);
}
return list;
}
private List<Kin02Rec> readR03() {
List<Kin02Rec> list = new ArrayList<>();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(R03_FILE), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
Kin02Rec rec = new Kin02Rec();
rec.fromRecordLine(line);
list.add(rec);
}
} catch (Exception e) {
throw new RuntimeException("R03読込失敗(" + R03_FILE + "): " + e.getMessage(), e);
}
return list;
}
/** メッセージ編集出力 4000MSGOUTSORSUB02MSG 呼出). */
private void msgOutPara(final int msgCod, final String p1, final String p2, final String p3) {
msgPar.reset();
msgPar.setM00MsgCod(msgCod);
msgPar.setM00Umkdats2203(CNS_KN0002);
msgPar.setM00Umkdats2204(CNS_KN0002);
msgPar.setM00Umkdats2205(CNS_PRGIDX);
msgPar.setM00Umkdats2201(p1);
msgPar.setM00Umkdats2202(p2);
Sub02MsgSub.execute(msgPar);
}
// *****************************************************************
// W02 ERROR-LOG 出力(設計書: W02ERR-CATEGORY='99' / W02ERR-DETAIL=詳細)
// 呼出元: SUB05TIM異常時・未知休暇種別等のエラー検出時
// *****************************************************************
private void writeW02(final String detail) {
Kin05Rec err = new Kin05Rec();
err.setErrCategory(99);
err.setErrDetail(detail);
try {
w02OutFil.write(err.toRecordLine());
w02OutFil.newLine();
cunW02Out++;
} catch (Exception e) {
throw new RuntimeException("W02出力失敗: " + e.getMessage(), e);
}
}
/** ABEND処理(9999ABDSOR 相当)。 */
private void abend() {
endPar.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(endPar);
}
}
+467
View File
@@ -0,0 +1,467 @@
import dto.Kin07Rec;
import dto.Kin08Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
/**
* ============================================================
* 【COBOL→Java】KIN08DBU 勤怠DB更新処理
* ============================================================
* PROGRAM-ID. KIN08DBU → 类 Kin08DbuMain
* PGMパターン:DB更新 + SYSIN読込(P28)
* 機能概要:DAILY-RECORD(R01/KIN07REC)を読込み DAILY_RECORDS へ INSERT。
* 社員別月次集計(AGGREGATION-TABLE)後、MONTHLY_ABSENCE へ UPSERT。
* 集計結果を ABSENCE-SUMMARY(W01/KIN08REC) に出力。
* SYSIN制御カード(T/P/M)で対象社員・対象年月・モードを指定。
*/
public class Kin08DbuMain {
private static final String CNS_PRGIDX = "KIN08DBU";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final String CNS_KN0002 = "2";
private static final int CNS_ABD999 = 999;
/** SELECT R01INNFIL ASSIGN TO "KIN08R01". */
private static final String R01_FILE = "data/KIN08R01.DAT";
/** SELECT SYSINFILE ASSIGN TO "KIN08S01". */
private static final String SYSIN_FILE = "data/KIN08S01.DAT";
/** SELECT W01OUTFIL ASSIGN TO "KIN08W01". */
private static final String W01_FILE = "data/KIN08W01.DAT";
// ---- カウンタ ----
private long cunR01Inn;
private long cunDbxIns;
private long cunDbxUpd;
private long cunDbxDel;
private long cunW01Out;
// ---- SYSIN 解析結果 ----
/** Tカード対象社員(指定なし=全社員)。 */
private final List<String> targetList = new ArrayList<>();
private boolean allEmp = true;
/** Pカード対象年月。 */
private String yearMonth = "";
private boolean periodFound = false;
/** Mカード RESET モード。 */
private boolean resetMode = false;
// ---- 集計テーブル(AGGREGATION-TABLE 相当、tenth 単位で保持)----
private static class AggRow {
String empId;
String yearMonth;
int annualH;
int personalH;
int officialH;
int sickH;
int absentH;
/** WS-TOTAL-HOURS(設計書: COMPUTE ROUNDED + ON SIZE ERROR、出力には非含). */
int totalH;
}
private final List<AggRow> aggTable = new ArrayList<>();
private final ZanmsgacPar msgPar = new ZanmsgacPar();
private final ZanendacPar endPar = new ZanendacPar();
private BufferedWriter w01OutFil;
private Connection conn;
public static void main(final String[] args) {
new Kin08DbuMain().run();
}
private void run() {
initProc();
majProc();
stpProc();
}
// *****************************************************************
// 初期処理 1000ITTSOR
// *****************************************************************
private void initProc() {
msgOutPara(CNS_MSGSTR, "", "", "");
msgOutPara(CNS_MSGKEYINF, "COMPILED", "", "");
// ワーク初期化
allEmp = true;
resetMode = false;
// DB接続
try {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection("jdbc:sqlite:data/kin.db");
conn.setAutoCommit(false);
} catch (Exception e) {
throw new RuntimeException("DB接続失敗: " + e.getMessage(), e);
}
// SYSIN読込
readSysin();
// 必須パラメータ(対象年月)未設定チェック
if (!periodFound) {
abend();
}
// 入出力ファイルOPEN
try {
w01OutFil = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(W01_FILE), StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("出力ファイルOPEN失敗: " + e.getMessage(), e);
}
}
// *****************************************************************
// SYSIN 読込・解析(1199-SYSIN-LOOP / 1110/1120/1130
// *****************************************************************
private void readSysin() {
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(SYSIN_FILE), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
char card = line.charAt(0);
String body = line.length() > 2 ? line.substring(2) : "";
if (card == 'T') {
// Tカード: 対象社員一覧(カンマ区切り)
targetList.clear();
String[] emps = body.split(",");
for (String e : emps) {
String t = e.trim();
if (!t.isEmpty()) {
targetList.add(t);
}
}
allEmp = false;
} else if (card == 'P') {
// Pカード: YEARMONTH=YYYYMM
String[] kv = body.split("=");
String val = kv.length > 1 ? kv[1].trim() : "";
if (val.length() >= 6) {
yearMonth = val.substring(0, 6);
periodFound = true;
}
} else if (card == 'M') {
// Mカード: MODE=RESET|NORMAL
String mode = body.replace("MODE=", "").trim();
resetMode = "RESET".equals(mode);
} else if (card == '*') {
// コメント行は無視
} else {
System.out.println("WARNING: Unknown card type [" + card + "]");
}
}
} catch (Exception e) {
throw new RuntimeException("SYSIN読込失敗: " + e.getMessage(), e);
}
}
// *****************************************************************
// 主処理 2000MAJSOR
// *****************************************************************
private void majProc() {
// 設計書(使用DBテーブル): RESET = DELETE + INSERT
// → DAILY_RECORDS 挿入前に既存レコードを削除(stpProc 後だと PK 違反)
if (resetMode) {
resetDelete();
}
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(R01_FILE), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
cunR01Inn++;
Kin07Rec rec = new Kin07Rec();
rec.fromRecordLine(line);
// 社員番号有効性チェック(空ならスキップ)
if (rec.getEmpId() == null || rec.getEmpId().trim().isEmpty()) {
System.out.println("WARNING: Empty EMP-ID record " + cunR01Inn);
continue;
}
String empId = rec.getEmpId();
String targetDate = String.format("%08d", rec.getDate());
String timeIn = ConvUtil.padLeftZero(rec.getTimeIn(), 4);
String timeOut = ConvUtil.padLeftZero(rec.getTimeOut(), 4);
double annualH = rec.getAnnualH() / 10.0;
double personalH = rec.getPersonalH() / 10.0;
double officialH = rec.getOfficialH() / 10.0;
double sickH = rec.getSickH() / 10.0;
double absentH = rec.getAbsentH() / 10.0;
// DAILY_RECORDS INSERT
String insSql = "INSERT INTO DAILY_RECORDS "
+ "(EMP_ID, TARGET_DATE, TIME_IN, TIME_OUT, ANNUAL_LEAVE_H, "
+ "PERSONAL_LEAVE_H, OFFICIAL_LEAVE_H, SICK_LEAVE_H, "
+ "UNAPPROVED_ABSENT_H, UPDATED_AT) VALUES "
+ "(?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)";
try (PreparedStatement ps = conn.prepareStatement(insSql)) {
ps.setString(1, empId);
ps.setString(2, targetDate);
ps.setString(3, timeIn);
ps.setString(4, timeOut);
ps.setDouble(5, annualH);
ps.setDouble(6, personalH);
ps.setDouble(7, officialH);
ps.setDouble(8, sickH);
ps.setDouble(9, absentH);
ps.executeUpdate();
cunDbxIns++;
} catch (Exception e) {
dbError(e);
return;
}
// 対象社員フィルタリング(Tカード指定時) → 集計のみスキップ
if (!allEmp && !targetList.contains(empId)) {
continue;
}
// 年月抽出(YYYYMMDD→YYYYMM)
String ym = targetDate.substring(0, 6);
// 集計テーブル加算
AggRow row = findAgg(empId, ym);
if (row == null) {
if (aggTable.size() < 100) {
row = new AggRow();
row.empId = empId;
row.yearMonth = ym;
row.annualH = rec.getAnnualH();
row.personalH = rec.getPersonalH();
row.officialH = rec.getOfficialH();
row.sickH = rec.getSickH();
row.absentH = rec.getAbsentH();
aggTable.add(row);
} else {
System.out.println("WARNING: Agg table full " + empId);
}
} else {
// ANNUAL は COMPUTE ROUNDED(入力は0.1h単位のため ADD と同一結果)、
// 他は ADDV9(1) 切捨だが値は既に0.1h単位)→ 全て単純加算
row.annualH += rec.getAnnualH();
row.personalH += rec.getPersonalH();
row.officialH += rec.getOfficialH();
row.sickH += rec.getSickH();
row.absentH += rec.getAbsentH();
}
// 設計書: WS-TOTAL-HOURS ROUNDED = ANNUAL+PERSONAL+OFFICIAL+SICK+ABSENT
// ON SIZE ERROR → 0 クリア + 警告
if (row != null) {
row.totalH = computeTotalHours(empId, ym, row.annualH, row.personalH,
row.officialH, row.sickH, row.absentH);
}
}
} catch (Exception e) {
throw new RuntimeException("R01読込失敗: " + e.getMessage(), e);
}
}
private AggRow findAgg(final String empId, final String ym) {
for (AggRow r : aggTable) {
if (r.empId.equals(empId) && r.yearMonth.equals(ym)) {
return r;
}
}
return null;
}
/** RESET 時の既存レコード削除(DAILY_RECORDS + MONTHLY_ABSENCE、対象年月のみ). */
private void resetDelete() {
try (PreparedStatement ps = conn.prepareStatement(
"DELETE FROM DAILY_RECORDS WHERE TARGET_DATE LIKE ?")) {
ps.setString(1, yearMonth + "%");
int n = ps.executeUpdate();
cunDbxDel += n;
} catch (Exception e) {
dbError(e);
return;
}
try (PreparedStatement ps = conn.prepareStatement(
"DELETE FROM MONTHLY_ABSENCE WHERE YEAR_MONTH = ?")) {
ps.setString(1, yearMonth);
int n = ps.executeUpdate();
cunDbxDel += n;
} catch (Exception e) {
dbError(e);
}
}
/**
* 設計書 2-3: WS-TOTAL-HOURS ROUNDED = ANNUAL+PERSONAL+OFFICIAL+SICK+ABSENT。
* 9(4)V9(1) → tenth 単位上限 99999。溢れ(ON SIZE ERROR)時は 0 クリア+警告。
*/
private int computeTotalHours(final String empId, final String ym,
final int annualH, final int personalH, final int officialH,
final int sickH, final int absentH) {
long sum = (long) annualH + personalH + officialH + sickH + absentH;
if (sum > 99999 || sum < -9999) {
System.out.println("WARNING: Total hours overflow emp=" + empId + " ym=" + ym);
return 0;
}
return (int) sum;
}
// *****************************************************************
// 終了処理 3000STPSOR
// *****************************************************************
private void stpProc() {
try {
// 集計結果 → MONTHLY_ABSENCE UPSERT
for (AggRow row : aggTable) {
String cntSql = "SELECT COUNT(*) FROM MONTHLY_ABSENCE "
+ "WHERE EMP_ID = ? AND YEAR_MONTH = ?";
int cnt;
try (PreparedStatement ps = conn.prepareStatement(cntSql)) {
ps.setString(1, row.empId);
ps.setString(2, row.yearMonth);
try (ResultSet rs = ps.executeQuery()) {
cnt = rs.next() ? rs.getInt(1) : 0;
}
}
if (cnt > 0) {
String updSql = "UPDATE MONTHLY_ABSENCE SET "
+ "ANNUAL_LEAVE_H=?, PERSONAL_LEAVE_H=?, OFFICIAL_LEAVE_H=?, "
+ "SICK_LEAVE_H=?, UNAPPROVED_ABSENT_H=?, UPDATED_AT=CURRENT_TIMESTAMP "
+ "WHERE EMP_ID=? AND YEAR_MONTH=?";
try (PreparedStatement ps = conn.prepareStatement(updSql)) {
ps.setDouble(1, row.annualH / 10.0);
ps.setDouble(2, row.personalH / 10.0);
ps.setDouble(3, row.officialH / 10.0);
ps.setDouble(4, row.sickH / 10.0);
ps.setDouble(5, row.absentH / 10.0);
ps.setString(6, row.empId);
ps.setString(7, row.yearMonth);
ps.executeUpdate();
cunDbxUpd++;
}
} else {
String insSql = "INSERT INTO MONTHLY_ABSENCE "
+ "(EMP_ID, YEAR_MONTH, ANNUAL_LEAVE_H, PERSONAL_LEAVE_H, "
+ "OFFICIAL_LEAVE_H, SICK_LEAVE_H, UNAPPROVED_ABSENT_H, UPDATED_AT) "
+ "VALUES (?,?,?,?,?,?,?,CURRENT_TIMESTAMP)";
try (PreparedStatement ps = conn.prepareStatement(insSql)) {
ps.setString(1, row.empId);
ps.setString(2, row.yearMonth);
ps.setDouble(3, row.annualH / 10.0);
ps.setDouble(4, row.personalH / 10.0);
ps.setDouble(5, row.officialH / 10.0);
ps.setDouble(6, row.sickH / 10.0);
ps.setDouble(7, row.absentH / 10.0);
ps.executeUpdate();
cunDbxIns++;
}
}
}
// ABSENCE_SUMMARY 出力
for (AggRow row : aggTable) {
Kin08Rec out = new Kin08Rec();
out.setEmpId(row.empId);
out.setYearMonth(row.yearMonth);
out.setAnnualH(row.annualH);
out.setPersonalH(row.personalH);
out.setOfficialH(row.officialH);
out.setSickH(row.sickH);
out.setAbsentH(row.absentH);
out.setFiller("");
w01OutFil.write(out.toRecordLine());
w01OutFil.newLine();
cunW01Out++;
}
// COMMIT
conn.commit();
} catch (Exception e) {
dbError(e);
return;
}
// ファイルCLOSE
try {
if (w01OutFil != null) {
w01OutFil.close();
}
} catch (Exception ignore) {
// 無視
}
try {
if (conn != null) {
conn.close();
}
} catch (Exception ignore) {
// 無視
}
// 件数メッセージ
msgOutPara(CNS_MSGIINKES, "KIN08R01", ConvUtil.padLeftZero(cunR01Inn, 9), "");
msgOutPara(CNS_MSGIINKES, "INS", ConvUtil.padLeftZero(cunDbxIns, 9), "");
msgOutPara(CNS_MSGIINKES, "UPD", ConvUtil.padLeftZero(cunDbxUpd, 9), "");
if (cunDbxDel > 0) {
msgOutPara(CNS_MSGOUTKES, "DEL", ConvUtil.padLeftZero(cunDbxDel, 9), "");
}
msgOutPara(CNS_MSGOUTKES, "KIN08W01", ConvUtil.padLeftZero(cunW01Out, 9), "");
msgOutPara(CNS_MSGFIN, "", "", "");
}
// *****************************************************************
// DBエラー処理 9100DBERRSORROLLBACK + ABEND
// *****************************************************************
private void dbError(final Exception e) {
System.out.println("SQL ERROR: " + e.getMessage() + " PGM=KIN08DBU");
try {
if (conn != null) {
conn.rollback();
}
} catch (Exception ignore) {
// 無視
}
abend();
}
/** メッセージ編集出力 4000MSGOUTSORSUB02MSG 呼出). */
private void msgOutPara(final int msgCod, final String p1, final String p2, final String p3) {
msgPar.reset();
msgPar.setM00MsgCod(msgCod);
msgPar.setM00Umkdats2203(CNS_KN0002);
msgPar.setM00Umkdats2204(CNS_KN0002);
msgPar.setM00Umkdats2205(CNS_PRGIDX);
msgPar.setM00Umkdats2201(p1);
msgPar.setM00Umkdats2202(p2);
Sub02MsgSub.execute(msgPar);
}
/** ABEND処理(9999ABDSOR 相当)。 */
private void abend() {
endPar.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(endPar);
}
}
+222
View File
@@ -0,0 +1,222 @@
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class Kin09CsvMain {
private static final int REC_LEN = 200;
private static final String DAILY_HEADER =
"社員番号,日付,出勤時刻,退勤時刻,休暇種別,年休時間,事假時間,因公特批假時間,病欠時間,未申請欠勤時間";
private static final String SUMMARY_HEADER =
"社員番号,年月,年休計,事假計,因公特批假計,病欠計,未申請欠勤計";
private static final String OUTPUT_FILE = "data/KIN09W01.DAT";
public static void main(String[] args) throws Exception {
System.out.println("KIN09CSV 開始");
String[] parsed = parseParm(args);
String yearMonth = parsed[0];
String mode = parsed[1];
boolean shortMode = mode.equalsIgnoreCase("SHORT");
if (yearMonth == null || yearMonth.trim().isEmpty()) {
System.err.println("ABEND: YEARMONTH は必須です (CNS-ABD999)");
System.exit(999);
return;
}
String dateFrom = yearMonth + "01";
String dateTo = yearMonth + "31";
Class.forName("org.sqlite.JDBC");
try (Connection conn = DriverManager.getConnection("jdbc:sqlite:data/kin.db")) {
try (BufferedWriter out = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(OUTPUT_FILE), "UTF-8"))) {
long w01out = 0;
boolean fullMode = !shortMode;
if (fullMode) {
writeRec(out, DAILY_HEADER);
w01out++;
int lineCount = 0;
String dSql = "SELECT EMP_ID, TARGET_DATE, TIME_IN, TIME_OUT, "
+ "ANNUAL_LEAVE_H, PERSONAL_LEAVE_H, OFFICIAL_LEAVE_H, "
+ "SICK_LEAVE_H, UNAPPROVED_ABSENT_H "
+ "FROM DAILY_RECORDS "
+ "WHERE TARGET_DATE >= ? AND TARGET_DATE <= ? "
+ "ORDER BY EMP_ID, TARGET_DATE";
try (PreparedStatement ps = conn.prepareStatement(dSql)) {
ps.setString(1, dateFrom);
ps.setString(2, dateTo);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String empId = sanitize(getStr(rs, "EMP_ID"));
String date = getStr(rs, "TARGET_DATE");
String timeIn = getStr(rs, "TIME_IN");
String timeOut = getStr(rs, "TIME_OUT");
double annual = rs.getDouble("ANNUAL_LEAVE_H");
double personal = rs.getDouble("PERSONAL_LEAVE_H");
double official = rs.getDouble("OFFICIAL_LEAVE_H");
double sick = rs.getDouble("SICK_LEAVE_H");
double absent = rs.getDouble("UNAPPROVED_ABSENT_H");
String leaveType = "99";
if (annual > 0) leaveType = "01";
else if (personal > 0) leaveType = "02";
else if (official > 0) leaveType = "03";
else if (sick > 0) leaveType = "04";
String line = empId + "," + date + "," + timeIn + "," + timeOut + ","
+ leaveType + ","
+ editHours(annual) + "," + editHours(personal) + ","
+ editHours(official) + "," + editHours(sick) + ","
+ editHours(absent);
writeRec(out, line);
w01out++;
lineCount++;
if (lineCount > 100) {
writeAdvancing(out, 2, DAILY_HEADER);
w01out++;
lineCount = 0;
}
}
}
}
}
// 月次サマリ(FULL / SHORT 両方出力)。FULL は日別との区切り空白を挿入。
if (fullMode) {
writeAdvancing(out, 2, "");
w01out++;
}
writeRec(out, SUMMARY_HEADER);
w01out++;
String mSql = "SELECT EMP_ID, YEAR_MONTH, ANNUAL_LEAVE_H, "
+ "PERSONAL_LEAVE_H, OFFICIAL_LEAVE_H, SICK_LEAVE_H, "
+ "UNAPPROVED_ABSENT_H "
+ "FROM MONTHLY_ABSENCE WHERE YEAR_MONTH = ? ORDER BY EMP_ID";
try (PreparedStatement ps = conn.prepareStatement(mSql)) {
ps.setString(1, yearMonth);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String empId = sanitize(getStr(rs, "EMP_ID"));
String ym = getStr(rs, "YEAR_MONTH");
double annual = rs.getDouble("ANNUAL_LEAVE_H");
double personal = rs.getDouble("PERSONAL_LEAVE_H");
double official = rs.getDouble("OFFICIAL_LEAVE_H");
double sick = rs.getDouble("SICK_LEAVE_H");
double absent = rs.getDouble("UNAPPROVED_ABSENT_H");
String line = empId + "," + ym + ","
+ editHours(annual) + "," + editHours(personal) + ","
+ editHours(official) + "," + editHours(sick) + ","
+ editHours(absent);
writeRec(out, line);
w01out++;
}
}
}
System.out.println("CSV-OUTPUT 出力件数: " + w01out);
}
}
System.out.println("KIN09CSV 終了");
}
private static String[] parseParm(String[] args) {
String cmdLine = String.join(",", args);
String parm1 = "";
String parm2 = "";
int commaIdx = cmdLine.indexOf(',');
if (commaIdx >= 0) {
parm1 = cmdLine.substring(0, commaIdx);
parm2 = cmdLine.substring(commaIdx + 1);
} else {
parm1 = cmdLine;
}
String[] kv1 = parm1.split("=", 2);
String[] kv2 = parm2.split("=", 2);
String key1 = kv1.length > 0 ? kv1[0].trim() : "";
String val1 = kv1.length > 1 ? kv1[1] : "";
String key2 = kv2.length > 0 ? kv2[0].trim() : "";
String val2 = kv2.length > 1 ? kv2[1] : "";
String yearMonth = "";
if (key1.equals("YEARMONTH")) {
yearMonth = val1.length() >= 6 ? val1.substring(0, 6) : val1;
} else if (key2.equals("YEARMONTH")) {
yearMonth = val2.length() >= 6 ? val2.substring(0, 6) : val2;
}
String mode = "FULL";
if (key1.equals("MODE")) {
mode = val1.length() >= 5 ? val1.substring(0, 5) : val1;
} else if (key2.equals("MODE")) {
mode = val2.length() >= 5 ? val2.substring(0, 5) : val2;
}
if (mode.trim().isEmpty()) {
mode = "FULL";
}
return new String[]{yearMonth, mode.toUpperCase()};
}
private static String sanitize(String s) {
if (s == null) return "";
return s.replace(",", ";").replace("\r", " ").replace("\n", " ");
}
private static String getStr(ResultSet rs, String col) throws Exception {
String v = rs.getString(col);
return v == null ? "" : v;
}
private static String editHours(double d) {
int tenth = (int) Math.round(d * 10);
int intPart = tenth / 10;
int dec = Math.abs(tenth % 10);
return intPart + "." + dec;
}
private static void writeRec(BufferedWriter out, String content) throws Exception {
out.write(padRight(content, REC_LEN));
out.newLine();
}
private static void writeAdvancing(BufferedWriter out, int linesBefore, String content) throws Exception {
for (int i = 0; i < linesBefore; i++) {
out.newLine();
}
writeRec(out, content);
}
private static String padRight(String s, int len) {
if (s == null) s = "";
StringBuilder sb = new StringBuilder(s);
while (sb.length() < len) {
sb.append(' ');
}
if (sb.length() > len) {
sb.setLength(len);
}
return sb.toString();
}
}
+468
View File
@@ -0,0 +1,468 @@
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.nio.charset.StandardCharsets;
import dto.Kyu01Rec;
import dto.Kyu99Rec;
import dto.ZanendacPar;
import dto.ZanchkacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU01CVT 社員マスタCSV→FB変換処理
* ============================================================
* PROGRAM-ID. KYU01CVT → 类 Kyu01CvtMainPGMパターン:CSV→FB変換)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R01INNFIL(KYU01R01 CSV入力 V500B) → R01INN_FILE
* W01OUTFIL(KYU01W01 EMP-RECORD F80B)→ W01OUT_FILECOPY KYU01REC → dto.Kyu01Rec
* W02OUTFIL(KYU01W02 ERROR-LOG VB200B)→ W02OUT_FILECOPY KYU99REC → dto.Kyu99Rec
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub / SUB04CHK→Sub04ChkSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01Para()
* 2000MAJSOR→majPara() 2100CSVUNSOR→splitCsv() 2200ERROUTSOR→errorCsvPara()
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 处理要点(詳細設計書_KYU01CVT 準拠):
* - コメント行(先頭'#')は読飛ばし。
* - 引用符内改行:WRK-IN-QUOTE フラグ追跡し、奇数個の'"'で状態反転、
* 引用符内の物理行を WRK-CSV-ACCUM に蓄積、完全1レコード分が揃ったら
* INSPECT CONVERTINGCR/LF→SPACE)で改行→スペース変換。
* - CSV分解は9項目を期待(設計「項目数≠9 は ERROR-LOG」)。
* ※COBOL の UNSTRING TALLYING は区切り数(8)をカウントし CNS-FLD-COUNT(=9)と比較して
*   常に不一致となる off-by-one と同種の不備があるが、設計書を優先しフィールド数=9 で判定。
* - 社員番号空チェック・SUB04CHKEMPID/NUM)で異常時は ERROR-LOG 出力。
*/
public class Kyu01CvtMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU01CVT'. */
private static final String CST_PRGIDX = "KYU01CVT";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-FLD-COUNT PIC 9(002) VALUE 9. 期待フィールド数(設計準拠) */
private static final int CST_FLD_COUNT = 9;
/** 各CSV項目のPIC幅(UNSTRING受信領域の桁数、超過は切捨)。 */
private static final int[] FLD_WIDTH = {8, 40, 2, 2, 3, 9, 7, 2, 1};
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KYU01R01.CSV入力) */
private static final String R01INN_FILE = "data/KYU01R01.csv";
/** SELECT W01OUTFIL ASSIGN TO KYU01W01.EMP-RECORD出力) */
private static final String W01OUT_FILE = "data/KYU01W01.txt";
/** SELECT W02OUTFIL ASSIGN TO KYU01W02.ERROR-LOG出力) */
private static final String W02OUT_FILE = "data/KYU01W02.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001). 読込フラグ(88 WRK-EOF-Y VALUE '1' */
private String wrkEof = "0";
/** 03 WRK-IN-QUOTE PIC X(001). 引用符状態(88 WRK-QUOTE-ACTIVE VALUE '1' */
private boolean inQuote;
/** 03 WRK-CSV-ACCUM PIC X(500) + WRK-ACCUM-LEN. 引用符内蓄積バッファ */
private final StringBuilder accum = new StringBuilder();
/** FD R01INNFIL の 01 R01INNREC PIC X(500).(現在の物理行バッファ) */
private String wrkCsvLine = "";
/** 88 WRK-EOF-Y VALUE '1'. */
private boolean isEof() {
return "1".equals(wrkEof);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
/** COPY ZANCHKAC.SUB04CHK用) */
private final ZanchkacPar c01ChkPar = new ZanchkacPar();
// ==================== 出力レコード(FILE SECTION → DTO ====================
/** 01 W01OUTREC.FD W01OUTFIL */
private final Kyu01Rec w01 = new Kyu01Rec();
/** 01 W02OUTREC.FD W02OUTFIL */
private final Kyu99Rec w02 = new Kyu99Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kyu01CvtMain(inputFile).run();
}
public Kyu01CvtMain() {
this(R01INN_FILE);
}
public Kyu01CvtMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!isEof()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF = '1'
majPara();
}
termPara();
} finally {
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→OPEN→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
cunR01Inn = 0;
cunW01Out = 0;
cunW02Out = 0;
inQuote = false;
accum.setLength(0);
wrkCsvLine = "";
// *** 入出力ファイル OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL.
openFiles();
// *** 初回READ
readR01Para();
}
/**
* 段落 1100R01INNSORR01読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR01Para() {
try {
final String line = r01InnFil.readLine();
if (line == null) {
// AT END: MOVE '1' TO WRK-EOF.
wrkEof = "1";
} else {
// NOT AT END: ADD 1 TO CUN-R01INN.(バッファへ格納)
wrkCsvLine = line;
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* コメント行スキップ→引用符内改行処理(蓄積/完了判定)→processRecord へ。
*/
private void majPara() {
final String line = wrkCsvLine;
// *** コメント行(先頭'#')はスキップ(COBOLは引用符状態に関わらずここで判定)
if (!line.isEmpty() && line.charAt(0) == '#') {
readR01Para();
return;
}
if (inQuote) {
// *** 引用符内:蓄積継続または完了判定
final int q = countQuotes(line);
if ((q % 2) == 1) {
// 奇数個 → 引用符クローズ(完全1レコード分が揃った)
inQuote = false;
accum.append(line);
final String logical = accum.toString()
.replace("\r", " ").replace("\n", " ");
processRecord(logical);
accum.setLength(0);
readR01Para(); // 次の物理行へ
} else {
// 偶数個 → 蓄積継続
accum.append(line);
readR01Para();
}
return;
}
// *** 引用符外:奇数個の'"'で開始
final int q = countQuotes(line);
if ((q % 2) == 1) {
inQuote = true;
accum.setLength(0);
accum.append(line);
readR01Para();
return;
}
// *** 通常行(引用符なし)→ 直接 CSV 処理
processRecord(line.replace("\r", " ").replace("\n", " "));
readR01Para();
}
/**
* 段落 2100CSVUNSOR + 2-32-8CSV分解→項目数/空チェック→SUB04CHK→W01出力。
* @param logical 完全な1論理レコード(改行→スペース変換済)
*/
private void processRecord(final String logical) {
// *** 項目数≠9 → ERROR-LOG出力(設計「項目数≠9」準拠。
// ※UNSTRING の TALLYING は区切り数(8)を数えるため、RAW トークン数で判定)
final String[] tokens = logical.split(",", -1);
if (tokens.length != CST_FLD_COUNT) {
errorCsvPara(logical);
return;
}
// *** CSV分解(UNSTRING DELIMITED BY ',' → 9項目、受信領域は PIC 幅で切捨)
final String[] f = splitCsv(logical);
// *** 社員番号空チェック(INSPECT TALLYING FOR LEADING SPACES = 8 → 全桁SPACE
final String empId = f[0];
if (leadingSpaces(empId) == 8) {
w02.reset();
w02.setErrCategory("EMPID-ERR");
w02.setErrDetail("EMPTY EMP-ID AT LINE " + cunR01Inn);
writeW02();
return;
}
// *** SUB04CHK:社員番号(EMPID)数値妥当性チェック
if (!chkPara("EMPID", empId)) {
errorCsvPara(logical);
return;
}
// *** SUB04CHK:時給(NUM)数値妥当性チェック
if (!chkPara("NUM", f[6])) {
errorCsvPara(logical);
return;
}
// *** 社員名英字チェック(IF NOT ALPHABETIC → CONTINUE のため無動作)
// *** W01OUTREC編集(MOVE CSV各項目→W01OUTREC
w01.reset();
w01.setEmpId(empId);
w01.setEmpName(f[1]);
w01.setDeptCode(f[2]);
w01.setRegionCode(f[3]);
w01.setCategoryCode(f[4]);
w01.setBaseSalary(ConvUtil.parseIntSafe(f[5], 0));
w01.setHourlyRate(ConvUtil.parseIntSafe(f[6], 0));
w01.setDependentCount(ConvUtil.parseIntSafe(f[7], 0));
w01.setStatus(f[8]);
writeW01();
}
/**
* 段落 2100CSVUNSORUNSTRING DELIMITED BY ',' INTO 各フィールド(TALLYING相当は判定に使用)。
* COBOL と同様に受信領域の PIC 幅で切捨;不足項目は SPACE(数値は0)で補完。
*/
private String[] splitCsv(final String line) {
final String[] tokens = line.split(",", -1);
final String[] out = new String[CST_FLD_COUNT];
for (int i = 0; i < CST_FLD_COUNT; i++) {
if (i < tokens.length) {
final String t = tokens[i];
if (t.isEmpty()) {
// 空項目は UNSTRING で受信されなかったのと同等(INITIALIZE 済の SPACE
out[i] = ConvUtil.padRight("", FLD_WIDTH[i]);
} else if (t.length() > FLD_WIDTH[i]) {
out[i] = t.substring(0, FLD_WIDTH[i]); // 受信領域 PIC 幅で切捨
} else {
out[i] = t;
}
} else {
// UNSTRING で受信されなかった項目は INITIALIZE 済(SPACE)のまま
out[i] = ConvUtil.padRight("", FLD_WIDTH[i]);
}
}
return out;
}
/** 行内の '"' 出現回数(引用符状態反転判定用)。 */
private static int countQuotes(final String s) {
int n = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '"') {
n++;
}
}
return n;
}
/** 先行スペース数(COBOL INSPECT TALLYING FOR LEADING SPACES 相当)。 */
private static int leadingSpaces(final String s) {
int n = 0;
while (n < s.length() && s.charAt(n) == ' ') {
n++;
}
return n;
}
/**
* 段落 2200ERROUTSORCSV系エラー出力処理。
* MOVE 'CSV-ERR' TO W02ERR-CATEGORY.MOVE WRK-CSV-ACCUM(1:80) TO W02ERR-DETAIL.WRITE W02OUTREC.
*/
private void errorCsvPara(final String logical) {
w02.reset();
w02.setErrCategory("CSV-ERR ");
final String detail = (logical.length() > 80) ? logical.substring(0, 80) : logical;
w02.setErrDetail(detail);
writeW02();
}
/** SUB04CHK 呼出(C01CHKTYP/C01CHKDAT 設定→execute→RC=0 判定)。 */
private boolean chkPara(final String typ, final String dat) {
c01ChkPar.reset();
c01ChkPar.setC01ChkTyp(typ);
c01ChkPar.setC01ChkDat(ConvUtil.padRight(dat, 80));
Sub04ChkSub.execute(c01ChkPar);
return c01ChkPar.getC01ChkRrc() == 0;
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
closeFiles();
msgOutPara(CST_MSGIINKES, "KYU01R01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGOUTKES, "KYU01W01", ConvUtil.padLeftZero(cunW01Out, 9));
msgOutPara(CST_MSGOUTKES, "KYU01W02", ConvUtil.padLeftZero(cunW02Out, 9));
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), StandardCharsets.UTF_8));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), StandardCharsets.UTF_8));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** WRITE W01OUTREC.ADD 1 TO CUN-W01OUT. */
private void writeW01() {
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/** WRITE W02OUTREC.ADD 1 TO CUN-W02OUT. */
private void writeW02() {
w02OutFil.println(w02.toRecordLine());
cunW02Out++;
}
/** CLOSE R01INNFIL W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
r01InnFil = null;
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+502
View File
@@ -0,0 +1,502 @@
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.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import dto.Kyu01Rec;
import dto.Kyu99Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU02REG 社員マスタDB2登録処理
* ============================================================
* PROGRAM-ID. KYU02REG → 类 Kyu02RegMainPGMパターン:DB更新)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R02INNFIL(KYU02R02 EMP-RECORD FB80B) → R02INN_FILECOPY KYU01REC → dto.Kyu01Rec 再利用)
* W03OUTFIL(KYU02W03 ERROR-LOG VB200B) → W03OUT_FILECOPY KYU99REC → dto.Kyu99Rec 再利用)
* 使用DB2EMP-MASTEREXEC SQL INSERT/UPDATE、重複-803時UPDATE
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 使用COPY(連絡領域):ZANMSGAC→ZanmsgacPar / ZANENDAC→ZanendacPar
* ※SQLCA は JDBC の SQLException に映射(SQLCODE = e.getErrorCode()
*
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R02INNSOR→readR02Para()
* 2000MAJSOR→majPara() 2100ERROUTSOR→errorOutPara()
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara()
* 9999ABDSOR→abendPara()(本程序未 CALL、死代码、忠实残留)
*
* 处理要点(詳細設計書_KYU02REG 準拠):
* - EMP-RECORD(80B固定長)を 1 行毎読み、DB2 ホスト変数へ MOVE。
* - INSERT 試行。SQLCODE=0 → CUN-DB-INS 加算。SQLCODE=-803 → UPDATE へ切替
* (成功→CUN-DB-UPD 加算、失敗→ERROR-LOG)。その他 SQL エラー → ERROR-LOG。
* - ERROR-LOG は 'DB-ERR' + エラー社員番号を出力し、処理継続(ABEND しない)。
* - DB 接続は DbSample 標準例に倣い setAutoCommit(false)、正常終了時に commit、
* SQLException 時は rollback(詳細設計書に明示的 COMMIT は無いが、
* 本プロジェクトの DB 標準例に準拠して追加。※COBOL との差異は報告書に記載)。
* - CONNECT 先は COBOL の 'data/SALARY.db' だが、作業指示により
* DB2 URL 'jdbc:db2://localhost:50000/TESTDB'(ユーザ/パスはプレースホルダ)を使用。
*/
public class Kyu02RegMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU02REG'. */
private static final String CST_PRGIDX = "KYU02REG";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. (宣言のみ、本程序未使用) */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数メッセージ */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数メッセージ */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時メッセージ */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** DB 接続先(作業指示:DB2 URL。ユーザ/パスはプレースホルダで実値を保持しない。テスト時は -Dkyu.db.url で上書き) */
private static final String DB_URL = System.getProperty("kyu.db.url", "jdbc:db2://localhost:50000/TESTDB");
private static final String DB_USER = "db2inst1";
private static final String DB_PASS = "password";
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R02INNFIL ASSIGN TO EXTERNAL KYU02R02.EMP-RECORD FB80B 入力) */
private static final String R02INN_FILE = "data/KYU02R02.txt";
/** SELECT W03OUTFIL ASSIGN TO EXTERNAL KYU02W03.ERROR-LOG VB200B 出力) */
private static final String W03OUT_FILE = "data/KYU02W03.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R02INN PIC S9(009) COMP-3 VALUE ZERO. 入力読込件数 */
private long cunR02Inn;
/** 03 CUN-W03OUT PIC S9(009) COMP-3 VALUE ZERO. エラーログ出力件数 */
private long cunW03Out;
/** 03 CUN-DB-INS PIC S9(009) COMP-3 VALUE ZERO. INSERT 件数 */
private long cunDbIns;
/** 03 CUN-DB-UPD PIC S9(009) COMP-3 VALUE ZERO. UPDATE 件数 */
private long cunDbUpd;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001).(宣言のみ。実際の EOF 判定は 88 WRK-EOF-Y を使用) */
private String wrkEof = "";
/** 03 WS-PAD-CHAR PIC X(001) VALUE SPACE. */
private String wsPadChar = " ";
/** 03 WS-FILE-STATUS PIC X(002). 88 WRK-EOF-Y VALUE '1'. */
private String wsFileStatus = "";
/** 88 WRK-EOF-Y VALUE '1'.SET WRK-EOF-Y TO TRUE → wsFileStatus='1' */
private boolean isWrkEofY() {
return "1".equals(wsFileStatus);
}
/** 現在読込中の R02 物理行(FD R02INNFIL 01 R02INNREC の生バッファ) */
private String r02Line = "";
// ==================== DB2 ホスト変数 DBVARA ====================
/** 03 DBV-EMP-ID PIC X(008). */
private String dbvEmpId = "";
/** 03 DBV-EMP-NAME PIC X(040). */
private String dbvEmpName = "";
/** 03 DBV-DEPT-CODE PIC X(002). */
private String dbvDeptCode = "";
/** 03 DBV-REGION-CODE PIC X(002). */
private String dbvRegionCode = "";
/** 03 DBV-CATEGORY-CODE PIC X(003). */
private String dbvCategoryCode = "";
/** 03 DBV-BASE-SALARY PIC 9(009). */
private int dbvBaseSalary;
/** 03 DBV-HOURLY-RATE PIC 9(007). */
private int dbvHourlyRate;
/** 03 DBV-DEPENDENT-COUNT PIC 9(002). */
private int dbvDependentCount;
/** 03 DBV-STATUS PIC X(001). */
private String dbvStatus = "";
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** 01 R02INNREC.FD R02INNFIL / COPY KYU01REC REPLACING ==(A)== BY ==R02== */
private final Kyu01Rec r02 = new Kyu01Rec();
/** 01 W03OUTREC.FD W03OUTFIL / COPY KYU99REC REPLACING ==(A)== BY ==W03== */
private final Kyu99Rec w03 = new Kyu99Rec();
// ==================== ファイルハンドル / DB 接続 ====================
/** R02INNFIL 読取ハンドル(OPEN/CLOSE はフィールド保持、finally でクローズ) */
private BufferedReader r02InnFil;
/** W03OUTFIL 書込ハンドル(同上) */
private PrintWriter w03OutFil;
/** DB2 接続(EXEC SQL CONNECT TO … END-EXEC 相当) */
private Connection conn;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
new Kyu02RegMain().run();
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL WRK-EOF-Y → PERFORM 3000STPSOR → GOBACK.
* DbSample 標準例に倣い、正常時は commit、SQLException 時は rollback。
*/
private void run() {
try {
initPara();
while (!isWrkEofY()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF-Y
majPara();
}
if (conn != null) {
// EXEC SQL COMMIT END-EXEC(正常終了時コミット:DbSample パターン)
conn.commit();
System.out.println("[db] NORMAL END (COMMIT)");
}
termPara();
} catch (SQLException e) {
// EXEC SQL WHENEVER SQLERROR GO TO ERROR-PROC 相当(接続/コミット等の致命エラー)
System.out.println("[db] SQLERROR code=" + e.getErrorCode() + " msg=" + e.getMessage());
rollbackQuiet(conn);
} finally {
closeFiles();
closeQuiet(conn);
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→OPEN→DB接続→初回読込。
*/
private void initPara() throws SQLException {
// *** 開始メッセージ出力(INITIALIZE M00MHOPAR / MOVE CNS-MSGSTR TO M00MSGCOD
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力(FUNCTION WHEN-COMPILED → 起動時刻で代用)
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
wrkEof = "";
wsPadChar = " ";
wsFileStatus = "";
cunR02Inn = 0;
cunW03Out = 0;
cunDbIns = 0;
cunDbUpd = 0;
r02Line = "";
// *** OPEN INPUT R02INNFIL / OUTPUT W03OUTFIL.
openFiles();
// *** DB接続 EXEC SQL CONNECT TO 'data/SALARY.db' END-EXEC
// (作業指示により DB2 URL を使用。ユーザ/パスはプレースホルダ)
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
conn.setAutoCommit(false); // バッチ標準:明示的トランザクション制御
// *** 初回 READ1100R02INNSOR
readR02Para();
}
/**
* 段落 1100R02INNSORR02読込処理。
* READ AT ENDSET WRK-EOF-Y TO TRUE/ NOT AT ENDADD 1 TO CUN-R02INN)のマッピング。
*/
private void readR02Para() {
try {
final String line = r02InnFil.readLine();
if (line == null) {
// AT END: SET WRK-EOF-Y TO TRUE(→ wsFileStatus='1'
wsFileStatus = "1";
} else {
// NOT AT END: 物理行をバッファへ格納し ADD 1 TO CUN-R02INN.
r02Line = line;
cunR02Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* DB2ホスト変数へ MOVE → INSERT 試行 → SQLCODE 判定(-803 は UPDATE へ切替)。
* ※いずれの分岐も最後に次行 READ を実施(COBOL の PERFORM 1100R02INNSOR 相当)。
*/
private void majPara() {
// *** DB2ホスト変数にMOVER02INNREC(80B固定長) → DBVARA
parseR02Line(r02Line);
moveToDbv();
// *** INSERT試行(EXEC SQL INSERT INTO EMP-MASTER ... VALUES ... :ホスト変数)
try {
final int insCnt = insertEmp(); // SQLCODE=0 → 1行挿入
if (insCnt >= 1) {
// IF SQLCODE = 0 → ADD 1 TO CUN-DB-INS
cunDbIns++;
}
} catch (SQLException e) {
if (isDuplicateKey(e)) {
// *** -803 = 重複 → UPDATE
try {
final int updCnt = updateEmp();
if (updCnt >= 1) {
// UPDATE 成功 → ADD 1 TO CUN-DB-UPD
cunDbUpd++;
} else {
// UPDATE だが 0 件(想定外)→ ERROR-LOG
errorOutPara();
}
} catch (SQLException e2) {
// UPDATE 異常 → ERROR-LOG
errorOutPara();
}
} else {
// *** その他 SQL エラー → ERROR-LOG
errorOutPara();
}
}
// *** 次行 READ(各分岐共通:PERFORM 1100R02INNSOR
readR02Para();
}
/**
* 段落 2100ERROUTSOR:エラー出力処理。
* MOVE 'DB-ERR' TO W03ERR-CATEGORY.MOVE DBV-EMP-ID TO W03ERR-DETAIL.WRITE W03OUTREC.ADD 1 TO CUN-W03OUT.
*/
private void errorOutPara() {
w03.reset();
w03.setErrCategory("DB-ERR"); // MOVE 'DB-ERR' TO W03ERR-CATEGORYPIC X(008)
w03.setErrDetail(dbvEmpId); // MOVE DBV-EMP-ID TO W03ERR-DETAILPIC X(192)
w03OutFil.println(w03.toRecordLine());
cunW03Out++;
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×4(入力/DB-INSERT/DB-UPDATE/ERROR-LOG)→終了メッセージ。
*/
private void termPara() {
closeFiles();
// *** 入力件数メッセージ(CNS-MSGIINKES=6 / 'KYU02R02' / CUN-R02INN
msgOutPara(CST_MSGIINKES, "KYU02R02", ConvUtil.padLeftZero(cunR02Inn, 9));
// *** INSERT 件数メッセージ(CNS-MSGOUTKES=7 / 'DB-INSERT' / CUN-DB-INS
msgOutPara(CST_MSGOUTKES, "DB-INSERT", ConvUtil.padLeftZero(cunDbIns, 9));
// *** UPDATE 件数メッセージ(CNS-MSGOUTKES=7 / 'DB-UPDATE' / CUN-DB-UPD
msgOutPara(CST_MSGOUTKES, "DB-UPDATE", ConvUtil.padLeftZero(cunDbUpd, 9));
// *** ERROR-LOG 件数メッセージ(CNS-MSGOUTKES=7 / 'KYU02W03' / CUN-W03OUT
msgOutPara(CST_MSGOUTKES, "KYU02W03", ConvUtil.padLeftZero(cunW03Out, 9));
// *** 終了メッセージ(CNS-MSGFIN=2
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
// *** MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1) / -04(1:1)(先頭1文字='2'、残りSPACE
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理(本程序からは CALL されていない死代码、忠实残留)。
* MOVE CNS-ABD999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== DB 操作補助(EXEC SQL INSERT/UPDATE 相当) ====================
/**
* EXEC SQL INSERT INTO EMP-MASTER ... :DBV-* 相当。
* @return 挿入件数(SQLCODE=0 で 1
*/
private int insertEmp() throws SQLException {
final String sql = "INSERT INTO \"EMP-MASTER\" "
+ "(\"EMP-ID\", \"EMP-NAME\", \"DEPT-CODE\", \"REGION-CODE\", \"CATEGORY-CODE\", "
+ " \"BASE-SALARY\", \"HOURLY-RATE\", \"DEPENDENT-COUNT\", \"STATUS\", \"UPDATED-AT\") "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
// MOVE 済の DBV ホスト変数 → setXxx(定位置プレースホルダ)
ps.setString(1, dbvEmpId); // :DBV-EMP-ID
ps.setString(2, dbvEmpName); // :DBV-EMP-NAME
ps.setString(3, dbvDeptCode); // :DBV-DEPT-CODE
ps.setString(4, dbvRegionCode); // :DBV-REGION-CODE
ps.setString(5, dbvCategoryCode); // :DBV-CATEGORY-CODE
ps.setLong(6, dbvBaseSalary); // :DBV-BASE-SALARY
ps.setLong(7, dbvHourlyRate); // :DBV-HOURLY-RATE
ps.setInt(8, dbvDependentCount); // :DBV-DEPENDENT-COUNT
ps.setString(9, dbvStatus); // :DBV-STATUS
return ps.executeUpdate();
}
}
/**
* EXEC SQL UPDATE EMP-MASTER SET ... WHERE EMP-ID = :DBV-EMP-ID 相当(-803 時)。
* @return 更新件数(成功で 1
*/
private int updateEmp() throws SQLException {
final String sql = "UPDATE \"EMP-MASTER\" SET "
+ "\"EMP-NAME\" = ?, \"DEPT-CODE\" = ?, \"REGION-CODE\" = ?, \"CATEGORY-CODE\" = ?, "
+ "\"BASE-SALARY\" = ?, \"HOURLY-RATE\" = ?, \"DEPENDENT-COUNT\" = ?, \"STATUS\" = ?, "
+ "\"UPDATED-AT\" = CURRENT_TIMESTAMP "
+ "WHERE \"EMP-ID\" = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, dbvEmpName); // :DBV-EMP-NAME
ps.setString(2, dbvDeptCode); // :DBV-DEPT-CODE
ps.setString(3, dbvRegionCode); // :DBV-REGION-CODE
ps.setString(4, dbvCategoryCode); // :DBV-CATEGORY-CODE
ps.setLong(5, dbvBaseSalary); // :DBV-BASE-SALARY
ps.setLong(6, dbvHourlyRate); // :DBV-HOURLY-RATE
ps.setInt(7, dbvDependentCount); // :DBV-DEPENDENT-COUNT
ps.setString(8, dbvStatus); // :DBV-STATUS
ps.setString(9, dbvEmpId); // :DBV-EMP-IDWHERE
return ps.executeUpdate();
}
}
// ==================== レコード編集補助 ====================
/**
* 80B 固定長 R02 物理行を R02INNRECCOPY KYU01REC)に分割格納。
* 位置は 05 層レイアウトに相当(empId 8 / empName 40 / dept 2 / region 2 /
* category 3 / baseSalary 9 / hourlyRate 7 / dependentCount 2 / status 1 / filler 6 = 80)。
*/
private void parseR02Line(final String line) {
final String l = (line == null) ? "" : line;
r02.setEmpId(ConvUtil.safeSub(l, 0, 8)); // R02EMP-ID (1:8)
r02.setEmpName(ConvUtil.safeSub(l, 8, 48)); // R02EMP-NAME (9:48)
r02.setDeptCode(ConvUtil.safeSub(l, 48, 50)); // R02DEPT-CODE (49:50)
r02.setRegionCode(ConvUtil.safeSub(l, 50, 52)); // R02REGION-CODE (51:52)
r02.setCategoryCode(ConvUtil.safeSub(l, 52, 55)); // R02CATEGORY-CODE (53:55)
r02.setBaseSalary(ConvUtil.parseIntSafe(ConvUtil.safeSub(l, 55, 64), 0)); // R02BASE-SALARY (56:64)
r02.setHourlyRate(ConvUtil.parseIntSafe(ConvUtil.safeSub(l, 64, 71), 0)); // R02HOURLY-RATE (65:71)
r02.setDependentCount(ConvUtil.parseIntSafe(ConvUtil.safeSub(l, 71, 73), 0)); // R02DEPENDENT-COUNT (72:73)
r02.setStatus(ConvUtil.safeSub(l, 73, 74)); // R02STATUS (74:74)
// filler (75:80) は名称項目なしのため省略
}
/** MOVE R02* → DBV*2000MAJSOR 先頭の 9 項目 MOVE 群相当)。 */
private void moveToDbv() {
dbvEmpId = r02.getEmpId();
dbvEmpName = r02.getEmpName();
dbvDeptCode = r02.getDeptCode();
dbvRegionCode = r02.getRegionCode();
dbvCategoryCode = r02.getCategoryCode();
dbvBaseSalary = r02.getBaseSalary();
dbvHourlyRate = r02.getHourlyRate();
dbvDependentCount = r02.getDependentCount();
dbvStatus = r02.getStatus();
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R02INNFIL / OUTPUT W03OUTFIL. */
private void openFiles() {
try {
r02InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(R02INN_FILE), StandardCharsets.UTF_8));
w03OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W03OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** ファイルハンドル closefinally から呼出)。 */
private void closeFiles() {
closeQuiet(r02InnFil);
closeQuiet(w03OutFil);
r02InnFil = null;
w03OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序も FILE STATUS 未管理)
}
}
/** EXEC SQL ROLLBACK END-EXEC(異常時)。 */
private static void rollbackQuiet(final Connection c) {
if (c != null) {
try {
c.rollback();
System.out.println("[db] ROLLBACK DONE");
} catch (SQLException e) {
System.out.println("[db] ROLLBACK FAILED: " + e.getMessage());
}
}
}
private static void closeQuiet(final Connection c) {
if (c != null) {
try {
c.close();
} catch (SQLException e) {
System.out.println("[db] CLOSE FAILED: " + e.getMessage());
}
}
}
/** DB2(-803) と SQLite(UNIQUE constraint / SQLState 23000) の両方で重複キーを判定。 */
private static boolean isDuplicateKey(final SQLException e) {
if (e.getErrorCode() == -803) {
return true;
}
if ("23000".equals(e.getSQLState())) {
return true;
}
final String m = e.getMessage();
return m != null && m.contains("UNIQUE constraint failed");
}
}
+387
View File
@@ -0,0 +1,387 @@
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.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import dto.Kyu02Rec;
import dto.Kyu99Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU03AGG 欠勤集約処理
* ============================================================
* PROGRAM-ID. KYU03AGG → 类 Kyu03AggMain(PGMパターン:キーブレイク集約、メイン)
*
* 使用ファイル(SELECT ASSIGN → 路径常量):
* R01INNFIL(KYU03R01 ABSENCE-SUMMARY FB80B) → R01INN_FILE(自前定義80B
* W01OUTFIL(KYU03W01 ABSENCE-AGGREGATE FB80B) → W01OUT_FILECOPY KYU02REC → dto.Kyu02Rec 再利用)
* W02OUTFIL(KYU03W02 ERROR-LOG VB200B) → W02OUT_FILECOPY KYU99REC → dto.Kyu99Rec
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 使用DB2テーブル:なし(DB操作なし)
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01Para()
* 2000MAJSOR→majPara() 2100WRITOUTSOR→writeOutPara() 3000STPSOR→termPara()
* 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 处理要点(詳細設計書_KYU03AGG 準拠):
* - JCL SORT済み欠勤データ(社員番号昇順)を読み、同一社員単位に
* 欠勤時間合計(WRK-TOTAL-HOURS)・欠勤回数(WRK-ABSENT-COUNT)を集約し
* 社員単位1件の集約レコードをW01へ出力する。
* - キーブレイク:WRK-PREV-EMP-ID と WRK-EMP-ID の比較により、
* 同一社員は加算継続、異社員出現時に前社員の集約結果を出力。
* - W02(ERROR-LOG)は現時点出力なし(将来拡張、COBOLも WRITE なし)。
*/
public class Kyu03AggMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU03AGG'. */
private static final String CST_PRGIDX = "KYU03AGG";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. (使用資源一覧/手順で未参照) */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. M00UMKDATS22-03/04 先頭1桁) */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. ABENDコード */
private static final int CST_ABD999 = 999;
/** 03 CNS-ONE PIC 9(002) VALUE 1. 初回/異社員時の欠勤回数初期値 */
private static final int CST_ONE = 1;
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KYU03R01.ABSENCE-SUMMARY FB80B 入力) */
private static final String R01INN_FILE = "data/KYU03R01.txt";
/** SELECT W01OUTFIL ASSIGN TO KYU03W01.ABSENCE-AGGREGATE FB80B 出力) */
private static final String W01OUT_FILE = "data/KYU03W01.txt";
/** SELECT W02OUTFIL ASSIGN TO KYU03W02.ERROR-LOG VB200B 出力、現時点出力なし) */
private static final String W02OUT_FILE = "data/KYU03W02.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-ABSENCE-REC 内 05 WRK-EMP-ID PIC X(008).READ ... INTO 受信) */
private String wrkEmpId = "";
/** 03 WRK-ABSENCE-REC 内 05 WRK-YEAR-MONTH PIC X(006). */
private String wrkYearMonth = "";
/** 03 WRK-ABSENCE-REC 内 05 WRK-ABSENT-HOURS PIC 9(004)V9(001). */
private BigDecimal wrkAbsentHours = BigDecimal.ZERO;
/** 03 WRK-PREV-EMP-ID PIC X(008). 前社員キー保持 */
private String wrkPrevEmpId = "";
/** 03 WRK-PREV-KEY-SET PIC X(001).88 WRK-PREV-Y VALUE '1' */
private String wrkPrevKeySet = "0";
/** 03 WRK-TOTAL-HOURS PIC 9(004)V9(001). 同一社員欠勤時間合計 */
private BigDecimal wrkTotalHours = BigDecimal.ZERO;
/** 03 WRK-ABSENT-COUNT PIC 9(002). 同一社員欠勤回数 */
private int wrkAbsentCount;
/** 03 WRK-EOF PIC X(001).(※88 WRK-EOF-Y は WS-FILE-STATUS 配下に定義される)未使用 */
private String wrkEof = "";
/** 03 WS-PAD-CHAR PIC X(001) VALUE SPACE.PADDING CHARACTER 指定用) */
private String wsPadChar = " ";
/** 03 WS-FILE-STATUS PIC X(002).88 WRK-EOF-Y VALUE '1' → SET 時に '1' となる) */
private String wsFileStatus = "0";
/** 88 WRK-PREV-Y VALUE '1'. */
private boolean isPrevY() {
return "1".equals(wrkPrevKeySet);
}
/** 88 WRK-EOF-Y VALUE '1'.WS-FILE-STATUS 配下の 88 */
private boolean isEofY() {
return "1".equals(wsFileStatus);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 出力レコード(FILE SECTION → DTO ====================
/** 01 W01OUTREC.FD W01OUTFIL / COPY KYU02REC REPLACING ==(A)== BY ==W01== → dto.Kyu02Rec を再利用) */
private final Kyu02Rec w01 = new Kyu02Rec();
/** 01 W02OUTREC.FD W02OUTFIL / COPY KYU99REC)※現時点出力なし */
private final Kyu99Rec w02 = new Kyu99Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kyu03AggMain(inputFile).run();
}
public Kyu03AggMain() {
this(R01INN_FILE);
}
public Kyu03AggMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR(初回READ) → PERFORM 2000MAJSOR UNTIL WRK-EOF-Y → PERFORM 3000STPSOR → GOBACK.
* ※初回READで EOF(空入力)の場合は 0 件処理とする(事前判定ループ)。
*/
private void run() {
try {
initPara();
while (!isEofY()) { // 初回READでEOF(空入力)なら 0 件
majPara();
}
termPara();
} finally {
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→OPEN→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
resetWrkara();
// *** 入出力ファイル OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL.
openFiles();
// *** 初回READ
readR01Para();
}
/** INITIALIZE WRKARA 相当:数値→0、文字→SPACE。 */
private void resetWrkara() {
wrkEmpId = "";
wrkYearMonth = "";
wrkAbsentHours = BigDecimal.ZERO;
wrkPrevEmpId = "";
wrkPrevKeySet = "0";
wrkTotalHours = BigDecimal.ZERO;
wrkAbsentCount = 0;
wrkEof = "";
wsPadChar = " ";
wsFileStatus = "0";
}
/**
* 段落 1100R01INNSORR01読込処理。
* READ R01INNFIL INTO WRK-ABSENCE-REC AT END SET WRK-EOF-Y TO TRUE
* / NOT AT END ADD 1 TO CUN-R01INN.
*/
private void readR01Para() {
try {
final String line = r01InnFil.readLine();
if (line == null) {
// AT END: SET WRK-EOF-Y TO TRUE → WS-FILE-STATUS = '1'
wsFileStatus = "1";
} else {
// NOT AT END: ADD 1 TO CUN-R01INN.(および READ INTO WRK-ABSENCE-REC の各項目格納)
cunR01Inn++;
// MOVE 位置指定で WRK-ABSENCE-REC 各項目へ(80B 固定長の位置割当)
wrkEmpId = ConvUtil.safeSub(line, 0, 8); // 01-08 EMP-ID
wrkYearMonth = ConvUtil.safeSub(line, 8, 14); // 09-14 YEAR-MONTH
final String ahStr = ConvUtil.safeSub(line, 14, 19); // 15-19 ABSENT-HOURS(9(4)V9(1)=5桁)
wrkAbsentHours = ConvUtil.toDecimal(ahStr, 1);
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理(キーブレイク集約)。
* 初回設定→同一社員は加算継続→異社員出現時に前社員集約出力、その後前レコード更新。
*/
private void majPara() {
if (isPrevY()) {
if (wrkPrevEmpId.equals(wrkEmpId)) {
// *** 同一社員 → 集約加算
// ADD 1 TO WRK-ABSENT-COUNT
wrkAbsentCount++;
// ADD WRK-ABSENT-HOURS TO WRK-TOTAL-HOURS
wrkTotalHours = wrkTotalHours.add(wrkAbsentHours);
} else {
// *** 異社員 → 前社員の集約出力
writeOutPara();
// MOVE WRK-EMP-ID TO WRK-PREV-EMP-ID
wrkPrevEmpId = wrkEmpId;
// INITIALIZE WRK-TOTAL-HOURS WRK-ABSENT-COUNT REPLACING NUMERIC DATA BY ZERO
wrkTotalHours = BigDecimal.ZERO;
wrkAbsentCount = 0;
// MOVE 1 TO WRK-ABSENT-COUNT
wrkAbsentCount = CST_ONE;
// MOVE WRK-ABSENT-HOURS TO WRK-TOTAL-HOURS
wrkTotalHours = wrkAbsentHours;
}
} else {
// *** 初回
// MOVE WRK-EMP-ID TO WRK-PREV-EMP-ID
wrkPrevEmpId = wrkEmpId;
// SET WRK-PREV-Y TO TRUE
wrkPrevKeySet = "1";
// MOVE CNS-ONE TO WRK-ABSENT-COUNT
wrkAbsentCount = CST_ONE;
// MOVE WRK-ABSENT-HOURS TO WRK-TOTAL-HOURS
wrkTotalHours = wrkAbsentHours;
}
// *** PERFORM 1100R01INNSOR(次レコード読込)
readR01Para();
}
/**
* 段落 2100WRITOUTSOR:集約レコード出力処理。
* INITIALIZE W01OUTREC → MOVE 各項目 → WRITE W01OUTREC → ADD 1 TO CUN-W01OUT.
*/
private void writeOutPara() {
// INITIALIZE W01OUTREC.
w01.reset();
// MOVE WRK-PREV-EMP-ID TO W01EMP-ID.
w01.setEmpId(wrkPrevEmpId);
// MOVE WRK-YEAR-MONTH TO W01YEAR-MONTH.
w01.setYearMonth(wrkYearMonth);
// MOVE WRK-TOTAL-HOURS TO W01TOTAL-HOURS.
w01.setTotalHours(wrkTotalHours);
// MOVE WRK-ABSENT-COUNT TO W01ABSENT-COUNT.
w01.setAbsentCount(wrkAbsentCount);
// WRITE W01OUTREC.ADD 1 TO CUN-W01OUT.
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/**
* 段落 3000STPSOR:終了処理。
* 最終社員出力→CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
// *** 最終社員の集約出力(CLOSE の前に出力すること)
if (isPrevY()) {
writeOutPara();
}
// CLOSE R01INNFIL W01OUTFIL W02OUTFIL.
closeFiles();
// *** 入出力件数メッセージ
msgOutPara(CST_MSGIINKES, "KYU03R01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGOUTKES, "KYU03W01", ConvUtil.padLeftZero(cunW01Out, 9));
msgOutPara(CST_MSGOUTKES, "KYU03W02", ConvUtil.padLeftZero(cunW02Out, 9));
// *** 終了メッセージ
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
* ※本プログラム手順からは呼出されない(COBOL定義のみ)が1:1再現のため残置。
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), StandardCharsets.UTF_8));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), StandardCharsets.UTF_8));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** CLOSE R01INNFIL W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
r01InnFil = null;
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+634
View File
@@ -0,0 +1,634 @@
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.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import dto.Kyu99Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* Sub01DatSub / Sub02MsgSub / Sub03EndSub は既定パッケージ(src/)のため import 不要。
* 同一パッケージ(既定)から単純名で参照可能。
* ============================================================
* 【COBOL→Java】KYU04CAL 給与計算処理
* ============================================================
* PROGRAM-ID. KYU04CAL → 类 Kyu04CalMainPGMパターン:1:N+キーブレイク)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R02INNFIL(KYU04R02 ABSENCE-AGGREGATE 入力 80B) → R02INN_FILECOPY KYU02REC → dto.Kyu02Rec
* R03INNFIL(KYU04R03 PAY-PARAM 入力 80B CSV) → R03INN_FILE(自前定義/UNSTRING
* W03OUTFIL(KYU04W03 SALARY-DETAIL 出力 240B) → W03OUT_FILECOPY KYU03REC → dto.Kyu03Rec
* W04OUTFIL(KYU04W04 ERROR-LOG 出力 200B VB) → W04OUT_FILECOPY KYU99REC → dto.Kyu99Rec 再利用)
* 使用DBEXEC SQL → JDBC):
* EMP-MASTERSELECT)/OVT-MONTHLYSELECT SUM
* 使用SUBCALL → 类#execute):
* SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R02INNSOR→readR02Para()
* 2000MAJSOR→majPara() 2100CALCSOR→calcPara() 2110WRITOUTSOR→writeOutPara()
* 2200ERROUTSOR→errorOutPara() 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara()
* 9999ABDSOR→abendPara()
*
* 处理要点(詳細設計書_KYU04CAL 準拠):
* - R02(ABSENCE-AGGREGATE)を全件読み、EMP-MASTER/OVT-MONTHLY を参照し給与計算。
* - 欠勤控除額→時間外手当→EVALUATE ALSO(勤怠区分×手当種別)→支給総額→SALARY-DETAIL出力。
* - 金額計算は PIC 9(009) の桁溢れを COBOL の ON SIZE ERROR 相当として 0 クリア。
* - ROUNDED は BigDecimal.setScale(0, RoundingMode.HALF_UP) で実現。
*/
public class Kyu04CalMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU04CAL'. */
private static final String CST_PRGIDX = "KYU04CAL";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. サブエラーメッセージ */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数メッセージ */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数メッセージ */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-OVERTIME-RATE PIC 9(002)V9(002) VALUE 01.25. */
private static final BigDecimal CST_OVERTIME_RATE = new BigDecimal("1.25");
/** 03 CNS-HOLIDAY-RATE PIC 9(001)V9(02) VALUE 1.35.(宣言のみ・本計算では未使用) */
@SuppressWarnings("unused")
private static final BigDecimal CST_HOLIDAY_RATE = new BigDecimal("1.35");
/** PIC 9(009) の最大値(ON SIZE ERROR 判定用)。 */
private static final BigDecimal MAX_PIC9_9 = BigDecimal.valueOf(999_999_999L);
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R02INNFIL ASSIGN TO KYU04R02.ABSENCE-AGGREGATE 入力) */
private static final String R02INN_FILE = "data/KYU04R02.txt";
/** SELECT R03INNFIL ASSIGN TO KYU04R03.PAY-PARAM 入力 CSV */
private static final String R03INN_FILE = "data/KYU04R03.txt";
/** SELECT W03OUTFIL ASSIGN TO KYU04W03.SALARY-DETAIL 出力) */
private static final String W03OUT_FILE = "data/KYU04W03.txt";
/** SELECT W04OUTFIL ASSIGN TO KYU04W04.ERROR-LOG 出力) */
private static final String W04OUT_FILE = "data/KYU04W04.txt";
// ==================== DB接続(EXEC SQL CONNECT 相当) ====================
/** CONNECT 先(DbSample に準拠:DB2 URL プレースホルダ。テスト時は -Dkyu.db.url で上書き)。 */
private static final String DB_URL = System.getProperty("kyu.db.url", "jdbc:db2://localhost:50000/TESTDB");
private static final String DB_USER = "db2inst1";
private static final String DB_PASS = "password";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R02INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR02Inn;
/** 03 CUN-W03OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW03Out;
/** 03 CUN-W04OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW04Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001). 読込フラグ(88 WRK-EOF-Y VALUE '1' */
private String wrkEof = "0";
/** 03 WRK-PARAM-BASE PIC X(080).PAY-PARAM 読込バッファ) */
private String wrkParamBase = "";
/** 03 WRK-YEAR-MONTH PIC X(006).(対象年月=設計に従い PAY-PARAM から設定) */
private String wrkYearMonth = "";
/** 03 WRK-EMP-ID PIC X(008). */
private String wrkEmpId = "";
/** 03 WRK-EMP-NAME PIC X(040). */
private String wrkEmpName = "";
/** 03 WRK-DEPT-CODE PIC X(002). */
private String wrkDeptCode = "";
/** 03 WRK-REGION-CODE PIC X(002).EMP-MASTER より取得・FYI用未使用) */
private String wrkRegionCode = "";
/** 03 WRK-CATEGORY-CODE PIC X(003).EMP-MASTER より取得・FYI用未使用) */
private String wrkCategoryCode = "";
/** 03 WRK-BASE-SALARY PIC 9(009). */
private long wrkBaseSalary;
/** 03 WRK-HOURLY-RATE PIC 9(007). */
private long wrkHourlyRate;
/** 03 WRK-DEPENDENT-COUNT PIC 9(002). */
private int wrkDependentCount;
/** 03 WRK-ABSENT-HOURS PIC 9(004)V9(001). */
private BigDecimal wrkAbsentHours = BigDecimal.ZERO;
/** 03 WRK-OVT-HOURS PIC 9(004)V9(001).(宣言のみ・計算は DBV-OVT-HOURS を使用) */
private BigDecimal wrkOvtHours = BigDecimal.ZERO;
/** 03 WRK-ABSENT-DEDUCT PIC 9(009). */
private long wrkAbsentDeduct;
/** 03 WRK-OVT-AMOUNT PIC 9(009). */
private long wrkOvtAmount;
/** 03 WRK-GROSS-PAYMENT PIC 9(009). */
private long wrkGrossPayment;
/** 03 WRK-ALLOWANCE-TYPE PIC X(003). */
private String wrkAllowanceType = "";
/** 03 WRK-ATTEND-TYPE PIC X(001). */
private String wrkAttendType = "";
/** 03 WRK-AMOUNT PIC 9(009).EVALUATE ALSO 算出・出力非使用) */
private long wrkAmount;
/** 03 WS-FILE-STATUS PIC X(002).(宣言のみ・FILE STATUS 未管理) */
@SuppressWarnings("unused")
private String wsFileStatus = "";
/** 03 WS-PAD-CHAR PIC X(001).(宣言のみ) */
@SuppressWarnings("unused")
private String wsPadChar = " ";
/** 88 WRK-EOF-Y VALUE '1'. */
private boolean isEof() {
return "1".equals(wrkEof);
}
// ==================== DB2ホスト変数領域(DBVARA → 本クラスフィールド) ====================
/** 03 DBV-EMP-ID PIC X(008). */
private String dbvEmpId = "";
/** 03 DBV-BASE-SALARY PIC 9(009). */
private long dbvBaseSalary;
/** 03 DBV-HOURLY-RATE PIC 9(007). */
private long dbvHourlyRate;
/** 03 DBV-DEPENDENT-COUNT PIC 9(002). */
private int dbvDependentCount;
/** 03 DBV-EMPLOYEE-NAME PIC X(040). */
private String dbvEmployeeName = "";
/** 03 DBV-DEPT-CODE PIC X(002). */
private String dbvDeptCode = "";
/** 03 DBV-OVT-HOURS PIC 9(004)V9(001).(小数点以下1桁) */
private BigDecimal dbvOvtHours = BigDecimal.ZERO;
/** 03 DBV-STATUS PIC X(001). */
private String dbvStatus = "";
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
/** COPY ZANDATAC.SUB01DAT用) */
private final ZandatacPar d01UbsPar = new ZandatacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** 01 R02INNREC.FD R02INNFIL / COPY KYU02REC)— 固定長行を直接解析(DTO非依存) */
private String r02EmpId = "";
private String r02YearMonth = "";
private BigDecimal r02TotalHours = BigDecimal.ZERO;
private int r02AbsentCount;
/** 01 W04OUTREC.FD W04OUTFIL / COPY KYU99REC 再利用) */
private final Kyu99Rec w04 = new Kyu99Rec();
// ==================== ファイルハンドル / DB接続(OPEN/CLOSE 相当) ====================
private BufferedReader r02InnFil;
private PrintWriter w03OutFil;
private PrintWriter w04OutFil;
private Connection conn;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
new Kyu04CalMain().run();
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!isEof()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF = '1'
majPara();
}
termPara();
} finally {
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始→コンパイル日時→ワーク初期化→運用日付取得(SUB01DAT)→
* PAY-PARAM読込/解析→R02/W03/W04 OPEN→DB接続→初回R02読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
final String compiled = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
initWrk();
// *** 運用日付取得 SUB01DAT
d01UbsPar.reset();
Sub01DatSub.execute(d01UbsPar);
if (d01UbsPar.getD01FkiCod() != 0) {
// 復帰コード≠ZERO → サブエラーメッセージ+ABEND
msgOutPara(CST_MSGSUBEEK, "SUB01DAT", String.valueOf(d01UbsPar.getD01FkiCod()));
abendPara();
}
// *** PAY-PARAM読込(OPEN INPUT R03INNFIL / READ / CLOSE
readParam();
// *** パラメータ解析(UNSTRING WRK-PARAM-BASE DELIMITED BY ',' INTO WRK-YEAR-MONTH
// 設計準拠:対象年月は PAY-PARAM の第1フィールドを使用(COBOL は R02 で上書きしているが設計優先)。
final String[] tokens = wrkParamBase.split(",", -1);
if (tokens.length > 0) {
wrkYearMonth = ConvUtil.padRight(tokens[0].trim(), 6);
}
// *** 使用ファイル OPENR02 INPUT / W03,W04 OUTPUT
openFiles();
// *** DB接続(EXEC SQL CONNECT TO ...
connectDb();
// *** 初回R02読込(1100R02INNSOR
readR02Para();
}
/** INITIALIZE WRKARA 相当(数値→0、文字→SPACE)。 */
private void initWrk() {
wrkEof = "0";
wrkParamBase = "";
wrkYearMonth = "";
wrkEmpId = "";
wrkEmpName = "";
wrkDeptCode = "";
wrkRegionCode = "";
wrkCategoryCode = "";
wrkBaseSalary = 0;
wrkHourlyRate = 0;
wrkDependentCount = 0;
wrkAbsentHours = BigDecimal.ZERO;
wrkOvtHours = BigDecimal.ZERO;
wrkAbsentDeduct = 0;
wrkOvtAmount = 0;
wrkGrossPayment = 0;
wrkAllowanceType = "";
wrkAttendType = "";
wrkAmount = 0;
wsFileStatus = "";
wsPadChar = " ";
}
/**
* 段落 1100R02INNSORR02読込処理。
* READ R02INNFIL AT END → SET WRK-EOF-Y / NOT AT END → ADD 1 TO CUN-R02INN.
*/
private void readR02Para() {
try {
final String line = r02InnFil.readLine();
if (line == null) {
// AT END: SET WRK-EOF-Y TO TRUE.
wrkEof = "1";
} else {
// NOT AT END: 固定長行を 05 層定義の位置で切出し(COPY KYU02REC 準拠)
// EMP-ID(1:8)/YEAR-MONTH(9:14)/TOTAL-HOURS(15:19)=9(4)V9(1)/ABSENT-COUNT(20:21)
r02EmpId = ConvUtil.padRight(ConvUtil.safeSub(line, 0, 8), 8);
r02YearMonth = ConvUtil.padRight(ConvUtil.safeSub(line, 8, 14), 6);
r02TotalHours = ConvUtil.toDecimal(ConvUtil.safeSub(line, 14, 19), 1);
r02AbsentCount = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 19, 21), 0);
cunR02Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* ホスト変数セット→EMP-MASTER参照(SQLCODE≠0ならエラー出力+次件)→
* OVT-MONTHLY参照→給与計算→SALARY-DETAIL出力→次件読込。
*/
private void majPara() {
// *** ホスト変数セット MOVE R02EMP-ID TO DBV-EMP-ID.
dbvEmpId = r02EmpId;
// *** DB2 EMP-MASTER参照(EXEC SQL SELECT ... INTO :DBV-*
boolean empOk;
try {
empOk = selectEmpMaster();
} catch (SQLException e) {
// SQLCODE≠0 に相当
empOk = false;
}
if (!empOk) {
// SQLCODE NOT = 0 → 2200ERROUTSOR 次件読込 EXIT SECTION
errorOutPara();
readR02Para();
return;
}
// *** MOVE EMP-MASTER項目 → WRK-*(出力用)
wrkEmpName = dbvEmployeeName;
wrkBaseSalary = dbvBaseSalary;
wrkHourlyRate = dbvHourlyRate;
wrkDeptCode = dbvDeptCode;
// MOVE R02YEAR-MONTH TO WRK-YEAR-MONTH. ※設計優先で PAY-PARAM の値を維持(上書きしない)
wrkAbsentHours = r02TotalHours;
// *** DB2 OVT-MONTHLY参照(EXEC SQL SELECT SUM(OVT-HOURS)
selectOvtMonthly();
// *** 給与計算(2100CALCSOR
calcPara();
// *** SALARY-DETAIL出力(2110WRITOUTSOR
writeOutPara();
// *** 次件読込(1100R02INNSOR
readR02Para();
}
/**
* EXEC SQLEMP-MASTER 参照(SELECT ... INTO :DBV-*)。
* @return 正常に取得できた場合 true(SQLCODE=0 かつ該当行あり)、
* 該当行なし/異常時 falseSQLCODE≠0 に相当)
*/
private boolean selectEmpMaster() throws SQLException {
final String sql = "SELECT \"EMP-NAME\", \"DEPT-CODE\", \"REGION-CODE\", \"CATEGORY-CODE\", "
+ "\"BASE-SALARY\", \"HOURLY-RATE\", \"DEPENDENT-COUNT\", \"STATUS\" "
+ "FROM \"EMP-MASTER\" WHERE \"EMP-ID\" = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, dbvEmpId);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) {
return false; // SQLCODE=100(該当なし)→ エラー扱い
}
String name = rs.getString("EMP-NAME");
dbvEmployeeName = (name == null) ? "" : name;
String dept = rs.getString("DEPT-CODE");
dbvDeptCode = (dept == null) ? "" : dept;
String region = rs.getString("REGION-CODE");
wrkRegionCode = (region == null) ? "" : region; // :WRK-REGION-CODE
String cat = rs.getString("CATEGORY-CODE");
wrkCategoryCode = (cat == null) ? "" : cat; // :WRK-CATEGORY-CODE
dbvBaseSalary = rs.getLong("BASE-SALARY"); // :DBV-BASE-SALARY
dbvHourlyRate = rs.getLong("HOURLY-RATE"); // :DBV-HOURLY-RATE
dbvDependentCount = rs.getInt("DEPENDENT-COUNT"); // :DBV-DEPENDENT-COUNT
String status = rs.getString("STATUS");
dbvStatus = (status == null) ? "" : status; // :DBV-STATUS
return true;
}
}
}
/**
* EXEC SQLOVT-MONTHLY 参照(SELECT SUM(OVT-HOURS) ... INTO :DBV-OVT-HOURS)。
* SQLCODE≠0 の場合は ZERO を設定(COBOL: MOVE ZERO TO DBV-OVT-HOURS)。
*/
private void selectOvtMonthly() {
BigDecimal ovt = BigDecimal.ZERO;
try {
final String sql = "SELECT SUM(\"OVT-HOURS\") FROM \"OVT-MONTHLY\" "
+ "WHERE \"EMP-ID\" = ? AND \"YEAR-MONTH\" = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, dbvEmpId);
ps.setString(2, wrkYearMonth);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
final BigDecimal s = rs.getBigDecimal(1);
if (s != null) {
ovt = s; // SUM 結果(小数点以下1桁想定)
}
}
}
}
} catch (SQLException e) {
ovt = BigDecimal.ZERO; // SQLCODE≠0 → ZERO
}
dbvOvtHours = ovt;
}
/**
* 段落 2100CALCSOR:給与計算処理。
* 欠勤控除額→時間外手当→EVALUATE ALSO(勤怠区分×手当種別)→支給総額。
*/
private void calcPara() {
// *** 欠勤控除額 COMPUTE WRK-ABSENT-DEDUCT ROUNDED = HOURLY-RATE * ABSENT-HOURS ON SIZE ERROR ZERO
final BigDecimal absentVal = new BigDecimal(wrkHourlyRate).multiply(wrkAbsentHours);
wrkAbsentDeduct = roundedP9(absentVal);
// *** 時間外手当 COMPUTE WRK-OVT-AMOUNT ROUNDED = HOURLY-RATE * OVT-HOURS * 1.25 ON SIZE ERROR ZERO
final BigDecimal ovtVal = new BigDecimal(wrkHourlyRate)
.multiply(dbvOvtHours)
.multiply(CST_OVERTIME_RATE);
wrkOvtAmount = roundedP9(ovtVal);
// *** EVALUATE ALSO(勤怠区分×手当種別)
// MOVE 'OVT' TO WRK-ALLOWANCE-TYPE.MOVE 'W' TO WRK-ATTEND-TYPE.
wrkAllowanceType = "OVT";
wrkAttendType = "W";
final BigDecimal hr = new BigDecimal(wrkHourlyRate);
final BigDecimal ovt = dbvOvtHours;
final BigDecimal amt;
if ("OVT".equals(wrkAllowanceType)) {
if ("W".equals(wrkAttendType)) {
// WHEN 'OVT' ALSO 'W' → HOURLY-RATE * OVT-HOURS * 1.25
amt = hr.multiply(ovt).multiply(new BigDecimal("1.25"));
} else if ("H".equals(wrkAttendType)) {
// WHEN 'OVT' ALSO 'H' → * 1.35
amt = hr.multiply(ovt).multiply(new BigDecimal("1.35"));
} else {
amt = BigDecimal.ZERO;
}
} else if ("LATE".equals(wrkAllowanceType)) {
// WHEN 'LATE' ALSO ANY → * 0.50
amt = hr.multiply(ovt).multiply(new BigDecimal("0.50"));
} else {
// WHEN OTHER → ZERO
amt = BigDecimal.ZERO;
}
wrkAmount = roundedP9(amt);
// *** 支給総額 COMPUTE WRK-GROSS-PAYMENT = BASE-SALARY + OVT-AMOUNT - ABSENT-DEDUCT
wrkGrossPayment = wrkBaseSalary + wrkOvtAmount - wrkAbsentDeduct;
}
/**
* 段落 2110WRITOUTSORSALARY-DETAIL出力処理。
* MOVE 各項目→W03OUTREC240B 固定長)/ WRITE ADD 1 TO CUN-W03OUT.
* ※W03 レコードは COPY KYU03REC のレイアウトに従いインライン構築(DTO非依存)。
* 配置: EMP-ID(8)+EMP-NAME(40)+DEPT-CODE(2)+BASE-SALARY(9)+OVT-AMOUNT(9)
* +HOLIDAY-AMOUNT(9)+ABSENT-DEDUCT(9)+GROSS-PAYMENT(9)+OVT-HOURS(5)+ABSENT-HOURS(5)
* +ATTEND-TYPE(1)+ALLOWANCE-TYPE(3)+FILLER(131) = 240B
*/
private void writeOutPara() {
final String line = ConvUtil.padRight(r02EmpId, 8) // MOVE R02EMP-ID
+ ConvUtil.padRight(dbvEmployeeName, 40) // MOVE WRK-EMP-NAME
+ ConvUtil.padRight(dbvDeptCode, 2) // MOVE WRK-DEPT-CODE
+ ConvUtil.padLeftZero(dbvBaseSalary, 9) // MOVE DBV-BASE-SALARY
+ ConvUtil.padLeftZero(wrkOvtAmount, 9) // MOVE WRK-OVT-AMOUNT
+ ConvUtil.padLeftZero(0L, 9) // MOVE ZERO(固定)
+ ConvUtil.padLeftZero(wrkAbsentDeduct, 9) // MOVE WRK-ABSENT-DEDUCT
+ ConvUtil.padLeftZero(wrkGrossPayment, 9) // MOVE WRK-GROSS-PAYMENT
+ ConvUtil.formatZoned(dbvOvtHours, 4, 1) // MOVE DBV-OVT-HOURS
+ ConvUtil.formatZoned(r02TotalHours, 4, 1) // MOVE WRK-ABSENT-HOURSR02TOTAL-HOURS
+ ConvUtil.padRight(wrkAttendType, 1) // MOVE WRK-ATTEND-TYPE
+ ConvUtil.padRight(wrkAllowanceType, 3) // MOVE WRK-ALLOWANCE-TYPE
+ ConvUtil.padRight("", 131); // FILLER X(131)
w03OutFil.println(line);
cunW03Out++;
}
/**
* 段落 2200ERROUTSOR:エラー出力処理。
* MOVE 'DB-ERR' TO W04ERR-CATEGORY.MOVE R02EMP-ID TO W04ERR-DETAIL.WRITE ADD 1 TO CUN-W04OUT.
*/
private void errorOutPara() {
w04.reset();
w04.setErrCategory("DB-ERR");
w04.setErrDetail(r02EmpId);
w04OutFil.println(w04.toRecordLine());
cunW04Out++;
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
closeFiles();
msgOutPara(CST_MSGIINKES, "KYU04R02", ConvUtil.padLeftZero(cunR02Inn, 9));
msgOutPara(CST_MSGOUTKES, "KYU04W03", ConvUtil.padLeftZero(cunW03Out, 9));
msgOutPara(CST_MSGOUTKES, "KYU04W04", ConvUtil.padLeftZero(cunW04Out, 9));
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.System.exit で異常終了)
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R02INNFIL / OUTPUT W03OUTFIL W04OUTFIL. */
private void openFiles() {
try {
r02InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(R02INN_FILE), StandardCharsets.UTF_8));
w03OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W03OUT_FILE), StandardCharsets.UTF_8));
w04OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W04OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** CLOSE R02INNFIL W03OUTFIL W04OUTFILidempotent)。 */
private void closeFiles() {
closeQuiet(r02InnFil);
closeQuiet(w03OutFil);
closeQuiet(w04OutFil);
r02InnFil = null;
w03OutFil = null;
w04OutFil = null;
closeQuiet(conn);
conn = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
} else if (handle instanceof Connection) {
((Connection) handle).close();
}
} catch (Exception e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
/** PAY-PARAM(R03) 読込:OPEN INPUT / READ INTO WRK-PARAM-BASE / CLOSE. */
private void readParam() {
try (BufferedReader r03 = new BufferedReader(
new InputStreamReader(new FileInputStream(R03INN_FILE), StandardCharsets.UTF_8))) {
final String line = r03.readLine();
wrkParamBase = (line == null) ? "" : line;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/** EXEC SQL CONNECT TO ...DbSample 準拠:DriverManager + setAutoCommit(false))。 */
private void connectDb() {
try {
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
conn.setAutoCommit(false); // バッチ標準:明示的トランザクション制御
} catch (SQLException e) {
// 接続失敗は ABEND 相当(メッセージ出力せずそのまま異常終了)
throw new UncheckedIOException(new IOException("DB CONNECT FAILED: " + e.getMessage()));
}
}
// ==================== 数値計算補助 ====================
/**
* COMPUTE ... ROUNDEDPIC 9(009))相当。
* 四捨五入(HALF_UP)の上、桁溢れ(ON SIZE ERROR)なら 0 を返す。
*/
private static long roundedP9(final BigDecimal v) {
final BigDecimal r = v.setScale(0, RoundingMode.HALF_UP);
if (r.compareTo(MAX_PIC9_9) > 0) {
return 0L; // ON SIZE ERROR → MOVE ZERO
}
return r.longValue();
}
}
+561
View File
@@ -0,0 +1,561 @@
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.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import dto.Kyu03Rec;
import dto.Kyu04Rec;
import dto.Kyu99Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU05DED 控除計算処理
* ============================================================
* PROGRAM-ID. KYU05DED → 类 Kyu05DedMainPGMパターン:SELECT条件+DB参照)
*
* 使用ファイル(SELECT ASSIGN → パス定数):
* R01INNFIL(KYU05R01 入力 FB240B) → R01INN_FILECOPY KYU03REC → dto.Kyu03Rec
* W01OUTFIL(KYU05W01 出力 FB200B) → W01OUT_FILECOPY KYU04REC → dto.Kyu04Rec
* W02OUTFIL(KYU05W02 出力 VB200B) → W02OUT_FILECOPY KYU99REC → dto.Kyu99Rec 再利用)
* 使用DB2JDBC):
* TAX-TABLE SELECT ... WHERE :WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO
* INSURANCE-TABLEDECLARE CURSOR → FETCH全件 → WORKING-STORAGE(INSURANCE-WORK) → SEARCH
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100INSLOADSOR→insLoadPara()
* 1200R01INNSOR→readR01Para() 2000MAJSOR→majPara() 2100WRITOUTSOR→writeW01Para()
* 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 処理要点(詳細設計書_KYU05DED 準拠):
* - 課税所得=総支給-基礎控除(50000)。所得税=課税所得×税率-控除額(ROUNDED、負は0)。
* - 社会保険料=課税所得×(健康+厚生)比率(ROUNDED)。住民税=課税所得×0.10(ROUNDED)。
* - 差引支給額=総支給-所得税-保険料-住民税。
* - 金額計算は BigDecimal.setScale(0, HALF_UP) で COBOL ROUNDED を再現(转换基准 3.2)。
* - 原COBOLの CONNECT TO 'data/SALARY.db' は SQLite 指定だが、設計書は DB2 を規定。
* 转换基准「COBOLと式様書不一致は式様書優先」により DB2 URL を採用(報告書に注記)。
*/
public class Kyu05DedMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU05DED'. */
private static final String CST_PRGIDX = "KYU05DED";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005.(設計上予約、本処理では未使用) */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数メッセージ */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数メッセージ */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時メッセージ */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-TAX-BASIC-DEDUCTION PIC 9(006) VALUE 050000. 基礎控除 */
private static final long CST_TAX_BASIC_DEDUCTION = 50000L;
/** 03 CNS-RESIDENT-TAX-RATE PIC 9(001)V9(02) VALUE 0.10. 住民税率 */
private static final BigDecimal CST_RESIDENT_TAX_RATE = new BigDecimal("0.10");
/** 各SQL文の定数 */
private static final String INS_SELECT_SQL =
"SELECT \"INS-INCOME-FROM\", \"INS-INCOME-TO\", \"INS-HEALTH-RATE\", \"INS-PENSION-RATE\" "
+ "FROM \"INSURANCE-TABLE\" ORDER BY \"INS-INCOME-FROM\"";
private static final String TAX_SELECT_SQL =
"SELECT \"TAX-RATE\", \"DEDUCTION\" FROM \"TAX-TABLE\" WHERE ? BETWEEN \"TAX-FROM\" AND \"TAX-TO\"";
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KYU05R01.FB 入力 240B */
private static final String R01INN_FILE = "data/KYU05R01.txt";
/** SELECT W01OUTFIL ASSIGN TO KYU05W01.FB 出力 200B */
private static final String W01OUT_FILE = "data/KYU05W01.txt";
/** SELECT W02OUTFIL ASSIGN TO KYU05W02.VB 出力 200B */
private static final String W02OUT_FILE = "data/KYU05W02.txt";
// ==================== DB 接続(EXEC SQL CONNECT 相当) ====================
/** DB2 接続先(設計書準拠;原COBOLの 'data/SALARY.db' は SQLite 指定のため非採用。テスト時は -Dkyu.db.url で上書き)。 */
private static final String DB_URL = System.getProperty("kyu.db.url", "jdbc:db2://localhost:50000/TESTDB");
private static final String DB_USER = "db2inst1";
private static final String DB_PASS = "password";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001). 読込フラグ(88 WRK-EOF-Y VALUE '1' */
private String wrkEof = "0";
/** 03 WRK-GROSS-PAYMENT PIC 9(009). */
private long wrkGrossPayment;
/** 03 WRK-TAXABLE-INCOME PIC 9(009). */
private long wrkTaxableIncome;
/** 03 WRK-INCOME-TAX PIC 9(009). */
private long wrkIncomeTax;
/** 03 WRK-INSURANCE PIC 9(009). */
private long wrkInsurance;
/** 03 WRK-RESIDENT-TAX PIC 9(009). */
private long wrkResidentTax;
/** 03 WRK-NET-PAYMENT PIC 9(009). */
private long wrkNetPayment;
/** 88 WRK-EOF-Y VALUE '1'. */
private boolean isEof() {
return "1".equals(wrkEof);
}
// ==================== DB2 ホスト変数 DBVARA ====================
/** 03 DBV-EMP-ID PIC X(008).R01EMP-ID 設定、SQLでは未直接使用) */
private String dbvEmpId = "";
/** 03 DBV-TAX-RATE PIC 9(001)V9(04).TAX-TABLE.TAX-RATE DECIMAL(5,4) */
private BigDecimal dbvTaxRate = BigDecimal.ZERO;
/** 03 DBV-TAX-DEDUCTION PIC 9(009).TAX-TABLE.DEDUCTION DECIMAL(9,0) */
private long dbvTaxDeduction;
// ==================== 保険料率 WORKING-STORAGESEARCH 相当) ====================
/** 03 IW-COUNT PIC 9(004).OCCURS DEPENDING ON */
private int iwCount;
/** 最大 200 件(OCCURS 1 TO 200)。 */
private static final int IW_MAX = 200;
/** 05 IW-INCOME-FROM PIC 9(009). */
private final long[] iwIncomeFrom = new long[IW_MAX];
/** 05 IW-INCOME-TO PIC 9(009). */
private final long[] iwIncomeTo = new long[IW_MAX];
/** 05 IW-HEALTH-RATE PIC V9(006).0.xxxxxx、DECIMAL(7,6) 相当) */
private final BigDecimal[] iwHealthRate = new BigDecimal[IW_MAX];
/** 05 IW-PENSION-RATE PIC V9(006). */
private final BigDecimal[] iwPensionRate = new BigDecimal[IW_MAX];
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== レコード(FILE SECTION → DTO ====================
/** 01 R01INNREC.FD R01INNFIL / COPY KYU03REC */
private final Kyu03Rec r01 = new Kyu03Rec();
/** 01 W01OUTREC.FD W01OUTFIL / COPY KYU04REC */
private final Kyu04Rec w01 = new Kyu04Rec();
/** 01 W02OUTREC.FD W02OUTFIL / COPY KYU99REC 再利用) */
private final Kyu99Rec w02 = new Kyu99Rec();
// ==================== ファイルハンドル / DB接続(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
private Connection conn;
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kyu05DedMain(inputFile).run();
}
public Kyu05DedMain() {
this(R01INN_FILE);
}
public Kyu05DedMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!isEof()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF = '1'
majPara();
}
termPara();
// EXEC SQL COMMIT END-EXEC(正常終了時)
if (conn != null) {
conn.commit();
}
} catch (SQLException e) {
// EXEC SQL WHENEVER SQLERROR 相当(DbSample パターン)
System.out.println("[db] SQLERROR code=" + e.getErrorCode() + " msg=" + e.getMessage());
rollbackQuiet(conn);
abendPara(); // MOVE 999 TO E01ABDCOD / CALL SUB03ENDSystem.exit
} finally {
closeFiles();
closeQuiet(conn);
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→DB接続→INSURANCE-TABLE LOAD→OPEN→初回READ.
*/
private void initPara() throws SQLException {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
cunR01Inn = 0;
cunW01Out = 0;
cunW02Out = 0;
wrkEof = "0";
wrkGrossPayment = 0;
wrkTaxableIncome = 0;
wrkIncomeTax = 0;
wrkInsurance = 0;
wrkResidentTax = 0;
wrkNetPayment = 0;
dbvEmpId = "";
dbvTaxRate = BigDecimal.ZERO;
dbvTaxDeduction = 0;
iwCount = 0;
// *** DB接続 EXEC SQL CONNECT TO 'data/SALARY.db'(設計書準拠で DB2 URL を使用)
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
conn.setAutoCommit(false); // 批処理標準(DbSample パターン)
// *** 使用ファイル OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL.
openFiles();
// *** INSURANCE-TABLE 全件 LOAD1100INSLOADSOR
insLoadPara();
// *** 初回 READ1200R01INNSOR
readR01Para();
}
/**
* 段落 1100INSLOADSORINSURANCE-TABLE 全件 LOAD 処理。
* DECLARE/OPEN CURSOR → FETCH ループ → INSURANCE-WORK 配列へ格納 → CLOSE CURSOR.
* OPEN/FETCH 異常(SQLCODE NOT = 0)時は W02 に詳細を書き ABEND.
*/
private void insLoadPara() {
try {
// EXEC SQL DECLARE INS-CURSOR CURSOR FOR ... / OPEN は executeQuery で暗黙実行
try (PreparedStatement ps = conn.prepareStatement(INS_SELECT_SQL);
ResultSet rs = ps.executeQuery()) { // OPEN INS-CURSOR
// PERFORM UNTIL SQLCODE NOT = 0rs.next() == false で終了)
while (rs.next()) {
iwCount++; // ADD 1 TO IW-COUNT
final int idx = iwCount - 1; // 1-based → 0-based
// MOVE DBV-INS-* TO IW-*OW 配列へ格納)
iwIncomeFrom[idx] = rs.getLong("INS-INCOME-FROM");
iwIncomeTo[idx] = rs.getLong("INS-INCOME-TO");
// DECIMAL(7,6) → scale 6PIC V9(006) 相当)
iwHealthRate[idx] = rs.getBigDecimal("INS-HEALTH-RATE");
iwPensionRate[idx] = rs.getBigDecimal("INS-PENSION-RATE");
}
} // CLOSE INS-CURSOR(自動)
} catch (SQLException e) {
// IF SQLCODE NOT = 0 → MOVE 'INS-CURSOR OPEN ERR' TO W02ERR-DETAIL / PERFORM 9999ABDSOR
w02.reset();
w02.setErrDetail("INS-CURSOR OPEN ERR");
writeW02();
abendPara();
}
}
/**
* 段落 1200R01INNSORR01 読込処理。
* READ AT END → SET WRK-EOF-Y / NOT AT END → ADD 1 TO CUN-R01INN.
*/
private void readR01Para() {
try {
final String line = r01InnFil.readLine();
if (line == null) {
// AT END: SET WRK-EOF-Y TO TRUE.
wrkEof = "1";
} else {
// NOT AT END: MOVE R01INNREC … / ADD 1 TO CUN-R01INN.
r01.setRecordLine(line);
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* 課税所得→所得税(SELECT BETWEEN)→社会保険料(SEARCH)→住民税→差引支給額→W01出力→次READ.
*/
private void majPara() {
// MOVE R01GROSS-PAYMENT TO WRK-GROSS-PAYMENT.
wrkGrossPayment = r01.getGrossPayment();
// MOVE R01EMP-ID TO DBV-EMP-ID.
dbvEmpId = r01.getEmpId();
// *** 課税所得計算(IF WRK-GROSS-PAYMENT < CNS-TAX-BASIC-DEDUCTION …)
if (wrkGrossPayment < CST_TAX_BASIC_DEDUCTION) {
// MOVE ZERO TO WRK-TAXABLE-INCOME.
wrkTaxableIncome = 0;
} else {
// COMPUTE WRK-TAXABLE-INCOME = WRK-GROSS-PAYMENT - CNS-TAX-BASIC-DEDUCTION.
wrkTaxableIncome = wrkGrossPayment - CST_TAX_BASIC_DEDUCTION;
}
// *** TAX-TABLE 参照(SELECT ... BETWEEN)→ 所得税計算
taxSelectPara();
// *** 社会保険料計算(SEARCH IW-ENTRY
insSearchPara();
// *** 住民税計算 COMPUTE WRK-RESIDENT-TAX ROUNDED = 課税所得 × 0.10.
BigDecimal resident = BigDecimal.valueOf(wrkTaxableIncome)
.multiply(CST_RESIDENT_TAX_RATE)
.setScale(0, RoundingMode.HALF_UP);
wrkResidentTax = resident.longValue();
// *** 差引支給額 COMPUTE WRK-NET-PAYMENT = 総支給 - 所得税 - 保険料 - 住民税.
wrkNetPayment = wrkGrossPayment - wrkIncomeTax - wrkInsurance - wrkResidentTax;
// *** W01 出力(2100WRITOUTSOR
writeW01Para();
// *** 次レコード読込(1200R01INNSOR
readR01Para();
}
/**
* 段落 2000MAJSOR 内:DB2 TAX-TABLE 税率取得(EXEC SQL SELECT ... BETWEEN)。
* SQLCODE = 0 の場合のみ所得税を計算(負数は 0 に補正)、それ以外は 0.
*/
private void taxSelectPara() {
wrkIncomeTax = 0; // 既定(SQLCODE NOT = 0 / エラー時は 0
try (PreparedStatement ps = conn.prepareStatement(TAX_SELECT_SQL)) {
// WHERE :WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO
ps.setLong(1, wrkTaxableIncome);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { // IF SQLCODE = 0
// INTO :DBV-TAX-RATE, :DBV-TAX-DEDUCTION
dbvTaxRate = rs.getBigDecimal("TAX-RATE"); // DECIMAL(5,4)
dbvTaxDeduction = rs.getLong("DEDUCTION"); // DECIMAL(9,0)
// COMPUTE WRK-INCOME-TAX ROUNDED = 課税所得 × 税率 - 控除額.
BigDecimal incomeTax = BigDecimal.valueOf(wrkTaxableIncome)
.multiply(dbvTaxRate)
.subtract(BigDecimal.valueOf(dbvTaxDeduction))
.setScale(0, RoundingMode.HALF_UP);
long it = incomeTax.longValue();
if (it < 0) {
it = 0; // IF WRK-INCOME-TAX < ZERO → MOVE ZERO.
}
wrkIncomeTax = it;
}
// ELSESQLCODE NOT = 0)→ WRK-INCOME-TAX は 0 のまま
}
} catch (SQLException e) {
// SQLCODE NOT = 0(実エラー含む)→ WRK-INCOME-TAX = 0COBOL の ELSE 分岐と同義)
wrkIncomeTax = 0;
}
}
/**
* 段落 2000MAJSOR 内:社会保険料計算(SEARCH IW-ENTRY)。
* 条件:IW-INCOME-FROM(idx) ≦ 課税所得 AND 課税所得 ≦ IW-INCOME-TO(idx)
* AT END → WRK-INSURANCE = 0.
*/
private void insSearchPara() {
boolean found = false;
// SET IW-IDX TO 1 からの SEARCH(順次スキャン:COBOL SEARCH IW-ENTRY 相当)
for (int i = 0; i < iwCount; i++) {
if (iwIncomeFrom[i] <= wrkTaxableIncome && wrkTaxableIncome <= iwIncomeTo[i]) {
// COMPUTE WRK-INSURANCE ROUNDED = 課税所得 × (健康比率 + 厚生比率).
final BigDecimal sum = iwHealthRate[i].add(iwPensionRate[i]); // 双方 scale 6
BigDecimal insurance = BigDecimal.valueOf(wrkTaxableIncome)
.multiply(sum)
.setScale(0, RoundingMode.HALF_UP);
wrkInsurance = insurance.longValue();
found = true;
break;
}
}
if (!found) {
wrkInsurance = 0; // AT END: MOVE ZERO TO WRK-INSURANCE.
}
}
/**
* 段落 2100WRITOUTSORSALARY-NET 出力処理。
* MOVE 各項目 → W01OUTRECWRITE W01OUTRECADD 1 TO CUN-W01OUT.
*/
private void writeW01Para() {
w01.reset();
// MOVE R01EMP-ID TO W01EMP-ID.
w01.setEmpId(r01.getEmpId());
// MOVE 'S' TO W01REC-TYPE.
w01.setRecType("S");
// MOVE R01EMP-NAME TO W01EMP-NAME.
w01.setEmpName(r01.getEmpName());
// MOVE R01DEPT-CODE TO W01DEPT-CODE.
w01.setDeptCode(r01.getDeptCode());
// MOVE R01BASE-SALARY TO W01GROSS-PAYMENT.(設計書:設定元 R01BASE-SALARY
w01.setGrossPayment(r01.getBaseSalary());
// MOVE WRK-INCOME-TAX TO W01INCOME-TAX.
w01.setIncomeTax(wrkIncomeTax);
// MOVE WRK-INSURANCE TO W01INSURANCE.
w01.setInsurance(wrkInsurance);
// MOVE WRK-RESIDENT-TAX TO W01RESIDENT-TAX.
w01.setResidentTax(wrkResidentTax);
// MOVE WRK-NET-PAYMENT TO W01NET-PAYMENT.
w01.setNetPayment(wrkNetPayment);
// MOVE WRK-TAXABLE-INCOME TO W01TAXABLE-INCOME.
w01.setTaxableIncome(wrkTaxableIncome);
// MOVE WRK-NET-PAYMENT TO W01PAY-AMOUNT.
w01.setPayAmount(wrkNetPayment);
writeW01();
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE → 入出力件数メッセージ×3 → 終了メッセージ.
*/
private void termPara() {
closeFiles();
msgOutPara(CST_MSGIINKES, "KYU05R01", ConvUtil.padLeftZero(cunR01Inn, 9));
msgOutPara(CST_MSGOUTKES, "KYU05W01", ConvUtil.padLeftZero(cunW01Out, 9));
msgOutPara(CST_MSGOUTKES, "KYU05W02", ConvUtil.padLeftZero(cunW02Out, 9));
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND 処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), StandardCharsets.UTF_8));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), StandardCharsets.UTF_8));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** WRITE W01OUTREC.ADD 1 TO CUN-W01OUT. */
private void writeW01() {
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/** WRITE W02OUTREC.ADD 1 TO CUN-W02OUT. */
private void writeW02() {
w02OutFil.println(w02.toRecordLine());
cunW02Out++;
}
/** CLOSE R01INNFIL W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
r01InnFil = null;
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序も FILE STATUS 未管理)
}
}
/** EXEC SQL ROLLBACK END-EXEC(異常時) */
private static void rollbackQuiet(final Connection c) {
if (c != null) {
try {
c.rollback();
System.out.println("[db] ROLLBACK DONE");
} catch (SQLException e) {
System.out.println("[db] ROLLBACK FAILED: " + e.getMessage());
}
}
}
private static void closeQuiet(final Connection c) {
if (c != null) {
try {
c.close();
} catch (SQLException e) {
System.out.println("[db] CLOSE FAILED: " + e.getMessage());
}
}
}
}
+481
View File
@@ -0,0 +1,481 @@
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.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import dto.Kyu04Rec;
import dto.Kyu99Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU06UPD 給与計算結果DB2更新処理
* ============================================================
* PROGRAM-ID. KYU06UPD → 类 Kyu06UpdMainPGMパターン:DB更新(UPSERT)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R02INNFIL(KYU06R02 SALARY-NET FB200B) → R02INN_FILECOPY KYU04REC → dto.Kyu04Rec 共通DTO
* W03OUTFIL(KYU06W03 ERROR-LOG VB200B) → W03OUT_FILECOPY KYU99REC → dto.Kyu99Rec 再利用)
* 使用DB2EXEC SQL → JDBC):
* SALARY-RESULTS へ INSERT 試行 → SQLCODE=-803(重複) で UPDATE に切替(UPSERT
* 接続は DbSample に準拠:DB2 URL + プレースホルダ資格 + setAutoCommit(false)
* 正常終了時に COMMIT、致命的SQL例外時に ROLLBACK(プロジェクトDB規約準拠)
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R02INNSOR→readR02Para()
* 2000MAJSOR→majPara() 2100ERROUTSOR→errorOutPara() 3000STPSOR→termPara()
* 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 処理要点(詳細設計書_KYU06UPD 準拠):
* - PARM(YEAR-MONTH) をコマンドラインから取得、未指定時は '202605'。
* - R02 を全件読み、ホスト変数へ MOVE 後 INSERT。
* SQLCODE=0 → CUN-DB-INS+1-803 → UPDATE(SQLCODE=0 で CUN-DB-UPD+1)
* その他 → ERROR-LOG('DB-ERR' + EMP-ID) 出力。いずれも次件読込へ継続。
* - COBOL 原本に COMMIT 文は無いが、プロジェクトDB規約(commit on success /
* rollback on SQLException)に従い、正常終了時に 3000STPSOR 内で COMMIT を実施。
*
* ※COBOL の CONNECT TO 'data/SALARY.db'(SQLite) は、プロジェクトDB規約により
* DB2 URL(jdbc:db2://localhost:50000/TESTDB) のプレースホルダ接続に読替える。
*/
public class Kyu06UpdMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU06UPD'. */
private static final String CST_PRGIDX = "KYU06UPD";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. (本PGM未使用だが定数保持) */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数 */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数 */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-YEAR-MONTH-PARM PIC X(006). 取得PARM(初期値'202605'の格納先とは別に保持) */
private static final String CST_YEAR_MONTH_DEF = "202605";
// ==================== DB 接続定数(DbSample 準拠) ====================
/** EXEC SQL CONNECT TO 'data/SALARY.db' の読替(DB2 URL プレースホルダ。テスト時は -Dkyu.db.url で上書き)。 */
private static final String DB_URL = System.getProperty("kyu.db.url", "jdbc:db2://localhost:50000/TESTDB");
private static final String DB_USER = "db2inst1";
private static final String DB_PASS = "password";
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R02INNFIL ASSIGN TO EXTERNAL KYU06R02.SALARY-NET入力 FB200B */
private static final String R02INN_FILE = "data/KYU06R02.txt";
/** SELECT W03OUTFIL ASSIGN TO EXTERNAL KYU06W03.ERROR-LOG出力 VB200B */
private static final String W03OUT_FILE = "data/KYU06W03.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R02INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR02Inn;
/** 03 CUN-W03OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW03Out;
/** 03 CUN-DB-INS PIC S9(009) COMP-3 VALUE ZERO. */
private long cunDbIns;
/** 03 CUN-DB-UPD PIC S9(009) COMP-3 VALUE ZERO. */
private long cunDbUpd;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001). 読込フラグ(88 WRK-EOF-Y VALUE '1' */
private String wrkEof = "0";
/** 03 WRK-YEAR-MONTH PIC X(006). コマンドラインPARM受領領域 */
private String wrkYearMonth = "";
/** 03 CNS-YEAR-MONTH-PARM PIC X(006). MOVE WRK-YEAR-MONTH → 本値を INSERT/UPDATE に使用 */
private String cnsYearMonthParm = "";
/** 88 WRK-EOF-Y VALUE '1'. */
private boolean isEof() {
return "1".equals(wrkEof);
}
// ==================== DB2 ホスト変数領域 DBVARA ====================
/** 03 DBV-EMP-ID PIC X(008). */
private String dbvEmpId = "";
/** 03 DBV-YEAR-MONTH PIC X(006). */
private String dbvYearMonth = "";
/** 03 DBV-GROSS-PAYMENT PIC 9(009). */
private long dbvGrossPayment;
/** 03 DBV-INCOME-TAX PIC 9(009). */
private long dbvIncomeTax;
/** 03 DBV-INSURANCE PIC 9(009). */
private long dbvInsurance;
/** 03 DBV-RESIDENT-TAX PIC 9(009). */
private long dbvResidentTax;
/** 03 DBV-NET-PAYMENT PIC 9(009). */
private long dbvNetPayment;
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** 01 R02INNREC.FD R02INNFIL / COPY KYU04REC REPLACING ==(A)== BY ==R02== */
private final Kyu04Rec r02 = new Kyu04Rec();
/** 01 W03OUTREC.FD W03OUTFIL / COPY KYU99REC 再利用) */
private final Kyu99Rec w03 = new Kyu99Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r02InnFil;
private PrintWriter w03OutFil;
/** EXEC SQL CONNECT で取得したコネクション */
private Connection conn;
/** コマンドライン引数(先頭要素=YEAR-MONTH)。 */
private final String[] args;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
new Kyu06UpdMain(args).run();
}
public Kyu06UpdMain(final String[] args) {
this.args = (args != null) ? args : new String[0];
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!isEof()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF-Y
majPara();
}
termPara();
} catch (SQLException e) {
// EXEC SQL WHENEVER SQLERROR 相当(接続等の致命例外)
System.out.println("[db] SQLERROR code=" + e.getErrorCode() + " msg=" + e.getMessage());
rollbackQuiet(conn);
abendPara();
} finally {
closeQuiet(conn);
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始MSG→コンパイル日時MSG→ワーク初期化→PARM取得→OPEN→DB接続→初回読込。
*/
private void initPara() throws SQLException {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力(FUNCTION WHEN-COMPILED → 起動時刻で代用)
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** INITIALIZE WRKARA.
wrkEof = "0";
wrkYearMonth = "";
cnsYearMonthParm = "";
// *** PARM取得(ACCEPT WRK-YEAR-MONTH FROM COMMAND-LINE
if (args.length > 0 && args[0] != null && !args[0].trim().isEmpty()) {
wrkYearMonth = ConvUtil.padRight(args[0].trim(), 6);
}
// *** IF WRK-YEAR-MONTH = SPACES MOVE '202605' TO WRK-YEAR-MONTH
if (wrkYearMonth.trim().isEmpty()) {
wrkYearMonth = CST_YEAR_MONTH_DEF;
}
// *** MOVE WRK-YEAR-MONTH TO CNS-YEAR-MONTH-PARM.
cnsYearMonthParm = wrkYearMonth;
// *** OPEN INPUT R02INNFIL / OUTPUT W03OUTFIL.
openFiles();
// *** DB接続(EXEC SQL CONNECT TO 'data/SALARY.db' → DB2 URL 読替)
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
conn.setAutoCommit(false); // バッチ標準:明示的トランザクション制御
// *** 初回 READPERFORM 1100R02INNSOR
readR02Para();
}
/**
* 段落 1100R02INNSORR02読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR02Para() {
try {
final String line = r02InnFil.readLine();
if (line == null) {
// AT END: SET WRK-EOF-Y TO TRUE.
wrkEof = "1";
} else {
// NOT AT END: R02INNREC 受信 → MOVE 各項目 / ADD 1 TO CUN-R02INN.
r02.fromRecordLine(line);
cunR02Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理(UPSERT)。
* ホスト変数セット→INSERT→SQLCODE判定(-803→UPDATE/その他→ERR)→次件読込。
*/
private void majPara() {
// *** MOVE R02... TO DBV...(ホスト変数セット)
dbvEmpId = r02.getEmpId();
dbvYearMonth = cnsYearMonthParm;
dbvGrossPayment = r02.getGrossPayment();
dbvIncomeTax = r02.getIncomeTax();
dbvInsurance = r02.getInsurance();
dbvResidentTax = r02.getResidentTax();
dbvNetPayment = r02.getNetPayment();
// *** EXEC SQL INSERT INTO SALARY-RESULTS ...
final int insCode = insertRow();
if (insCode == 0) {
// *** SQLCODE = 0 → ADD 1 TO CUN-DB-INS/次件読込
cunDbIns++;
readR02Para();
return;
}
if (insCode == -803) {
// *** SQLCODE = -803(重複)→ EXEC SQL UPDATE SALARY-RESULTS ...
final int updCode = updateRow();
if (updCode == 0) {
// *** UPDATE SQLCODE = 0 → ADD 1 TO CUN-DB-UPD
cunDbUpd++;
} else {
// *** UPDATE SQLCODE ≠ 0 → エラー出力
errorOutPara();
}
readR02Para();
return;
}
// *** SQLCODE = その他(DBエラー)→ エラー出力
errorOutPara();
readR02Para();
}
/**
* EXEC SQL INSERT ... VALUES (:DBV-..., CURRENT TIMESTAMP).
* 戻り値: 0=成功 / -803=重複 / その他=SQLエラーコード。
*/
private int insertRow() {
final String sql = "INSERT INTO \"SALARY-RESULTS\" "
+ "(\"EMP-ID\", \"YEAR-MONTH\", \"GROSS-PAYMENT\", \"INCOME-TAX\", "
+ " \"INSURANCE\", \"RESIDENT-TAX\", \"NET-PAYMENT\", \"UPDATED-AT\") "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, dbvEmpId); // :DBV-EMP-ID
ps.setString(2, dbvYearMonth); // :DBV-YEAR-MONTH
ps.setLong(3, dbvGrossPayment); // :DBV-GROSS-PAYMENT
ps.setLong(4, dbvIncomeTax); // :DBV-INCOME-TAX
ps.setLong(5, dbvInsurance); // :DBV-INSURANCE
ps.setLong(6, dbvResidentTax); // :DBV-RESIDENT-TAX
ps.setLong(7, dbvNetPayment); // :DBV-NET-PAYMENT
ps.executeUpdate();
return 0; // SQLCODE = 0
} catch (SQLException e) {
// -803 は重複キー(UPSERT の UPDATE へ切替)。SQLite は UNIQUE constraint / SQLState 23000。
if (isDuplicateKey(e)) {
return -803;
}
return e.getErrorCode(); // その他 SQL エラー
}
}
/**
* EXEC SQL UPDATE SALARY-RESULTS SET ... WHERE EMP-ID AND YEAR-MONTH.
* 戻り値: 0=成功 / その他=SQLエラーコード。
*/
private int updateRow() {
final String sql = "UPDATE \"SALARY-RESULTS\" SET "
+ " \"GROSS-PAYMENT\" = ?, \"INCOME-TAX\" = ?, \"INSURANCE\" = ?, "
+ " \"RESIDENT-TAX\" = ?, \"NET-PAYMENT\" = ?, \"UPDATED-AT\" = CURRENT_TIMESTAMP "
+ "WHERE \"EMP-ID\" = ? AND \"YEAR-MONTH\" = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, dbvGrossPayment); // :DBV-GROSS-PAYMENT
ps.setLong(2, dbvIncomeTax); // :DBV-INCOME-TAX
ps.setLong(3, dbvInsurance); // :DBV-INSURANCE
ps.setLong(4, dbvResidentTax); // :DBV-RESIDENT-TAX
ps.setLong(5, dbvNetPayment); // :DBV-NET-PAYMENT
ps.setString(6, dbvEmpId); // :DBV-EMP-ID
ps.setString(7, dbvYearMonth); // :DBV-YEAR-MONTH
ps.executeUpdate();
return 0; // SQLCODE = 0
} catch (SQLException e) {
return e.getErrorCode();
}
}
/**
* 段落 2100ERROUTSOR:エラー出力処理。
* MOVE 'DB-ERR' TO W03ERR-CATEGORY.MOVE DBV-EMP-ID TO W03ERR-DETAIL.WRITE W03OUTREC.
*/
private void errorOutPara() {
w03.reset();
w03.setErrCategory("DB-ERR");
w03.setErrDetail(dbvEmpId);
writeW03();
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→件数メッセージ×4→COMMIT→終了メッセージ。
*/
private void termPara() {
closeFiles();
msgOutPara(CST_MSGIINKES, "KYU06R02", ConvUtil.padLeftZero(cunR02Inn, 9));
msgOutPara(CST_MSGOUTKES, "DB-INSERT", ConvUtil.padLeftZero(cunDbIns, 9));
msgOutPara(CST_MSGOUTKES, "DB-UPDATE", ConvUtil.padLeftZero(cunDbUpd, 9));
msgOutPara(CST_MSGOUTKES, "KYU06W03", ConvUtil.padLeftZero(cunW03Out, 9));
// *** EXEC SQL COMMIT END-EXEC(プロジェクトDB規約:成功時コミット)
if (conn != null) {
try {
conn.commit();
} catch (SQLException e) {
System.out.println("[db] COMMIT FAILED code=" + e.getErrorCode() + " " + e.getMessage());
rollbackQuiet(conn);
abendPara();
}
}
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN INPUT R02INNFIL / OUTPUT W03OUTFIL. */
private void openFiles() {
try {
r02InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(R02INN_FILE), StandardCharsets.UTF_8));
w03OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W03OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** WRITE W03OUTREC.ADD 1 TO CUN-W03OUT. */
private void writeW03() {
w03OutFil.println(w03.toRecordLine());
cunW03Out++;
}
/** CLOSE R02INNFIL W03OUTFIL. */
private void closeFiles() {
closeQuiet(r02InnFil);
closeQuiet(w03OutFil);
r02InnFil = null;
w03OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
/** EXEC SQL ROLLBACK END-EXEC(異常時) */
private static void rollbackQuiet(final Connection c) {
if (c != null) {
try {
c.rollback();
} catch (SQLException e) {
// ignore
}
}
}
private static void closeQuiet(final Connection c) {
if (c != null) {
try {
c.close();
} catch (SQLException e) {
// ignore
}
}
}
/** DB2(-803) と SQLite(UNIQUE constraint / SQLState 23000) の両方で重複キーを判定。 */
private static boolean isDuplicateKey(final SQLException e) {
if (e.getErrorCode() == -803) {
return true;
}
if ("23000".equals(e.getSQLState())) {
return true;
}
final String m = e.getMessage();
return m != null && m.contains("UNIQUE constraint failed");
}
}
+578
View File
@@ -0,0 +1,578 @@
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.nio.charset.StandardCharsets;
import dto.Kyu07R01Rec;
import dto.Kyu99Rec;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU07DIV 部門別分割出力処理
* ============================================================
* PROGRAM-ID. KYU07DIV → 类 Kyu07DivMainPGMパターン:50分割)
*
* 使用文件(SELECT ASSIGN → 路径常量):
* R01INNFIL(KYU07R01 SALARY-NET FB200B入力) → R01INN_FILECOPY KYU04REC → dto.Kyu07R01Rec
* SYSIN-FILE(KYU07SYS 制御カード F80B入力) → SYSIN_FILE(自前 PIC X(80)
* W99OUTFIL(KYU07W99 ERROR-LOG VB200B出力) → W99OUT_FILECOPY KYU99REC → dto.Kyu99Rec
* DEPT01〜50KYU07D01〜D50 各200B FB出力) → getDeptPath()(自前 PIC X(200)
* 使用SUBCALL → 类#execute):
* SUB02MSG→Sub02MsgSub4000MSGOUTSOR / SUB03END→Sub03EndSub9999ABDSOR
* 段落对应:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100SYSINSOR→sysInPara()
* 1200R01INNSOR→readR01Para() 2000MAJSOR→majPara() 2100OPENSOR→openDeptPara()
* 2200CLOSESOR→closeDeptPara() 2300WRITESOR→writeDeptPara() 3000STPSOR→termPara()
* 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara()
*
* 处理要点(詳細設計書_KYU07DIV 準拠):
* - JCL SORT済 SALARY-NET を部署コード(50-51桁)で50ファイルに分割出力。
* - 常に1ファイルのみOPEN:部署切替時に前部署CLOSE→新部署OPEN。
* - SYSIN制御カード "DEPT=01,02,05,10" で出力対象部署を絞り込み(省略時は全50部署)。
* - 無効部署(01〜50 範囲外)は ERROR-LOG(ERR-CATEGORY='INVDEPT', ERR-DETAIL='INVALID DEPT')を出力。
* ※設計書は W99ERR-CATEGORY='INVDEPT' を固定値指定(原COBOLは DETAIL のみ設定)→ 設計書優先。
*/
public class Kyu07DivMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU07DIV'. */
private static final String CST_PRGIDX = "KYU07DIV";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. (設計上使用なし) */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数メッセージ */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数メッセージ */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時 */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KYU07R01.SALARY-NET入力) */
private static final String R01INN_FILE = "data/KYU07R01.txt";
/** SELECT SYSIN-FILE ASSIGN TO KYU07SYS.(制御カード入力) */
private static final String SYSIN_FILE = "data/KYU07SYS.txt";
/** SELECT W99OUTFIL ASSIGN TO KYU07W99.ERROR-LOG出力) */
private static final String W99OUT_FILE = "data/KYU07W99.txt";
/** 部署コード 01〜50 の定数配列(DEPT01〜50 / 0は未使用)。 */
private static final String[] DEPT_CODE = new String[51];
static {
for (int k = 1; k <= 50; k++) {
DEPT_CODE[k] = String.format("%02d", k);
}
}
/** SELECT DEPTnn-FILE ASSIGN TO KYU07Dnn.(部署別出力、nn=01〜50 */
private static String getDeptPath(final String deptCode) {
return "data/KYU07D" + deptCode + ".txt";
}
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W99OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW99Out;
/** 03 CUN-DEPT-OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunDeptOut;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001). 読込フラグ(88 WRK-EOF-Y VALUE '1' */
private String wrkEof = "0";
/** 03 WRK-DEPT-CODE PIC X(002). */
private String wrkDeptCode = "";
/** 03 WRK-PREV-DEPT PIC X(002). */
private String wrkPrevDept = "";
/** 03 WRK-FIRST PIC X(001) VALUE '1'.88 WRK-FIRST-Y '1' / WRK-FIRST-N '0' */
private String wrkFirst = "1";
/** 03 WRK-TARGET-DEPT / 05 WRK-TARGET OCCURS 50 TIMES PIC X(002). */
private final String[] wrkTarget = new String[51];
/** 03 WRK-TARGET-COUNT PIC 9(002). */
private int wrkTargetCount;
/** 03 WRK-HAS-TARGET PIC X(001).88 WRK-ALL-DEPTS 'A' / WRK-FILTER-DEPTS 'F' */
private String wrkHasTarget = "";
/** 03 WRK-SYSIN-LINE PIC X(080). */
private String wrkSysinLine = "";
/** 03 WRK-I PIC 9(002).(パース用索引) */
private int wrkI;
/** 03 WRK-J PIC 9(002).(パース用索引) */
private int wrkJ;
/** 03 WRK-TOKEN PIC X(080). */
private String wrkToken = "";
/** 03 WS-PAD-CHAR PIC X(001) VALUE SPACE. */
private String wsPadChar = " ";
/** 03 WS-FILE-STATUS PIC X(002). */
private String wsFileStatus = "";
/** 88 WRK-EOF-Y VALUE '1'. */
private boolean isEof() {
return "1".equals(wrkEof);
}
/** 88 WRK-FIRST-Y VALUE '1'. */
private boolean isFirstY() {
return "1".equals(wrkFirst);
}
/** 88 WRK-ALL-DEPTS VALUE 'A'. */
private boolean isAllDepts() {
return "A".equals(wrkHasTarget);
}
/** 88 WRK-FILTER-DEPTS VALUE 'F'. */
private boolean isFilterDepts() {
return "F".equals(wrkHasTarget);
}
/** SET WRK-ALL-DEPTS TO TRUE. */
private void setAllDepts() {
wrkHasTarget = "A";
}
/** SET WRK-FILTER-DEPTS TO TRUE. */
private void setFilterDepts() {
wrkHasTarget = "F";
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** 01 R01INNREC.FD R01INNFIL / COPY KYU04REC → dto.Kyu07R01Rec */
private final Kyu07R01Rec r01 = new Kyu07R01Rec();
/** 01 W99OUTREC.FD W99OUTFIL / COPY KYU99REC */
private final Kyu99Rec w99 = new Kyu99Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private PrintWriter w99OutFil;
private PrintWriter deptOutFil;
/** 現在OPEN中の部署コード(1ファイルのみOPENを管理)。 */
private String openDeptCode = "";
/** 入力ファイルパス。デフォルトは SELECT ASSIGN 対応の R01INN_FILE。 */
private final String r01InnFile;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
final String inputFile = (args != null && args.length > 0) ? args[0] : R01INN_FILE;
new Kyu07DivMain(inputFile).run();
}
public Kyu07DivMain() {
this(R01INN_FILE);
}
public Kyu07DivMain(final String r01InnFile) {
this.r01InnFile = r01InnFile;
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL EOF → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!isEof()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF = '1'
majPara();
}
termPara();
} finally {
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→SYSIN読込→OPEN→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
resetWrk();
// *** MOVE '1' TO WRK-FIRST.
wrkFirst = "1";
// *** SYSIN読込(対象部署指定)
sysInPara();
// *** OPEN INPUT R01INNFIL / OPEN OUTPUT W99OUTFIL.
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(r01InnFile), StandardCharsets.UTF_8));
w99OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W99OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
// *** 初回READ
readR01Para();
}
/**
* 段落 1100SYSINSORSYSIN読込処理。
* OPEN→READ AT END(全部署対象) / カードありは "DEPT=01,02,05,10" 形式をパース。
*/
private void sysInPara() {
try (BufferedReader sysIn = new BufferedReader(
new InputStreamReader(new FileInputStream(SYSIN_FILE), StandardCharsets.UTF_8))) {
final String line = sysIn.readLine();
if (line == null) {
// *** AT END: SET WRK-ALL-DEPTS TO TRUE(制御カードなし→全部署対象)
setAllDepts();
return;
}
// *** 制御カードあり:READ INTO WRK-SYSIN-LINEPIC X(80)
wrkSysinLine = ConvUtil.padRight(line, 80);
// *** MOVE WRK-SYSIN-LINE TO WRK-TOKEN.
wrkToken = wrkSysinLine;
// *** MOVE ZERO TO WRK-TARGET-COUNT.
wrkTargetCount = 0;
// *** MOVE 1 TO WRK-J.
wrkJ = 1;
// *** 先頭の "DEPT=" をスキップ('=' まで読飛ばし)
// PERFORM VARYING WRK-I FROM 1 BY 1
// UNTIL WRK-I > 80 OR SYSIN(WRK-I:1) = SPACE OR '='
int i = 1;
while (i <= 80) {
final char c = charAt1(wrkSysinLine, i);
if (c == ' ' || c == '=') {
break;
}
i++;
}
// *** ADD 1 TO WRK-I.'=' の次桁へ)
i++;
// *** トークン解析ループ
// PERFORM VARYING WRK-J FROM 1 BY 1
// UNTIL WRK-I > 80 OR SYSIN(WRK-I:1) = SPACE
while (i <= 80) {
final char c = charAt1(wrkSysinLine, i);
if (c == ' ') {
break;
}
if (c == ',') {
// *** IF SYSIN(WRK-I:1) = ',' → ADD 1 TO WRK-I(区切り読飛ばし)
i++;
// CONTINUE(直後の MOVE で次トークンを取り込む)
}
if (wrkTargetCount < 50) {
// *** ADD 1 TO WRK-TARGET-COUNT / MOVE SYSIN(WRK-I:2) TO WRK-TARGET-CODE(n)
wrkTargetCount++;
wrkTarget[wrkTargetCount] = read2(wrkSysinLine, i);
}
// *** ADD 2 TO WRK-I.
i += 2;
}
if (wrkTargetCount > 0) {
// *** SET WRK-FILTER-DEPTS TO TRUE
setFilterDepts();
} else {
// *** SET WRK-ALL-DEPTS TO TRUE
setAllDepts();
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* 段落 1200R01INNSORR01読込処理。
* READ AT END / NOT AT END のマッピング。
*/
private void readR01Para() {
try {
final String line = r01InnFil.readLine();
if (line == null) {
// AT END: MOVE '1' TO WRK-EOF.
wrkEof = "1";
} else {
// NOT AT END: レコード取込み + ADD 1 TO CUN-R01INN.
r01.setRecordLine(line);
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* 部署コード取得→SYSINフィルタ→部署切替判定→WRITE。
*/
private void majPara() {
// *** MOVE R01DEPT-CODE TO WRK-DEPT-CODE.
wrkDeptCode = r01.getDeptCode();
// *** SYSIN対象部署フィルタ(WRK-FILTER-DEPTS 時のみ SEARCH
if (isFilterDepts()) {
boolean found = false;
for (int t = 1; t <= wrkTargetCount; t++) {
if (wrkTarget[t].equals(wrkDeptCode)) {
found = true;
break;
}
}
if (!found) {
// *** AT END: PERFORM 1200R01INNSOR / EXIT SECTION(対象外は読飛ばし)
readR01Para();
return;
}
}
// *** 部署切替判定
if (isFirstY()) {
// *** 初回:WRK-FIRST='0' → 2100OPENSOR
wrkFirst = "0";
openDeptPara(wrkDeptCode);
} else {
if (!wrkPrevDept.equals(wrkDeptCode)) {
// *** 部署切替:2200CLOSESOR → 2100OPENSOR
closeDeptPara(wrkPrevDept);
openDeptPara(wrkDeptCode);
}
}
// *** MOVE WRK-DEPT-CODE TO WRK-PREV-DEPT.
wrkPrevDept = wrkDeptCode;
// *** 該当ファイルにWRITE2300WRITESOR
writeDeptPara(wrkDeptCode);
// *** PERFORM 1200R01INNSOR.
readR01Para();
}
/**
* 段落 2100OPENSOR:部署ファイルOPEN処理。
* EVALUATE WRK-DEPT-CODE → '01'〜'50' は OPEN OUTPUT、それ以外は ERROR-LOG出力。
* ※常に1ファイルのみOPEN(呼出元で前部署は CLOSE 済)。
*/
private void openDeptPara(final String deptCode) {
final int k = deptIndex(deptCode);
if (k < 0) {
// *** WHEN OTHER: MOVE 'INVALID DEPT' TO W99ERR-DETAIL / WRITE / ADD 1 CUN-W99OUT
// ※設計書準拠:ERR-CATEGORY は固定値 'INVDEPT' を設定
w99.reset();
w99.setErrCategory("INVDEPT");
w99.setErrDetail("INVALID DEPT");
writeW99();
return;
}
// *** WHEN '01' 〜 '50': OPEN OUTPUT DEPTnn-FILE
try {
deptOutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(getDeptPath(deptCode)), StandardCharsets.UTF_8));
openDeptCode = deptCode;
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/**
* 段落 2200CLOSESOR:部署ファイルCLOSE処理。
* EVALUATE WRK-PREV-DEPT → '01'〜'50' は CLOSE WITH LOCK、それ以外は CONTINUE。
*/
private void closeDeptPara(final String prevDept) {
final int k = deptIndex(prevDept);
if (k < 0) {
// *** WHEN OTHER: CONTINUE(対象外部署は OPEN されていない)
return;
}
// *** WHEN '01' 〜 '50': CLOSE DEPTnn-FILE WITH LOCK
try {
if (deptOutFil != null) {
deptOutFil.close();
}
} catch (Exception e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
deptOutFil = null;
openDeptCode = "";
}
/**
* 段落 2300WRITESOR:部署WRITE処理。
* EVALUATE WRK-DEPT-CODE → '01'〜'50' は WRITE、それ以外は CONTINUE。
* その後 ADD 1 TO CUN-DEPT-OUTEVALUATE 外・常時カウント)。
*/
private void writeDeptPara(final String deptCode) {
final int k = deptIndex(deptCode);
if (k >= 0) {
// *** WHEN '01' 〜 '50': WRITE DEPTnn-REC FROM R01INNREC
if (deptOutFil != null) {
deptOutFil.println(r01.toRecordLine());
}
}
// *** ADD 1 TO CUN-DEPT-OUTWHEN OTHER 含む全レコードをカウント)
cunDeptOut++;
}
/**
* 段落 3000STPSOR:終了処理。
* 最終部署CLOSE→CLOSE R01/W99→件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
// *** IF WRK-PREV-DEPT > SPACES → PERFORM 2200CLOSESOR
if (wrkPrevDept != null && !wrkPrevDept.isEmpty()) {
closeDeptPara(wrkPrevDept);
}
// *** CLOSE R01INNFIL W99OUTFIL.finally の closeFiles で実施)
// *** 入力件数メッセージ
msgOutPara(CST_MSGIINKES, "KYU07R01", ConvUtil.padLeftZero(cunR01Inn, 9));
// *** 出力件数メッセージ(DEPT-OUT)
msgOutPara(CST_MSGOUTKES, "DEPT-OUT", ConvUtil.padLeftZero(cunDeptOut, 9));
// *** 出力件数メッセージ(KYU07W99)
msgOutPara(CST_MSGOUTKES, "KYU07W99", ConvUtil.padLeftZero(cunW99Out, 9));
// *** 終了メッセージ
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
// *** MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1) / -04(1:1).(先頭1桁='2'
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
// *** MOVE CNS-PRGIDX TO M00UMKDATS22-05.
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** INITIALIZE WRKARA 相当:数値→0、文字→SPACE。 */
private void resetWrk() {
wrkEof = "0";
wrkDeptCode = "";
wrkPrevDept = "";
wrkFirst = "0";
for (int t = 1; t <= 50; t++) {
wrkTarget[t] = "";
}
wrkTargetCount = 0;
wrkHasTarget = "";
wrkSysinLine = "";
wrkI = 0;
wrkJ = 0;
wrkToken = "";
wsPadChar = " ";
wsFileStatus = "";
}
/** WRITE W99OUTREC.ADD 1 TO CUN-W99OUT. */
private void writeW99() {
w99OutFil.println(w99.toRecordLine());
cunW99Out++;
}
/** CLOSE R01INNFIL W99OUTFIL(+最終部署ファイルが残れば close)。 */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w99OutFil);
closeQuiet(deptOutFil);
r01InnFil = null;
w99OutFil = null;
deptOutFil = null;
openDeptCode = "";
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
/** 部署コード→DEPT_CODE 索引(1〜50)を返す。一致しなければ -1。 */
private static int deptIndex(final String deptCode) {
if (deptCode == null) {
return -1;
}
for (int k = 1; k <= 50; k++) {
if (DEPT_CODE[k].equals(deptCode)) {
return k;
}
}
return -1;
}
/** 1-indexed 位置の文字を取得(範囲外は SPACE)。 */
private static char charAt1(final String s, final int idx1) {
final int p = idx1 - 1;
if (p >= 0 && p < s.length()) {
return s.charAt(p);
}
return ' ';
}
/** 1-indexed 位置から2文字を取得(範囲外は SPACE で補完)。 */
private static String read2(final String s, final int idx1) {
final char a = charAt1(s, idx1);
final char b = charAt1(s, idx1 + 1);
return "" + a + b;
}
}
+413
View File
@@ -0,0 +1,413 @@
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.nio.charset.StandardCharsets;
import dto.Kyu04Rec;
import dto.Kyu05Rec;
import dto.Kyu99Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU08PRI 給与明細印刷処理
* ============================================================
* PROGRAM-ID. KYU08PRI → 类 Kyu08PriMainPGMパターン:レイアウト編集のみ GETPUT)
*
* 使用ファイル(SELECT ASSIGN → パス定数):
* R01INNFIL(KYU08R01 SALARY-NET F200B 入力) → R01INN_FILECOPY KYU04REC → dto.Kyu04Rec
* W01OUTFIL(KYU08W01 PAYSLIP F200B 出力) → W01OUT_FILECOPY KYU05REC → dto.Kyu05Rec
* W02OUTFIL(KYU08W02 ERROR-LOG VB200B 出力)→ W02OUT_FILECOPY KYU99REC → dto.Kyu99Rec、将来拡張・現状出力なし)
*
* 使用SUBCALL → 类#execute):
* SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
*
* 段落対応:
* 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100R01INNSOR→readR01Para()
* 2000MAJSOR→majPara() 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara()
* 9999ABDSOR→abendPara()
*
* 処理要点(詳細設計書_KYU08PRI 準拠):
* - 1社員1ページ。ヘッダ行→明細行→フッタ行を 200B 固定長で順次出力(LINAGE 66行/ページ相当)。
* - ヘッダ行:HL-TITLE('========== 給与明細書 ==========') ページ番号(Z(03)9) 'PG'。
* - 明細行:DETAIL-LINE は KYU05REC 布局と一致(PAGE-TITLE=SPACE)するため Kyu05Rec で編集。
* EMP-NAME は X(020) JUSTIFIED RIGHT、BASIC/NET は Z(008)9 BLANK WHEN ZERO で編集。
* - フッタ行:FL-MSG('--- 明細終了 ---')。
* - COBOL は LINAGE/ADVANCING PAGE を用いるが、Java 平文テキスト出力では 200B 行の連続書出とし
* ページ制御(フォームフィード等)は行わない(設計上 1社員1ページのため実害なし)。
* - W02(ERROR-LOG) は OPEN OUTPUT のみ(現状 WRITE なし/将来拡張)。
*/
public class Kyu08PriMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU08PRI'. */
private static final String CST_PRGIDX = "KYU08PRI";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. サブエラーメッセージ */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数メッセージ */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数メッセージ */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時メッセージ */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-HEADER-LINES PIC 9(002) VALUE 6. LINAGE TOP 相当・参照用) */
private static final int CST_HEADER_LINES = 6;
/** 03 CNS-DETAIL-LINES PIC 9(002) VALUE 54. LINAGE 明細相当・参照用) */
private static final int CST_DETAIL_LINES = 54;
/** 03 CNS-FOOTER-LINES PIC 9(002) VALUE 6. LINAGE BOTTOM 相当・参照用) */
private static final int CST_FOOTER_LINES = 6;
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT R01INNFIL ASSIGN TO KYU08R01.SALARY-NET 入力 F200B */
private static final String R01INN_FILE = "data/KYU08R01.txt";
/** SELECT W01OUTFIL ASSIGN TO KYU08W01.PAYSLIP 出力 F200B */
private static final String W01OUT_FILE = "data/KYU08W01.txt";
/** SELECT W02OUTFIL ASSIGN TO KYU08W02.ERROR-LOG 出力 VB200B・現状出力なし) */
private static final String W02OUT_FILE = "data/KYU08W02.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-R01INN PIC S9(009) COMP-3 VALUE ZERO. */
private long cunR01Inn;
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-EOF PIC X(001). 読込フラグ(88 WRK-EOF-Y VALUE '1' */
private String wrkEof = "0";
/** 03 WRK-LINE-NO PIC 9(002). */
private int wrkLineNo;
/** 03 WRK-PAGE-COUNTER PIC 9(004) VALUE 0. */
private int wrkPageCounter = 0;
/** 03 WS-PAD-CHAR PIC X(001) VALUE SPACE. */
private String wsPadChar = " ";
/** 03 WS-FILE-STATUS PIC X(002). */
private String wsFileStatus = "";
/** 03 WS-DISP-PG PIC Z(03)9. HL-PAGE-NO 編集用・参照用) */
private int wsDispPg;
/** 88 WRK-EOF-Y VALUE '1'. */
private boolean isEof() {
return "1".equals(wrkEof);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
/** COPY ZANDATAC.SUB01DAT用) */
private final ZandatacPar d01UbsPar = new ZandatacPar();
// ==================== 入出力レコード(FILE SECTION → DTO ====================
/** 01 R01INNREC.FD R01INNFIL / COPY KYU04REC */
private final Kyu04Rec r01 = new Kyu04Rec();
/** 01 W01OUTREC.FD W01OUTFIL / COPY KYU05REC */
private final Kyu05Rec w01 = new Kyu05Rec();
/** 01 W02OUTREC.FD W02OUTFIL / COPY KYU99REC */
private final Kyu99Rec w02 = new Kyu99Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private BufferedReader r01InnFil;
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
new Kyu08PriMain().run();
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → PERFORM 2000MAJSOR UNTIL WRK-EOF-Y → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara();
while (!isEof()) { // PERFORM 2000MAJSOR UNTIL WRK-EOF = '1'
majPara();
}
termPara();
} finally {
closeFiles();
}
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→SUB01DAT→OPEN→初回読込。
*/
private void initPara() {
// *** 開始メッセージ出力
// MOVE CNS-MSGSTR TO M00MSGCOD.PERFORM 4000MSGOUTSOR.
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
// MOVE CNS-MSGKEYINF TO M00MSGCOD.
// MOVE FUNCTION WHEN-COMPILED TO M00UMKDATS22-01.
// MOVE 'COMPILED' TO M00UMKDATS22-02.
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
wrkEof = "0";
wrkLineNo = 0;
wrkPageCounter = 0;
wsPadChar = " ";
wsFileStatus = "";
wsDispPg = 0;
// *** 運用日付取得 SUB01DAT
// INITIALIZE D01UBSPAR.CALL 'SUB01DAT' USING D01UBSPAR.
d01UbsPar.reset();
Sub01DatSub.execute(d01UbsPar);
if (d01UbsPar.getD01FkiCod() != 0) {
// IF D01FKICOD NOT = ZERO
// MOVE CNS-MSGSUBEEK TO M00MSGCOD
// MOVE 'SUB01DAT' TO M00UMKDATS22-01
// MOVE D01FKICOD TO M00UMKDATS22-02
// PERFORM 4000MSGOUTSOR
// PERFORM 9999ABDSOR
msgOutPara(CST_MSGSUBEEK, "SUB01DAT",
ConvUtil.padLeftZero(d01UbsPar.getD01FkiCod(), 4));
abendPara();
return;
}
// *** 使用ファイルのオープン
// OPEN INPUT R01INNFIL.OPEN OUTPUT W01OUTFIL W02OUTFIL.
openFiles();
// *** R01 初回読込(1100R01INNSOR
readR01Para();
}
/**
* 段落 1100R01INNSORR01読込処理。
* READ R01INNFIL AT END SET WRK-EOF-Y / NOT AT END ADD 1 TO CUN-R01INN.
*/
private void readR01Para() {
try {
final String line = r01InnFil.readLine();
if (line == null) {
// AT END: SET WRK-EOF-Y TO TRUE.
wrkEof = "1";
} else {
// NOT AT END: 固定長解析 → R01INNREC、ADD 1 TO CUN-R01INN.
r01.parseRecord(line);
cunR01Inn++;
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ異常 ≈ ABEND
}
}
/**
* 段落 2000MAJSOR:主処理。
* ヘッダ出力(改ページ+ページ番号)→明細行編集・出力→フッタ出力(改ページ)→次レコード読込。
*/
private void majPara() {
// *** ページカウンタ加算
// ADD 1 TO WRK-PAGE-COUNTER.
wrkPageCounter++;
// MOVE WRK-PAGE-COUNTER TO HL-PAGE-NO.MOVE WRK-PAGE-COUNTER TO WS-DISP-PG.
wsDispPg = wrkPageCounter;
// *** ヘッダ出力(BEFORE ADVANCING PAGE
// WRITE W01OUTREC FROM HEADER-LINE BEFORE ADVANCING PAGE.
w01OutFil.println(buildHeaderLine(wrkPageCounter));
// *** 明細行編集(DETAIL-LINE ≡ KYU05REC 布局:PAGE-TITLE=SPACE
// MOVE R01EMP-ID TO DL-EMP-ID.
// MOVE R01EMP-NAME TO DL-EMP-NAME.
// MOVE R01GROSS-PAYMENT TO DL-BASIC-SALARY.
// MOVE R01NET-PAYMENT TO DL-NET-PAYMENT.
w01.reset();
w01.setW01EmpId(r01.getR01EmpId());
w01.setW01EmpName(r01.getR01EmpName());
w01.setW01BasicSalary((int) r01.getR01GrossPayment());
w01.setW01NetPayment((int) r01.getR01NetPayment());
w01.setW01PageTitle(""); // 明細行の PAGE-TITLE は SPACE
// WRITE W01OUTREC FROM DETAIL-LINE BEFORE ADVANCING 1 LINE
// AT END-OF-PAGE WRITE W01OUTREC FROM FOOTER-LINE BEFORE ADVANCING PAGE.
// 1社員1ページのため AT END-OF-PAGE は発動しない)
w01OutFil.println(w01.toRecordLine());
// ADD 1 TO CUN-W01OUT.
cunW01Out++;
// *** フッタ出力(BEFORE ADVANCING PAGE
// WRITE W01OUTREC FROM FOOTER-LINE BEFORE ADVANCING PAGE.
w01OutFil.println(buildFooterLine());
// *** 次レコード読込(1100R01INNSOR
readR01Para();
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE→入出力件数メッセージ×3→終了メッセージ。
*/
private void termPara() {
// *** 入出力ファイルのクローズ
// CLOSE R01INNFIL W01OUTFIL W02OUTFIL.
closeFiles();
// *** 入出力件数メッセージ出力
// MOVE CNS-MSGIINKES TO M00MSGCOD. MOVE 'KYU08R01' TO PARM1. MOVE CUN-R01INN TO PARM2.
msgOutPara(CST_MSGIINKES, "KYU08R01", ConvUtil.padLeftZero(cunR01Inn, 9));
// MOVE CNS-MSGOUTKES TO M00MSGCOD. MOVE 'KYU08W01' TO PARM1. MOVE CUN-W01OUT TO PARM2.
msgOutPara(CST_MSGOUTKES, "KYU08W01", ConvUtil.padLeftZero(cunW01Out, 9));
// MOVE CNS-MSGOUTKES TO M00MSGCOD. MOVE 'KYU08W02' TO PARM1. MOVE CUN-W02OUT TO PARM2.
msgOutPara(CST_MSGOUTKES, "KYU08W02", ConvUtil.padLeftZero(cunW02Out, 9));
// *** 終了メッセージ出力
// MOVE CNS-MSGFIN TO M00MSGCOD.PERFORM 4000MSGOUTSOR.
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
// MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1). → 先頭1桁 '2' 後続SPACE(30)
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
// MOVE CNS-KN0002 TO M00UMKDATS22-04(1:1).
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
// MOVE CNS-PRGIDX TO M00UMKDATS22-05.
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
// ==================== 印刷行編集補助(HEADER-LINE / FOOTER-LINE ====================
/**
* 01 HEADER-LINE 編集(200B)。
* 05 HL-TITLE PIC X(040) VALUE '========== 給与明細書 =========='
* 05 FILLER PIC X(010) VALUE SPACES
* 05 HL-PAGE-NO PIC Z(03)9
* 05 FILLER PIC X(002) VALUE 'PG'
* 05 FILLER PIC X(144) VALUE SPACES
*/
private static String buildHeaderLine(final int pageNo) {
// PIC Z(03)9 編集(4桁、先頭3桁ゼロ抑制)
final String pageNoEd = editZ(pageNo, 3);
return ConvUtil.padRight("========== 給与明細書 ==========", 40)
+ ConvUtil.padRight("", 10)
+ pageNoEd
+ "PG"
+ ConvUtil.padRight("", 144);
}
/**
* 01 FOOTER-LINE 編集(200B)。
* 05 FL-MSG PIC X(040) VALUE '--- 明細終了 ---'
* 05 FILLER PIC X(160) VALUE SPACES
*/
private static String buildFooterLine() {
return ConvUtil.padRight("--- 明細終了 ---", 40)
+ ConvUtil.padRight("", 160);
}
/** PIC Z(n)9 BLANK WHEN ZERO 編集(編集桁数 = zCount + 1)。 */
private static String editZ(final int val, final int zCount) {
final int len = zCount + 1;
if (val == 0) {
// BLANK WHEN ZERO
return ConvUtil.padRight("", len);
}
final String s = ConvUtil.padLeftZero(val, len);
final char[] ca = s.toCharArray();
for (int i = 0; i < zCount; i++) {
if (ca[i] == '0') {
ca[i] = ' ';
} else {
break;
}
}
return new String(ca);
}
// ==================== ファイル操作補助(OPEN/CLOSE 相当) ====================
/** OPEN INPUT R01INNFIL / OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
r01InnFil = new BufferedReader(
new InputStreamReader(new FileInputStream(R01INN_FILE), StandardCharsets.UTF_8));
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), StandardCharsets.UTF_8));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN失敗 ≈ ABEND
}
}
/** CLOSE R01INNFIL W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(r01InnFil);
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
r01InnFil = null;
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof BufferedReader) {
((BufferedReader) handle).close();
} else if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (IOException e) {
// CLOSE異常は無視(原程序もFILE STATUS未管理)
}
}
}
+450
View File
@@ -0,0 +1,450 @@
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.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import dto.Kyu04Rec;
import dto.Kyu06Rec;
import dto.Kyu99Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】KYU09MRG 支給データ結合処理
* ============================================================
* PROGRAM-ID. KYU09MRG → 类 Kyu09MrgMainPGMパターン:MERGE 複数ファイル結合)
*
* 使用ファイル(SELECT ASSIGN → 路径常量):
* SALARY-FILE(KYU09M01 SALARY-NET F200B) → SAL_FILECOPY KYU04REC → dto.Kyu04Rec
* BONUS-FILE(KYU09M02 BONUS-DATA F200B) → BON_FILECOPY KYU04REC → dto.Kyu04Rec
* W01OUTFIL(KYU09W01 PAYMENT-DATA F200B) → W01OUT_FILECOPY KYU06REC → dto.Kyu06Rec
* W02OUTFIL(KYU09W02 ERROR-LOG VB200B) → W02OUT_FILECOPY KYU99REC → dto.Kyu99Rec
* MERGE-FILE(KYU09M99 作業SD) → Java 側は2ファイルのマージ結合で再現
* 使用SUBCALL → 类#execute):
* SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub
* 段落对应:
* 0000MAJCOLSOR→run()/mrgPara() 1000ITTSOR→initPara() 2000MRGOUTSOR→mrgOutSor()
* 2100PAYOUTSOR→payOutPara() 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara()
* 9999ABDSOR→abendPara() 0010DIRECTSOR→dirSor()ENTRY 'KYU09ENT' 代替入口)
*
* 处理要点(詳細設計書_KYU09MRG 準拠):
* - COBOL の MERGE 文(SALARY/BONUS を EMP-ID 昇順に結合)は、Java に同等文が
* 無いため「両入力ファイルは EMP-ID 昇順済」という前提の下、2ポインタ・マージ
* 結合リストとして再現。結合後ストリームを OUTPUT PROCEDURE(2000MRGOUTSOR) に渡す。
* - 同一社員内で TYPE='S'(給与) は WRK-SALARY-AMOUNT/WRK-BANK-CODE/WRK-ACCOUNT-NO に、
* TYPE='B'(賞与) は WRK-BONUS-AMOUNT に集約、合算して PAYMENT-DATA を出力。
*/
public class Kyu09MrgMain {
// ==================== コンスタント領域 CNSARA ====================
/** 03 CNS-PRGIDX PIC X(008) VALUE 'KYU09MRG'. */
private static final String CST_PRGIDX = "KYU09MRG";
/** 03 CNS-MSGSTR PIC 9(003) VALUE 001. 開始メッセージ */
private static final int CST_MSGSTR = 1;
/** 03 CNS-MSGFIN PIC 9(003) VALUE 002. 終了メッセージ */
private static final int CST_MSGFIN = 2;
/** 03 CNS-MSGSUBEEK PIC 9(003) VALUE 005. サブエラーメッセージ */
private static final int CST_MSGSUBEEK = 5;
/** 03 CNS-MSGIINKES PIC 9(003) VALUE 006. 入力件数メッセージ */
private static final int CST_MSGIINKES = 6;
/** 03 CNS-MSGOUTKES PIC 9(003) VALUE 007. 出力件数メッセージ */
private static final int CST_MSGOUTKES = 7;
/** 03 CNS-MSGKEYINF PIC 9(003) VALUE 033. コンパイル日時メッセージ */
private static final int CST_MSGKEYINF = 33;
/** 03 CNS-KN0002 PIC 9(001) VALUE 2. */
private static final int CST_KN0002 = 2;
/** 03 CNS-ABD999 PIC 9(003) VALUE 999. */
private static final int CST_ABD999 = 999;
/** 03 CNS-PAY-TYPE-SALARY PIC X(001) VALUE 'S'. */
private static final String CST_PAY_TYPE_SALARY = "S";
/** 03 CNS-PAY-TYPE-BONUS PIC X(001) VALUE 'B'. */
private static final String CST_PAY_TYPE_BONUS = "B";
/** 03 CNS-PAY-TYPE-TOTAL PIC X(001) VALUE 'T'. */
private static final String CST_PAY_TYPE_TOTAL = "T";
/** 88 WRK-MERGE-EOF-Y の HIGH-VALUESPIC X(008) の最大値)相当。 */
private static final String HIGH_VALUES_EMP_ID = "\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF\u00FF";
// ==================== ファイル定義(SELECT ASSIGN 相当) ====================
/** SELECT SALARY-FILE ASSIGN TO KYU09M01.SALARY-NET 入力 F200B */
private static final String SAL_FILE = "data/KYU09M01.txt";
/** SELECT BONUS-FILE ASSIGN TO KYU09M02.BONUS-DATA 入力 F200B */
private static final String BON_FILE = "data/KYU09M02.txt";
/** SELECT W01OUTFIL ASSIGN TO KYU09W01.PAYMENT-DATA 出力 F200B */
private static final String W01OUT_FILE = "data/KYU09W01.txt";
/** SELECT W02OUTFIL ASSIGN TO KYU09W02.ERROR-LOG 出力 VB200B */
private static final String W02OUT_FILE = "data/KYU09W02.txt";
// ==================== カウンタ領域 CUNARACOMP-3 → long ====================
/** 03 CUN-W01OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW01Out;
/** 03 CUN-W02OUT PIC S9(009) COMP-3 VALUE ZERO. */
private long cunW02Out;
// ==================== 作業領域 WRKARA ====================
/** 03 WRK-MERGE-EOF PIC X(001). 読込フラグ(88 WRK-MERGE-EOF-Y VALUE '1' */
private String wrkMergeEof = "0";
/** 03 WRK-CURRENT-EMP PIC X(008). */
private String wrkCurrentEmp = "";
/** 03 WRK-SALARY-AMOUNT PIC 9(009). */
private int wrkSalaryAmount;
/** 03 WRK-BONUS-AMOUNT PIC 9(009). */
private int wrkBonusAmount;
/** 03 WRK-TOTAL-PAYMENT PIC 9(009). */
private int wrkTotalPayment;
/** 03 WRK-BANK-CODE PIC X(004). */
private String wrkBankCode = "";
/** 03 WRK-ACCOUNT-NO PIC 9(008). */
private int wrkAccountNo;
/** 03 WS-PAD-CHAR PIC X(001) VALUE SPACE. */
private String wsPadChar = "";
/** 03 WS-FILE-STATUS PIC X(002). */
private String wsFileStatus = "";
/** 88 WRK-MERGE-EOF-Y VALUE '1'. */
private boolean isMergeEof() {
return "1".equals(wrkMergeEof);
}
// ==================== サブプログラム連絡領域(LINKAGE COPY → DTO ====================
/** COPY ZANDATAC.SUB01DAT用) */
private final ZandatacPar d01UbsPar = new ZandatacPar();
/** COPY ZANMSGAC.SUB02MSG用) */
private final ZanmsgacPar m00MhoPar = new ZanmsgacPar();
/** COPY ZANENDAC.SUB03END用) */
private final ZanendacPar e01AbdPar = new ZanendacPar();
// ==================== 出力レコード(FILE SECTION → DTO ====================
/** 01 W01OUTREC.FD W01OUTFIL */
private final Kyu06Rec w01 = new Kyu06Rec();
/** 01 W02OUTREC.FD W02OUTFIL */
private final Kyu99Rec w02 = new Kyu99Rec();
// ==================== ファイルハンドル(OPEN/CLOSE 相当) ====================
private PrintWriter w01OutFil;
private PrintWriter w02OutFil;
// ==================== PROCEDURE DIVISION ====================
public static void main(final String[] args) {
new Kyu09MrgMain().run();
}
/**
* 段落 0000MAJCOLSOR:制御処理。
* PERFORM 1000ITTSOR → MERGE実行 → PERFORM 3000STPSOR → GOBACK.
*/
private void run() {
try {
initPara(); // 1000ITTSOR
mrgPara(); // MERGE 実行(0000MAJCOLSOR 内)→ OUTPUT PROCEDURE 2000MRGOUTSOR
termPara(); // 3000STPSOR
} finally {
closeFiles();
}
}
/**
* MERGE 実行(0000MAJCOLSOR 内)。
* COBOL: MERGE MERGE-FILE ON ASCENDING KEY MERGE-EMP-ID
* USING SALARY-FILE BONUS-FILE
* OUTPUT PROCEDURE 2000MRGOUTSOR.
* Java: 2入力ファイルは EMP-ID 昇順済と仮定し、2ポインタ・マージ結合リストを構築、
* そのストリームを 2000MRGOUTSOR に渡す。
*/
private void mrgPara() {
// *** MERGE 作業領域構築(SALARY + BONUS を EMP-ID 昇順に結合)
final List<Kyu04Rec> merged = mergeInputs();
// *** OUTPUT PROCEDURE 2000MRGOUTSOR
mrgOutSor(merged);
}
/**
* 両入力ファイル読込+EMP-ID 昇順マージ結合(COBOL MERGE 文の再現)。
* 入力ファイルは MERGE 文が自動 OPEN するため、ここで開いて読込後にクローズ。
*/
private List<Kyu04Rec> mergeInputs() {
final List<Kyu04Rec> sal = readFile(SAL_FILE); // SALARY-FILEREC-TYPE='S'
final List<Kyu04Rec> bon = readFile(BON_FILE); // BONUS-FILEREC-TYPE='B'
final List<Kyu04Rec> merged = new ArrayList<>();
int i = 0;
int j = 0;
// USING 順(SALARY 優先)で同キー時の安定性を維持
while (i < sal.size() && j < bon.size()) {
final int cmp = sal.get(i).getR01EmpId().compareTo(bon.get(j).getR01EmpId());
if (cmp <= 0) {
merged.add(sal.get(i));
i++;
} else {
merged.add(bon.get(j));
j++;
}
}
while (i < sal.size()) {
merged.add(sal.get(i));
i++;
}
while (j < bon.size()) {
merged.add(bon.get(j));
j++;
}
return merged;
}
/** 入力ファイル読込(OPEN INPUT / READ AT END 相当)。 */
private List<Kyu04Rec> readFile(final String path) {
final List<Kyu04Rec> list = new ArrayList<>();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (line.isEmpty()) {
continue; // 空行はスキップ
}
// READ → MOVE 行 TO レコード(200B 固定長)
final Kyu04Rec r = new Kyu04Rec();
r.parseRecord(line); // READ → MOVE 行 TO レコード(200B 固定長)
list.add(r);
}
} catch (IOException e) {
throw new UncheckedIOException(e); // READ/OPEN 異常 ≈ ABEND
}
return list;
}
/**
* 段落 1000ITTSOR:初期処理。
* 開始メッセージ→コンパイル日時→ワーク初期化→運用日付取得→出力ファイル OPEN。
*/
private void initPara() {
// *** 開始メッセージ出力
msgOutPara(CST_MSGSTR, null, null);
// *** コンパイル日時出力 ※FUNCTION WHEN-COMPILED→起動時刻で代用
final String compiled = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
msgOutPara(CST_MSGKEYINF, compiled, "COMPILED");
// *** ワークエリア初期化 INITIALIZE WRKARA.
wrkMergeEof = "0";
wrkCurrentEmp = "";
wrkSalaryAmount = 0;
wrkBonusAmount = 0;
wrkTotalPayment = 0;
wrkBankCode = "";
wrkAccountNo = 0;
cunW01Out = 0;
cunW02Out = 0;
wsPadChar = "";
wsFileStatus = "";
// *** 運用日付取得 SUB01DAT
d01UbsPar.reset();
Sub01DatSub.execute(d01UbsPar);
if (d01UbsPar.getD01FkiCod() != 0) {
// 復帰コード≠ZERO → サブエラーメッセージ出力 → ABEND
msgOutPara(CST_MSGSUBEEK, "SUB01DAT",
ConvUtil.padLeftZero(d01UbsPar.getD01FkiCod(), 4));
abendPara();
}
// *** OPEN OUTPUT W01OUTFIL W02OUTFIL.(入力は MERGE が自動 OPEN
openFiles();
}
/**
* 段落 2000MRGOUTSORMERGE 出力(同一社員統合)処理。
* RETURN MERGE-FILE ループ → 同一社員内で TYPE=S/B を集約 → 合算 → 2100PAYOUTSOR。
*/
private void mrgOutSor(final List<Kyu04Rec> merged) {
// *** MERGE 結果受取ループ(PERFORM WITH TEST AFTER UNTIL WRK-MERGE-EOF-Y
wrkMergeEof = "0";
int idx = 0;
// 外側 RETURN MERGE-FILE INTO MERGE-REC AT END SET WRK-MERGE-EOF-Y TO TRUE
Kyu04Rec rec = returnMerge(merged, idx);
idx = nextIdx;
while (!isMergeEof()) {
// 2-2-1. 現社員番号を WRK-CURRENT-EMP に設定
wrkCurrentEmp = rec.getR01EmpId();
// 2-2-2. WRK-SALARY-AMOUNTWRK-BONUS-AMOUNT を ZERO 初期化
wrkSalaryAmount = 0;
wrkBonusAmount = 0;
wrkTotalPayment = 0;
// 2-2-3. 同一社員内ループ(WRK-CURRENT-EMP ≠ MERGE-EMP-ID まで)
// RETURN は returnMerge 経由で行い、EOF は wrkMergeEof で判定する。
while (!isMergeEof() && wrkCurrentEmp.equals(rec.getR01EmpId())) {
// 2-2-3-1. MERGE-REC-TYPE = 'S'(給与) の場合
if (CST_PAY_TYPE_SALARY.equals(rec.getR01RecType())) {
// MOVE MERGE-PAY-AMOUNT TO WRK-SALARY-AMOUNT.
wrkSalaryAmount = (int) rec.getR01PayAmount();
// MOVE MERGE-GROSS-PAYMENT TO WRK-BANK-CODE.9(9)→X(4) 先頭4桁)
wrkBankCode = ConvUtil.padLeftZero(rec.getR01GrossPayment(), 9).substring(0, 4);
// MOVE MERGE-EMP-ID(1:4) TO WRK-ACCOUNT-NO.X(4)→9(8) 右詰)
wrkAccountNo = ConvUtil.parseIntSafe(rec.getR01EmpId().substring(0, 4), 0);
} else {
// 2-2-3-2. 上記以外(賞与) → MOVE MERGE-PAY-AMOUNT TO WRK-BONUS-AMOUNT.
wrkBonusAmount = (int) rec.getR01PayAmount();
}
// 2-2-3-3. RETURN MERGE-FILE で次レコード取得(AT END は returnMerge が wrkMergeEof='1'
rec = returnMerge(merged, idx);
idx = nextIdx;
}
// 2-2-4. 給与+賞与合算 ADD WRK-SALARY-AMOUNT TO WRK-BONUS-AMOUNT GIVING WRK-TOTAL-PAYMENT
wrkTotalPayment = wrkSalaryAmount + wrkBonusAmount;
// 2-2-5. 2100PAYOUTSOR で PAYMENT-DATA 出力
payOutPara();
// ※外側ループ末尾の RETURN は上記同一社員内ループの最後の RETURN で完了済み。
// 重複 RETURN を削除(旧実装は次社員の先頭レコードを 1 件読み飛ばしていた)。
}
}
/** RETURN MERGE-FILE 擬似実装:idx が範囲外なら EOF をセットして null を返す。 */
private Kyu04Rec returnMerge(final List<Kyu04Rec> merged, final int idx) {
if (idx < merged.size()) {
nextIdx = idx + 1;
return merged.get(idx);
}
wrkMergeEof = "1"; // AT END: SET WRK-MERGE-EOF-Y TO TRUE
nextIdx = idx;
return null;
}
/** returnMerge が次回の読込位置を戻すための作業変数。 */
private int nextIdx;
/**
* 段落 2100PAYOUTSORPAYMENT-DATA 出力処理。
* W01OUTREC 編集 → WRITE W01OUTREC → ADD 1 TO CUN-W01OUT.
*/
private void payOutPara() {
// INITIALIZE W01OUTREC.
w01.reset();
// MOVE WRK-CURRENT-EMP TO W01EMP-ID.
w01.setEmpId(wrkCurrentEmp);
// MOVE CNS-PAY-TYPE-TOTAL('T') TO W01PAY-TYPE.
w01.setPayType(CST_PAY_TYPE_TOTAL);
// MOVE WRK-TOTAL-PAYMENT TO W01AMOUNT.
w01.setAmount(wrkTotalPayment);
// MOVE WRK-BANK-CODE TO W01BANK-CODE.
w01.setBankCode(wrkBankCode);
// MOVE WRK-ACCOUNT-NO TO W01ACCOUNT-NO.
w01.setAccountNo(wrkAccountNo);
// WRITE W01OUTREC. ADD 1 TO CUN-W01OUT.
w01OutFil.println(w01.toRecordLine());
cunW01Out++;
}
/**
* 段落 3000STPSOR:終了処理。
* CLOSE → 入出力件数メッセージ×3 → 終了メッセージ。
*/
private void termPara() {
closeFiles();
// 入力件数メッセージ(PARM1='MERGE-FILE'
// ※COBOL は CUN-W01OUT を流用しており、設計書に準拠し MERGE 入力件数≈出力件数で出力
msgOutPara(CST_MSGIINKES, "MERGE-FILE", ConvUtil.padLeftZero(cunW01Out, 9));
// 出力件数メッセージ(PARM1='KYU09W01'
msgOutPara(CST_MSGOUTKES, "KYU09W01", ConvUtil.padLeftZero(cunW01Out, 9));
// 出力件数メッセージ(PARM1='KYU09W02'
msgOutPara(CST_MSGOUTKES, "KYU09W02", ConvUtil.padLeftZero(cunW02Out, 9));
// 終了メッセージ
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) {
m00MhoPar.reset();
m00MhoPar.setM00MsgCod(msgCod);
if (p1 != null) {
m00MhoPar.setM00Umkdats2201(p1);
}
if (p2 != null) {
m00MhoPar.setM00Umkdats2202(p2);
}
// MOVE CNS-KN0002 TO M00UMKDATS22-03(1:1) / -04(1:1).
m00MhoPar.setM00Umkdats2203(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30));
// MOVE CNS-PRGIDX TO M00UMKDATS22-05.
m00MhoPar.setM00Umkdats2205(CST_PRGIDX);
Sub02MsgSub.execute(m00MhoPar);
}
/**
* 段落 9999ABDSORABEND 処理。
* MOVE 999 TO E01ABDCOD.CALL 'SUB03END' USING E01ABDPAR.
*/
private void abendPara() {
e01AbdPar.reset();
e01AbdPar.setE01AbdCod(CST_ABD999);
Sub03EndSub.execute(e01AbdPar);
}
/**
* 段落 0010DIRECTSORENTRY 'KYU09ENT' 代替エントリポイント(直接結合モード)。
* COBOL は ENTRY 文による別入口だが Java に同等が無いため、main からは呼ばれない
* 独立メソッドとして再現。簡易エラーログ('DIRECT ENTRY')を W02 へ出力。
*/
private void dirSor() {
// MOVE 'DIRECT ENTRY' TO W02ERR-DETAIL.
w02.reset();
w02.setErrDetail("DIRECT ENTRY");
// WRITE W02OUTREC. ADD 1 TO CUN-W02OUT.
w02OutFil.println(w02.toRecordLine());
cunW02Out++;
}
// ==================== ファイル操作補助(OPEN/CLOSE/WRITE 相当) ====================
/** OPEN OUTPUT W01OUTFIL W02OUTFIL. */
private void openFiles() {
try {
w01OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), StandardCharsets.UTF_8));
w02OutFil = new PrintWriter(
new OutputStreamWriter(new FileOutputStream(W02OUT_FILE), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new UncheckedIOException(e); // OPEN 失敗 ≈ ABEND
}
}
/** CLOSE W01OUTFIL W02OUTFIL. */
private void closeFiles() {
closeQuiet(w01OutFil);
closeQuiet(w02OutFil);
w01OutFil = null;
w02OutFil = null;
}
private static void closeQuiet(final Object handle) {
try {
if (handle instanceof PrintWriter) {
((PrintWriter) handle).close();
}
} catch (Exception e) {
// CLOSE 異常は無視(原程序も FILE STATUS 未管理)
}
}
}
+138
View File
@@ -0,0 +1,138 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* ============================================================
* 【COBOL→Java 标准例】主模板(常用语法映射)
* ============================================================
* 程序整体结构对应:
* IDENTIFICATION DIVISION. PROGRAM-ID. SAMPLE01. → 本类名 Sample01Main
* ENVIRONMENT DIVISION. → (无直接对应,文件定义见 FileSample
* DATA DIVISION. WORKING-STORAGE SECTION. → 类的字段区
* PROCEDURE DIVISION. 各段落 → 各 private 方法(xxxPara
* STOP RUN → main 方法正常返回
*
* 命名规范:
* COBOL 变量 WS-XXX → Java 字段 wsXxx
* COBOL 段落 XXX-PARA → Java 方法 xxxPara()
* COBOL 常量 / 88级 → static final 常量 或 isXxx() 判定方法
*/
public class Sample01Main {
// ==================== WORKING-STORAGE SECTION ====================
// 01 WS-CST.VALUE 子句的常量定义 → static final 常量)
private static final BigDecimal CST_TAX_RATE = new BigDecimal("0.08"); // 77 CST-TAX-RATE PIC V99 VALUE 0.08.
private static final String CST_DEPT_SALES = "10"; // 78 CST-DEPT-SALES VALUE '10'.
private static final String CST_DEPT_DEV = "20"; // 78 CST-DEPT-DEV VALUE '20'.
// 01 WS-WORK.(各 05 层项目 → 私有字段,类型按 ConvUtil 头部规则映射)
private String wsDept = ""; // 05 WS-DEPT PIC X(2).
private BigDecimal wsAmount = BigDecimal.ZERO; // 05 WS-AMOUNT PIC 9(5)V99.
private BigDecimal wsTax = BigDecimal.ZERO; // 05 WS-TAX PIC 9(3)V99.
private int wsIdx = 0; // 05 WS-IDX PIC 9(3).
private String wsEndFlg = "0"; // 05 WS-END-FLG PIC X(1) VALUE '0'.
// 88 END-ON VALUE '1'.88 级条件名 → 常量 + 判定方法)
private static final String FLG_ON = "1";
private boolean isEndOn() {
return FLG_ON.equals(wsEndFlg);
}
// ==================== PROCEDURE DIVISION ====================
public static void main(String[] args) {
new Sample01Main().run(); // PERFORM RUN-PARA 相当
}
/**
* 主控制段落。
* COBOL:
* MAIN-PROC.
* PERFORM INIT-PROC THRU TERM-PROC.
* STOP RUN.
*/
private void run() {
initPara();
calcPara();
judgePara();
loopPara();
fileProcPara();
termPara();
// STOP RUN → main 正常返回即结束
}
/** 初期处理段落(MOVE / INITIALIZE 的映射) */
private void initPara() {
// MOVE 1000 TO WS-AMOUNT.
wsAmount = new BigDecimal("1000.00");
// MOVE "10" TO WS-DEPT.
wsDept = CST_DEPT_SALES;
// INITIALIZE WS-WORK.(字段恢复初值:数值→ZERO、文字→SPACE)
wsTax = BigDecimal.ZERO;
wsIdx = 0;
wsEndFlg = "0";
}
/** 计算处理段落(COMPUTE / ADD / SUBTRACT / ROUNDED 的映射) */
private void calcPara() {
// COMPUTE WS-TAX ROUNDED = WS-AMOUNT * CST-TAX-RATE.
wsTax = wsAmount.multiply(CST_TAX_RATE).setScale(0, RoundingMode.HALF_UP);
// ADD 100 TO WS-AMOUNT. / SUBTRACT 50 FROM WS-AMOUNT.
wsAmount = wsAmount.add(BigDecimal.valueOf(100)).subtract(BigDecimal.valueOf(50));
// DISPLAY "WS-AMOUNT=" WS-AMOUNT " WS-TAX=" WS-TAX.
System.out.println("[calc] amount=" + wsAmount.toPlainString() + " tax=" + wsTax.toPlainString());
}
/** 判定处理段落(IF / EVALUATE 的映射) */
private void judgePara() {
// IF WS-DEPT = "10" ... ELSE ... END-IF.
if (CST_DEPT_SALES.equals(wsDept)) {
System.out.println("[judge] dept=SALES");
} else {
System.out.println("[judge] dept=OTHER");
}
// EVALUATE WS-DEPT
// WHEN "10" ... WHEN "20" ... WHEN OTHER ...
// END-EVALUATE.
switch (wsDept) {
case CST_DEPT_SALES:
System.out.println("[eval] branch=SALES DEPT");
break;
case CST_DEPT_DEV:
System.out.println("[eval] branch=DEV DEPT");
break;
default:
System.out.println("[eval] branch=OTHER");
break;
}
}
/** 反复处理段落(PERFORM UNTIL / PERFORM VARYING 的映射) */
private void loopPara() {
// MOVE 0 TO WS-END-FLG. PERFORM UNTIL WS-END-FLG = '1' ... END-PERFORM.
wsEndFlg = "0";
while (!isEndOn()) {
wsIdx++;
if (wsIdx >= 3) {
wsEndFlg = FLG_ON; // MOVE '1' TO WS-END-FLG.
}
}
// PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 5. → for 文
for (int i = 1; i <= 5; i++) {
// 段内处理
}
System.out.println("[loop] idx=" + wsIdx);
}
/** 文件处理段落(READ / WRITE 的映射 → FileSample 参照) */
private void fileProcPara() {
FileSample.execute();
}
/** 终了处理段落 */
private void termPara() {
System.out.println("[term] SAMPLE01 NORMAL END");
}
}
+34
View File
@@ -0,0 +1,34 @@
import dto.ZandatacPar;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* ============================================================
* 【COBOL→Java】SUB01DAT 運用日付取得サブ
* ============================================================
* PROGRAM-ID. SUB01DAT → 类 Sub01DatSub
* LINKAGE: D01UBSPARCOPY ZANDATAC → dto.ZandatacPar
* 処理概要: FUNCTION CURRENT-DATE により現在日付を取得し
* YYYYMMDD 形式で返す。
*
* ※FUNCTION CURRENT-DATE26桁 YYYYMMDDhhmmsscc…)の先頭8桁を使用。
*/
public class Sub01DatSub {
private static final DateTimeFormatter YMD_FMT =
DateTimeFormatter.ofPattern("yyyyMMdd");
/**
* PROCEDURE DIVISION USING D01UBSPAR 段落 0000MAINSOR。
*/
public static void execute(final ZandatacPar par) {
// MOVE FUNCTION CURRENT-DATE TO WRK-SYS-DATE.X(026)
String wrkSysDate = LocalDateTime.now().format(YMD_FMT); // 先頭8桁相当
// MOVE WRK-SYS-DATE(1:8) TO D01UBSUDATE.
par.setD01UbsUdate(Integer.parseInt(wrkSysDate.substring(0, 8)));
// MOVE ZERO TO D01FKICOD.(正常終了)
par.setD01FkiCod(0);
}
}
+34
View File
@@ -0,0 +1,34 @@
import dto.ZanmsgacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】SUB02MSG メッセージ編集出力サブ
* ============================================================
* PROGRAM-ID. SUB02MSG → 类 Sub02MsgSub
* LINKAGE: M00MHOPARCOPY ZANMSGAC → dto.ZanmsgacPar
* 処理概要: メッセージ番号とパラメータを編集し標準出力へ出力する。
*
* ※STRING文は DELIMITED 句省略時 = DELIMITED BY SIZE
* 各 X(30) 置換項目は后尾空格含め30桁そのまま連結されるため
* Java 侧也按 padRight(30) 忠实再现。
* ※DISPLAY は PIC X(303) 全長出力(后尾空格含む)。
*/
public class Sub02MsgSub {
/**
* PROCEDURE DIVISION USING M00MHOPAR 段落 0000MAINSOR。
*/
public static void execute(final ZanmsgacPar par) {
// STRING 'MSG[' M00MSGCOD '] P1=' ... INTO WRK-MSG-LINE END-STRING.
String line = "MSG["
+ ConvUtil.padLeftZero(par.getM00MsgCod(), 3) + "] P1="
+ ConvUtil.padRight(par.getM00Umkdats2201(), 30) + " P2="
+ ConvUtil.padRight(par.getM00Umkdats2202(), 30) + " P3="
+ ConvUtil.padRight(par.getM00Umkdats2203(), 30) + " P4="
+ ConvUtil.padRight(par.getM00Umkdats2204(), 30) + " P5="
+ ConvUtil.padRight(par.getM00Umkdats2205(), 30);
// DISPLAY WRK-MSG-LINE.X(303) 全长,含后尾空格)
System.out.println(ConvUtil.padRight(line, 303));
}
}
+34
View File
@@ -0,0 +1,34 @@
import dto.ZanendacPar;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】SUB03END ABEND処理サブ
* ============================================================
* PROGRAM-ID. SUB03END → 类 Sub03EndSub
* LINKAGE: E01ABDPARCOPY ZANENDAC → dto.ZanendacPar
* 処理概要: ABENDコードを表示しプログラムを異常終了させる。
*
* ※STOP RUNABEND)→ 疑问Q4回答により System.exit(コード) にマッピング。
*/
public class Sub03EndSub {
/**
* PROCEDURE DIVISION USING E01ABDPAR 段落 0000MAINSOR。
*/
public static void execute(final ZanendacPar par) {
// MOVE 'ABEND CODE=' TO WRK-ABEND-MSG.X(020) 左詰め)
String wrkAbendMsg = ConvUtil.padRight("ABEND CODE=", 20);
// MOVE E01ABDCOD TO WRK-ABEND-MSG(12:3).1214桁を3桁数字で上書き)
wrkAbendMsg = wrkAbendMsg.substring(0, 11)
+ ConvUtil.padLeftZero(par.getE01AbdCod(), 3)
+ wrkAbendMsg.substring(14);
// DISPLAY WRK-ABEND-MSG.
System.out.println(wrkAbendMsg);
// STOP RUN.(異常終了)
System.exit(par.getE01AbdCod());
}
}
+135
View File
@@ -0,0 +1,135 @@
import dto.ZanchkacPar;
/**
* ============================================================
* 【COBOL→Java】SUB04CHK 項目チェックサブ
* ============================================================
* PROGRAM-ID. SUB04CHK → 类 Sub04ChkSub
* LINKAGE: C01CHKPARCOPY ZANCHKAC → dto.ZanchkacPar
* 処理概要: 指定されたタイプによりデータ項目の妥当性をチェックする。
* DATE : YYYYMMDD 月日範囲チェック NG=0001
* TIME : HHMM 時分範囲チェック NG=0002
* NUM : 数字・空白のみか NG=0003
* EMPID : 英数字8桁 NG=0004
* OTHER : 型誤り 9999
*
* ※原代码各段落内的 GO TO xxx-EXT 均映射为方法提前 return(GO TO 方针:保持原结构)。
* ※NUMVAL 对非数字数据会运行时异常,对应 Java 的 NumberFormatException
* (≈ COBOL S0C1/S0C7 ABEND),保持一致不另行捕获。
*/
public class Sub04ChkSub {
/**
* PROCEDURE DIVISION USING C01CHKPAR 段落 0000MAINSOR。
* EVALUATE C01CHKTYP ... END-EVALUATE.
*/
public static void execute(final ZanchkacPar par) {
int rc;
switch (par.getC01ChkTyp().trim()) {
case "DATE":
rc = chkDatePara(par.getC01ChkDat());
break;
case "TIME":
rc = chkTimePara(par.getC01ChkDat());
break;
case "NUM":
rc = chkNumPara(par.getC01ChkDat());
break;
case "EMPID":
rc = chkEmpPara(par.getC01ChkDat());
break;
default:
rc = 9999; // WHEN OTHER: MOVE 9999 TO WRK-RETURN-CODE
break;
}
// MOVE WRK-RETURN-CODE TO C01CHKRRC.(復帰コード設定して復帰)
par.setC01ChkRrc(rc);
}
/**
* 1000CHKDATEYYYYMMDD 形式の妥当性チェック。
* ※月(5:2)/日(7:2) のみ範囲判定(原逻辑如此,不做历法校验)。
*/
private static int chkDatePara(final String dat) {
// MOVE FUNCTION NUMVAL(C01CHKDAT(5:2)) TO WRK-NN.
int nn = Integer.parseInt(dat.substring(4, 6));
if (nn < 1 || nn > 12) {
return 1; // GO TO 1000CHKDATE-EXT 相当
}
// MOVE FUNCTION NUMVAL(C01CHKDAT(7:2)) TO WRK-NN.
nn = Integer.parseInt(dat.substring(6, 8));
if (nn < 1 || nn > 31) {
return 1;
}
return 0; // 正常
}
/**
* 2000CHKTIMEHHMM 形式の妥当性チェック。
*/
private static int chkTimePara(final String dat) {
// MOVE FUNCTION NUMVAL(C01CHKDAT(1:2)) TO WRK-NN.(時)
int nn = Integer.parseInt(dat.substring(0, 2));
if (nn > 23) {
return 2; // GO TO 2000CHKTIME-EXT 相当
}
// MOVE FUNCTION NUMVAL(C01CHKDAT(3:2)) TO WRK-NN.(分)
nn = Integer.parseInt(dat.substring(2, 4));
if (nn > 59) {
return 2;
}
return 0; // 正常
}
/**
* 3000CHKNUM:全桁が数字であることのチェック。
* ※全空白は正常扱い(オプショナル項目)。
*/
private static int chkNumPara(final String dat) {
// INSPECT TALLYING WRK-NN FOR LEADING SPACES.(先行空白数)
int leading = 0;
while (leading < dat.length() && dat.charAt(leading) == ' ') {
leading++;
}
if (leading == dat.length()) {
return 0; // 全スペース → 正常(GO TO EXT 相当)
}
// INSPECT TALLYING FOR ALL "0".."9" SPACE.
// (数字と空白以外が一つでもあれば NG)
for (int i = 0; i < dat.length(); i++) {
char c = dat.charAt(i);
if (c != ' ' && (c < '0' || c > '9')) {
return 3; // GO TO 3000CHKNUM-EXT 相当
}
}
return 0; // 正常
}
/**
* 4000CHKEMP:英数字8桁のチェック。
*/
private static int chkEmpPara(final String dat) {
// IF C01CHKDAT(9:72) NOT = SPACES THEN NG8桁超過データあり)
if (!dat.substring(8).trim().isEmpty()) {
return 4; // GO TO 4000CHKEMP-EXT 相当
}
// IF C01CHKDAT(1:8) = SPACES THEN NG(必須エラー)
if (dat.substring(0, 8).trim().isEmpty()) {
return 4;
}
// 数字/英字/空白をカウントし合計8桁か判定
// INSPECT TALLYING FOR ALL ... の集合カウント相当)
int cnt = 0;
for (int i = 0; i < 8; i++) {
char c = dat.charAt(i);
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z') || c == ' ') {
cnt++;
}
}
if (cnt != 8) {
return 4; // 英数字以外(特殊文字)あり
}
return 0; // 正常
}
}
+56
View File
@@ -0,0 +1,56 @@
import dto.ZantimacPar;
/**
* ============================================================
* 【COBOL→Java】SUB05TIM 時刻丸め計算サブ
* ============================================================
* PROGRAM-ID. SUB05TIM → 类 Sub05TimSub
* LINKAGE: T01TIMPARCOPY ZANTIMAC → dto.ZantimacPar
* 処理概要: 時間値(T01TIMHRS、1桁小数)を指定単位で丸め、T01TIMOUT に格納。
*
* T01TIMRRC=0: 0.5時間単位・切上(30分単位)
* T01TIMRRC=1: 0.1時間単位・切上(6分単位)← KIN07DAI 使用
* T01TIMRRC=2: 0.1時間単位・切捨(6分単位)
* T01TIMRRC=3: 単純四捨五入(0.1h単位、+0.05 して V9(1) 切捨)
* OTHER : そのまま出力
*
* ※T01TIMHRS は 9(4)V9(1)1桁小数)であるため、内部では「0.1h単位の整数(tenth)」
* に変換して整数演算で丸め、再び 1桁小数へ戻す(浮動小数点誤差回避)。
*/
public class Sub05TimSub {
/**
* PROCEDURE DIVISION USING T01TIMPAR 0000MAINSOR。
*/
public static void execute(final ZantimacPar par) {
// 9(4)V9(1) → 0.1h単位整数への変換。設計(mode2=切捨)に合わせ床関数で切捨。
// ※ZAN05CAL の OVT-HOURS は 0.1h 単位切捨(floor)が仕様。round だと境界で +0.1h ずれる。
int tenth = (int) Math.floor(par.getT01Timhrs() * 10.0);
int outTenth;
switch (par.getT01Timrrc()) {
case 0:
// 0.5h単位(=5/10h)で切上
outTenth = ((tenth + 4) / 5) * 5;
break;
case 1:
// 0.1h単位で切上 → 既に 0.1h 単位のためそのまま(ceil(tenth/1)
outTenth = tenth;
break;
case 2:
// 0.1h単位で切捨 → そのまま
outTenth = tenth;
break;
case 3:
// 0.1h単位四捨五入((hrs+0.05) して V9(1) 切捨)→ 1桁小数入力では no-op
outTenth = tenth;
break;
default:
// モード指定なし → そのまま
outTenth = tenth;
break;
}
par.setT01Timout(outTenth / 10.0);
}
}
+349
View File
@@ -0,0 +1,349 @@
import dto.ZanchkacPar;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.Zan01Rec;
import dto.Zan05Rec;
import dto.ZanmsgacPar;
import util.ConvUtil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* ============================================================
* 銆怌OBOL鈫扟ava銆慞ROGRAM-ID. ZAN01CHK 鈫?绫?Zan01ChkMain
* ============================================================
* 娈嬫キ鐢宠珛鎸垎鍑︾悊: CSV(OVT-APPLY) 銈掕銇胯炯銇裤€併偣銉嗐兗銈裤偣銇ф尟銈婂垎銇戙€? * 銈广儐銉笺偪銈?0/1 鈫?闋呯洰銉併偋銉冦偗寰?OVT-VALID(W01) 銇俱仧銇?ERROR-LOG(W03)
* 銈广儐銉笺偪銈?9 鈫?OVT-CANCEL(W02)
* 銇濄伄浠? 鈫?ERROR-LOG(W03)
* 鈥昏ō瑷堟浉(瑭崇窗瑷▓鏇竉ZAN01CHK) 鍎厛銆侰OBOL 銇ㄦ暣鍚堛€? */
public class Zan01ChkMain {
// ---- 銈炽兂銈广偪銉炽儓 ----
private static final String PRGIDX = "ZAN01CHK";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGSUBEEK = 5;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final int CNS_KN0002 = 2;
private static final int CNS_ABD999 = 999;
private static final int CNS_TIME_1830 = 1830;
private static final int CNS_DIFF_30 = 30;
private static final String R01_PATH = "data/ZAN01R01.txt";
private static final String W01_PATH = "data/ZAN01W01.txt";
private static final String W02_PATH = "data/ZAN01W02.txt";
private static final String W03_PATH = "data/ZAN01W03.txt"; // ERROR-LOG
// ---- 銈偊銉炽偪 ----
private long cunR01Inn = 0;
private long cunW01Out = 0;
private long cunW02Out = 0;
private long cunW03Out = 0;
// ---- 浣滄キ闋樺煙 ----
private int wrkU06;
private String wrkR01Eof = "0";
private String r01Line = "";
private String csvApplId = "";
private String csvEmpId = "";
private String csvApplDate = "";
private String csvStartTime = "";
private String csvEndTime = "";
private String csvStatus = "";
private String csvOvtType = "";
private String csvFiller = "";
// ---- 銈点儢銉椼儹銈般儵銉犻€g怠闋樺煙 ----
private final ZandatacPar d01 = new ZandatacPar();
private final ZanmsgacPar m00 = new ZanmsgacPar();
private final ZanendacPar e01 = new ZanendacPar();
private final ZanchkacPar c01 = new ZanchkacPar();
// ---- 銉曘偂銈ゃ儷 ----
private BufferedReader r01In;
private BufferedWriter w01Out;
private BufferedWriter w02Out;
private BufferedWriter w03Out;
public static void main(final String[] args) {
new Zan01ChkMain().run();
}
private void run() {
initProc();
while (!"1".equals(wrkR01Eof)) {
majProc();
}
stpProc();
}
// ---- 1000ITTSOR ----
private void initProc() {
msgOut(CNS_MSGSTR, "", "");
msgCompiled();
// 銉兗銈垵鏈熷寲
wrkU06 = 0;
wrkR01Eof = "0";
// 閬嬬敤鏃ヤ粯鍙栧緱
d01.reset();
Sub01DatSub.execute(d01);
if (d01.getD01FkiCod() != 0) {
msgOut(CNS_MSGSUBEEK, "SUB01DAT", String.valueOf(d01.getD01FkiCod()));
abend();
return;
}
wrkU06 = d01.getD01UbsUdate();
// OPEN
try {
r01In = FileIo.openReader(R01_PATH);
w01Out = FileIo.openWriter(W01_PATH);
w02Out = FileIo.openWriter(W02_PATH);
w03Out = FileIo.openWriter(W03_PATH);
} catch (IOException e) {
throw new RuntimeException("FILE OPEN ERROR", e);
}
readR01();
}
// ---- 1100R01INNSOR ----
private void readR01() {
try {
String line = r01In.readLine();
if (line == null) {
wrkR01Eof = "1";
} else {
r01Line = line;
cunR01Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ ERROR", e);
}
}
// ---- 2000MAJSOR ----
private void majProc() {
csvSplit();
if ("0".equals(csvStatus) || "1".equals(csvStatus)) {
validateProc();
} else if ("9".equals(csvStatus)) {
cancelProc();
} else {
errorProc();
}
readR01();
}
// ---- 2010CSVSOR ----
private void csvSplit() {
csvApplId = csvEmpId = csvApplDate = "";
csvStartTime = csvEndTime = csvStatus = csvOvtType = csvFiller = "";
String[] parts = r01Line.split(",", -1);
if (parts.length > 0) {
csvApplId = parts[0].trim();
}
if (parts.length > 1) {
csvEmpId = parts[1].trim();
}
if (parts.length > 2) {
csvApplDate = parts[2].trim();
}
if (parts.length > 3) {
csvStartTime = parts[3].trim();
}
if (parts.length > 4) {
csvEndTime = parts[4].trim();
}
if (parts.length > 5) {
csvStatus = parts[5].trim();
}
if (parts.length > 6) {
csvOvtType = parts[6].trim();
}
if (parts.length > 7) {
csvFiller = parts[7].trim();
}
}
// ---- 2020VALIDATESOR ----
private void validateProc() {
// 绀惧摗鐣彿銉併偋銉冦偗
c01.reset();
c01.setC01ChkTyp("EMPID");
c01.setC01ChkDat(pad8(csvEmpId));
Sub04ChkSub.execute(c01);
if (c01.getC01ChkRrc() != 0) {
writeW03("01", "EMP-ID ERROR:" + csvEmpId);
return;
}
// 鏃ヤ粯銉併偋銉冦偗
c01.reset();
c01.setC01ChkTyp("DATE");
c01.setC01ChkDat(pad8(csvApplDate));
Sub04ChkSub.execute(c01);
if (c01.getC01ChkRrc() != 0) {
writeW03("01", "DATE ERROR:" + csvEmpId);
return;
}
int startNum = ConvUtil.parseIntSafe(csvStartTime, 0);
if (startNum < CNS_TIME_1830) {
writeW03("01", "START TIME < 1830");
return;
}
int endNum = ConvUtil.parseIntSafe(csvEndTime, 0);
if (startNum >= endNum) {
writeW03("01", "START >= END");
return;
}
int hh1 = startNum / 100;
int mm1 = startNum % 100;
int hh2 = endNum / 100;
int mm2 = endNum % 100;
int diffMin = (hh2 - hh1) * 60 + (mm2 - mm1);
if (diffMin < CNS_DIFF_30) {
writeW03("01", "DIFF < 30MIN");
return;
}
// W01鍑哄姏
Zan01Rec rec = new Zan01Rec();
rec.setApplId(csvApplId);
rec.setEmpId(csvEmpId);
rec.setApplDate(csvApplDate);
rec.setStartTime(csvStartTime);
rec.setEndTime(csvEndTime);
rec.setStatus(csvStatus);
rec.setOvtType(csvOvtType);
rec.setFiller(csvFiller);
try {
w01Out.write(rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
}
// ---- 2030CANCELSOR ----
private void cancelProc() {
Zan01Rec rec = new Zan01Rec();
rec.setApplId(csvApplId);
rec.setEmpId(csvEmpId);
rec.setApplDate(csvApplDate);
rec.setStartTime(csvStartTime);
rec.setEndTime(csvEndTime);
rec.setStatus(csvStatus);
rec.setOvtType(csvOvtType);
rec.setFiller(csvFiller);
try {
w02Out.write(rec.toRecordLine());
w02Out.newLine();
cunW02Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W02 ERROR", e);
}
}
// ---- 2040ERRORSOR ----
private void errorProc() {
writeW03("01", "INVALID STATUS:" + csvStatus);
}
private void writeW03(final String category, final String detail) {
Zan05Rec rec = new Zan05Rec();
rec.setErrCategory(category);
rec.setErrDetail(detail);
try {
w03Out.write(rec.toRecordLine());
w03Out.newLine();
cunW03Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W03 ERROR", e);
}
}
// ---- 3000STPSOR ----
private void stpProc() {
try {
if (r01In != null) {
r01In.close();
}
if (w01Out != null) {
w01Out.close();
}
if (w02Out != null) {
w02Out.close();
}
if (w03Out != null) {
w03Out.close();
}
} catch (IOException e) {
throw new RuntimeException("CLOSE ERROR", e);
}
msgInkCnt("ZAN01R01", cunR01Inn);
msgOutCnt("ZAN01W01", cunW01Out);
msgOutCnt("ZAN01W02", cunW02Out);
msgOutCnt("ZAN01W03", cunW03Out);
msgOut(CNS_MSGFIN, "", "");
}
// ---- 4000MSGOUTSOR ----
private void msgOut(final int code, final String p1, final String p2) {
m00.reset();
m00.setM00MsgCod(code);
m00.setM00Umkdats2201(p1);
m00.setM00Umkdats2202(p2);
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgCompiled() {
m00.reset();
m00.setM00MsgCod(CNS_MSGKEYINF);
m00.setM00Umkdats2201("2026-06-14");
m00.setM00Umkdats2202("COMPILED");
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgInkCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGIINKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgOutCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGOUTKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void abend() {
e01.reset();
e01.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(e01);
}
/** EMPID/鏃ヤ粯绛夈伄銉併偋銉冦偗鐢ㄣ伀8妗佸彸瑭般倎(宸﹀煁銈佹枃瀛楀垪闀峰悎銈忋仜). */
private String pad8(final String s) {
return ConvUtil.padRight(s, 8);
}
}
+240
View File
@@ -0,0 +1,240 @@
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.Zan01Rec;
import dto.Zan05Rec;
import dto.ZanmsgacPar;
import util.ConvUtil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* ============================================================
* 銆怌OBOL鈫扟ava銆慞ROGRAM-ID. ZAN02CHK 鈫?绫?Zan02ChkMain
* ============================================================
* 鏅傞枔甯噸瑜囥儊銈с儍銈? OVT-VSORT(R01) 銇悓涓€绀惧摗銉诲悓涓€鏃ヤ粯鍐呫仹
* 鍓嶃儸銈炽兗銉夌祩浜嗘檪鍒?> 鐝鹃枊濮嬫檪鍒?銇倝 ERROR-LOG(W02) 銇嚭鍔涖€? * 銇濄倢浠ュ銇?OVT-NODUP(W01) 銇稿嚭鍔涖€? * 鈥昏ō瑷堟浉婧栨嫚銆侰OBOL 銇ㄦ暣鍚堛€? */
public class Zan02ChkMain {
private static final String PRGIDX = "ZAN02CHK";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGSUBEEK = 5;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final int CNS_KN0002 = 2;
private static final int CNS_ABD999 = 999;
private static final int CNS_ERR_CAT02 = 2;
private static final String R01_PATH = "data/ZAN02R01.txt";
private static final String W01_PATH = "data/ZAN02W01.txt";
private static final String W02_PATH = "data/ZAN02W02.txt";
private long cunR01Inn = 0;
private long cunW01Out = 0;
private long cunW02Out = 0;
private int wrkU06;
private String wrkR01Eof = "0";
private String r01Line = "";
private final ZandatacPar d01 = new ZandatacPar();
private final ZanmsgacPar m00 = new ZanmsgacPar();
private final ZanendacPar e01 = new ZanendacPar();
private Zan01Rec r01Rec = new Zan01Rec();
private Zan01Rec prevRec = new Zan01Rec();
private BufferedReader r01In;
private BufferedWriter w01Out;
private BufferedWriter w02Out;
public static void main(final String[] args) {
new Zan02ChkMain().run();
}
private void run() {
initProc();
while (!"1".equals(wrkR01Eof)) {
majProc();
}
stpProc();
}
private void initProc() {
msgOut(CNS_MSGSTR, "", "");
msgCompiled();
wrkU06 = 0;
wrkR01Eof = "0";
d01.reset();
Sub01DatSub.execute(d01);
if (d01.getD01FkiCod() != 0) {
msgOut(CNS_MSGSUBEEK, "SUB01DAT", String.valueOf(d01.getD01FkiCod()));
abend();
return;
}
wrkU06 = d01.getD01UbsUdate();
try {
r01In = FileIo.openReader(R01_PATH);
w01Out = FileIo.openWriter(W01_PATH);
w02Out = FileIo.openWriter(W02_PATH);
} catch (IOException e) {
throw new RuntimeException("FILE OPEN ERROR", e);
}
readR01();
// 1浠剁洰: W01鍑哄姏 + 鍓嶃儸銈炽兗銉夊垵鏈熷寲
if (!"1".equals(wrkR01Eof)) {
r01Rec.fromRecordLine(r01Line);
prevRec.fromRecordLine(r01Line);
try {
w01Out.write(r01Rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
readR01();
}
}
private void readR01() {
try {
String line = r01In.readLine();
if (line == null) {
wrkR01Eof = "1";
} else {
r01Line = line;
cunR01Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ ERROR", e);
}
}
private void majProc() {
if (!"1".equals(wrkR01Eof)) {
r01Rec.fromRecordLine(r01Line);
checkDupProc();
readR01();
}
}
private void checkDupProc() {
if (prevRec.getEmpId().equals(r01Rec.getEmpId())
&& prevRec.getApplDate().equals(r01Rec.getApplDate())) {
int prevEnd = ConvUtil.parseIntSafe(prevRec.getEndTime(), 0);
int curStart = ConvUtil.parseIntSafe(r01Rec.getStartTime(), 0);
if (prevEnd > curStart) {
overlapErrProc();
} else {
normalOutProc();
}
} else {
keyBrProc();
}
}
private void overlapErrProc() {
Zan05Rec rec = new Zan05Rec();
rec.setErrCategory(String.valueOf(CNS_ERR_CAT02));
rec.setErrDetail("OVERLAP: " + prevRec.getEmpId() + "," + prevRec.getApplDate());
try {
w02Out.write(rec.toRecordLine());
w02Out.newLine();
cunW02Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W02 ERROR", e);
}
}
private void normalOutProc() {
try {
w01Out.write(r01Rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
prevRec.fromRecordLine(r01Rec.toRecordLine());
}
private void keyBrProc() {
prevRec.fromRecordLine(r01Rec.toRecordLine());
normalOutProc();
}
private void stpProc() {
try {
if (r01In != null) {
r01In.close();
}
if (w01Out != null) {
w01Out.close();
}
if (w02Out != null) {
w02Out.close();
}
} catch (IOException e) {
throw new RuntimeException("CLOSE ERROR", e);
}
msgInkCnt("ZAN02R01", cunR01Inn);
msgOutCnt("ZAN02W01", cunW01Out);
msgOutCnt("ZAN02W02", cunW02Out);
msgOut(CNS_MSGFIN, "", "");
}
private void msgOut(final int code, final String p1, final String p2) {
m00.reset();
m00.setM00MsgCod(code);
m00.setM00Umkdats2201(p1);
m00.setM00Umkdats2202(p2);
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgCompiled() {
m00.reset();
m00.setM00MsgCod(CNS_MSGKEYINF);
m00.setM00Umkdats2201("2026-06-14");
m00.setM00Umkdats2202("COMPILED");
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgInkCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGIINKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgOutCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGOUTKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void abend() {
e01.reset();
e01.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(e01);
}
}
+337
View File
@@ -0,0 +1,337 @@
import dto.Zan01Rec;
import dto.Zan05Rec;
import dto.ZanmsgacPar;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* ============================================================
* 銆怌OBOL鈫扟ava銆慞ROGRAM-ID. ZAN03CHK 鈫?绫?Zan03ChkMain
* ============================================================
* 鎵撳埢鏅傞枔鐓у悎: OVT-NODUP(R01) 銇?PUNCH(R02) 銈掋偔銉?绀惧摗+鐢宠珛鏃?銇х獊鍚堛€? * 涓€鑷淬仐鏅傞枔甯亴鍑哄嫟銆滈€€鍕ょ瘎鍥插唴 鈫?OVT-TYPE浠樺姞銇椼仸 OVT-CHECKED(W01)銆? * 绡勫洸澶栥伨銇熴伅涓嶄竴鑷?鈫?ERROR-LOG(W02) 銈儐銈淬儶03銆? * 浼戞棩鍒ゅ畾銇?R03(浼戞棩銉曘偂銈ゃ儷) 銇ц銇嗐€? * 鈥籞AN03 銇?SUB01DAT 鏈娇鐢?FUNCTION CURRENT-DATE)銆傝ō瑷堟浉婧栨嫚銆? */
public class Zan03ChkMain {
private static final String PRGIDX = "ZAN03CHK";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final int CNS_KN0002 = 2;
private static final int CNS_ERR_CAT03 = 3;
private static final String OVT_W = "W";
private static final String OVT_H = "H";
private static final String HIGH = "zzzzzzzzzzzzzzzz"; // 16脳'z' = HIGH-VALUE
private static final String R01_PATH = "data/ZAN03R01.txt";
private static final String R02_PATH = "data/ZAN03R02.txt";
private static final String R03_PATH = "data/ZAN03R03.txt";
private static final String W01_PATH = "data/ZAN03W01.txt";
private static final String W02_PATH = "data/ZAN03W02.txt";
private long cunR01Inn = 0;
private long cunR02Inn = 0;
private long cunR03Inn = 0;
private long cunW01Out = 0;
private long cunW02Out = 0;
private String wrkR01Eof = "0";
private String wrkR02Eof = "0";
private String wrkR03Eof = "0";
private String r01Line = "";
private String r02Line = "";
private String wrkR01Key = "";
private String wrkR02Key = HIGH;
private String wrkOvtTypeSet = OVT_W;
private final ZanmsgacPar m00 = new ZanmsgacPar();
private Zan01Rec r01Rec = new Zan01Rec();
// R02 銈ゃ兂銉┿偆銉抽爡鐩? // R02 インライン項目
private String r02EmpId = "";
private String r02Date = "";
private String r02TimeIn = "";
private String r02TimeOut = "";
// 浼戞棩銉嗐兗銉栥儷
private final List<String[]> holidayTable = new ArrayList<>();
private BufferedReader r01In;
private BufferedReader r02In;
private BufferedReader r03In;
private BufferedWriter w01Out;
private BufferedWriter w02Out;
private static final class Holiday {
final String date;
final String flg;
Holiday(final String d, final String f) {
this.date = d;
this.flg = f;
}
}
public static void main(final String[] args) {
new Zan03ChkMain().run();
}
private void run() {
initProc();
while (!"1".equals(wrkR01Eof)) {
majProc();
}
stpProc();
}
private void initProc() {
msgOut(CNS_MSGSTR, "", "");
msgCompiled();
wrkR01Eof = "0";
wrkR02Eof = "0";
wrkR03Eof = "0";
try {
r01In = FileIo.openReader(R01_PATH);
r02In = FileIo.openReader(R02_PATH);
r03In = FileIo.openReader(R03_PATH);
w01Out = FileIo.openWriter(W01_PATH);
w02Out = FileIo.openWriter(W02_PATH);
} catch (IOException e) {
throw new RuntimeException("FILE OPEN ERROR", e);
}
// 浼戞棩銉曘偂銈ゃ儷瑾炯
holidayLoad();
readR01();
readR02();
}
private void holidayLoad() {
try {
String line;
while ((line = r03In.readLine()) != null) {
cunR03Inn++;
String date = line.length() >= 8 ? line.substring(0, 8).trim() : "";
String flg = line.length() >= 9 ? line.substring(8, 9) : " ";
holidayTable.add(new String[]{date, flg});
}
wrkR03Eof = "1";
} catch (IOException e) {
throw new RuntimeException("READ R03 ERROR", e);
}
}
private void readR01() {
try {
String line = r01In.readLine();
if (line == null) {
wrkR01Eof = "1";
} else {
r01Line = line;
r01Rec.fromRecordLine(line);
wrkR01Key = pad8(r01Rec.getEmpId()) + pad8(r01Rec.getApplDate());
cunR01Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R01 ERROR", e);
}
}
private void readR02() {
try {
String line = r02In.readLine();
if (line == null) {
wrkR02Eof = "1";
wrkR02Key = HIGH;
} else {
r02Line = line;
r02EmpId = line.length() >= 8 ? line.substring(0, 8).trim() : "";
r02Date = line.length() >= 16 ? line.substring(8, 16).trim() : "";
r02TimeIn = line.length() >= 20 ? line.substring(16, 20).trim() : "";
r02TimeOut = line.length() >= 24 ? line.substring(20, 24).trim() : "";
wrkR02Key = pad8(r02EmpId) + pad8(r02Date);
cunR02Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R02 ERROR", e);
}
}
private void majProc() {
int cmp = wrkR01Key.compareTo(wrkR02Key);
if (cmp < 0) {
noMatchProc();
readR01();
} else if (cmp == 0) {
matchProc();
readR01();
} else {
readR02();
}
}
private void noMatchProc() {
Zan05Rec rec = new Zan05Rec();
rec.setErrCategory(String.valueOf(CNS_ERR_CAT03));
rec.setErrDetail(r01Rec.getEmpId() + "/" + r01Rec.getApplDate());
try {
w02Out.write(rec.toRecordLine());
w02Out.newLine();
cunW02Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W02 ERROR", e);
}
}
private void matchProc() {
int r01Start = parseInt(r01Rec.getStartTime(), 0);
int r01End = parseInt(r01Rec.getEndTime(), 0);
int punchIn = parseInt(r02TimeIn, 0);
int punchOut = parseInt(r02TimeOut, 0);
if (r01Start >= punchIn && r01End <= punchOut) {
setOvtProc();
try {
w01Out.write(r01Rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
} else {
Zan05Rec rec = new Zan05Rec();
rec.setErrCategory(String.valueOf(CNS_ERR_CAT03));
rec.setErrDetail(r01Rec.getEmpId() + "/" + r01Rec.getApplDate() + "/"
+ r01Rec.getStartTime() + "-" + r01Rec.getEndTime());
try {
w02Out.write(rec.toRecordLine());
w02Out.newLine();
cunW02Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W02 ERROR", e);
}
}
}
private void setOvtProc() {
wrkOvtTypeSet = OVT_W;
String date = r01Rec.getApplDate();
for (String[] h : holidayTable) {
if (h[0].equals(date)) {
// 休日フラグ: COBOL データ慣習は '1'、設計上は 'H' も可。両方受容して運用互換を確保。
if (OVT_H.equals(h[1]) || "1".equals(h[1])) {
wrkOvtTypeSet = OVT_H;
}
break;
}
}
r01Rec.setOvtType(wrkOvtTypeSet);
}
private void stpProc() {
try {
if (r01In != null) {
r01In.close();
}
if (r02In != null) {
r02In.close();
}
if (r03In != null) {
r03In.close();
}
if (w01Out != null) {
w01Out.close();
}
if (w02Out != null) {
w02Out.close();
}
} catch (IOException e) {
throw new RuntimeException("CLOSE ERROR", e);
}
msgInkCnt("ZAN03R01", cunR01Inn);
msgInkCnt("ZAN03R02", cunR02Inn);
msgInkCnt("ZAN03R03", cunR03Inn);
msgOutCnt("ZAN03W01", cunW01Out);
msgOutCnt("ZAN03W02", cunW02Out);
msgOut(CNS_MSGFIN, "", "");
}
private void msgOut(final int code, final String p1, final String p2) {
m00.reset();
m00.setM00MsgCod(code);
m00.setM00Umkdats2201(p1);
m00.setM00Umkdats2202(p2);
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgCompiled() {
m00.reset();
m00.setM00MsgCod(CNS_MSGKEYINF);
m00.setM00Umkdats2201("2026-06-14");
m00.setM00Umkdats2202("COMPILED");
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgInkCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGIINKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgOutCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGOUTKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private String pad8(final String s) {
if (s == null) {
return " ";
}
if (s.length() >= 8) {
return s.substring(0, 8);
}
StringBuilder sb = new StringBuilder(s);
while (sb.length() < 8) {
sb.append(' ');
}
return sb.toString();
}
private int parseInt(final String s, final int def) {
if (s == null) {
return def;
}
String t = s.trim();
if (t.isEmpty()) {
return def;
}
try {
return Integer.parseInt(t);
} catch (NumberFormatException e) {
return def;
}
}
}
+277
View File
@@ -0,0 +1,277 @@
import dto.Zan01Rec;
import dto.Zan02Rec;
import dto.Zan04Rec;
import dto.Zan05Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* ============================================================
* 銆怌OBOL鈫扟ava銆慞ROGRAM-ID. ZAN04MAT 鈫?绫?Zan04MatMain
* ============================================================
* 鍙栨秷銉炪儍銉併兂銈? OVT-SORTED(R01) 銇?OVT-CSORT(R02) 銈掔敵璜嬬暘鍙?APPL-ID)銇? * 1:1 銉炪儍銉併兂銈般仐銆佺祼鏋溿倰鎸倞鍒嗐亼銈嬨€? * 涓€鑷? 鈫?鍙栨秷娓堛伩鐢宠珛銈?ERROR-LOG(W03) 銇嚭鍔涳紙鐩f熁瑷艰贰锛? * R01銇伩 鈫?鏈夊姽鐢宠珛銈?OVT-MATCHED(W01) 銇嚭鍔? * R02銇伩 鈫?鍙栨秷鐢宠珛銈?OVT-DBCLEAN(W02) 銇嚭鍔? * 鈥昏ō瑷堟浉婧栨嫚銆侰OBOL 銇ㄦ暣鍚堛€傘偔銉笺伅 APPL-ID (8B)銆? */
public class Zan04MatMain {
private static final String PRGIDX = "ZAN04MAT";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGSUBEEK = 5;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final int CNS_KN0002 = 2;
private static final int CNS_ABD999 = 999;
private static final int CNS_ERR_CAT04 = 4;
private static final int CNS_PROC_SEQ01 = 1;
private static final String HIGH = "zzzzzzzz"; // 8脳'z' = HIGH-VALUE
private static final String R01_PATH = "data/ZAN04R01.txt";
private static final String R02_PATH = "data/ZAN04R02.txt";
private static final String W01_PATH = "data/ZAN04W01.txt";
private static final String W02_PATH = "data/ZAN04W02.txt";
private static final String W03_PATH = "data/ZAN04W03.txt";
private long cunR01Inn = 0;
private long cunR02Inn = 0;
private long cunW01Out = 0;
private long cunW02Out = 0;
private long cunW03Out = 0;
private String r01Key = HIGH;
private String r02Key = HIGH;
private final ZandatacPar d01 = new ZandatacPar();
private final ZanmsgacPar m00 = new ZanmsgacPar();
private final ZanendacPar e01 = new ZanendacPar();
private Zan01Rec r01Rec = new Zan01Rec();
private Zan01Rec r02Rec = new Zan01Rec();
private BufferedReader r01In;
private BufferedReader r02In;
private BufferedWriter w01Out;
private BufferedWriter w02Out;
private BufferedWriter w03Out;
public static void main(final String[] args) {
new Zan04MatMain().run();
}
private void run() {
initProc();
while (!(HIGH.equals(r01Key) && HIGH.equals(r02Key))) {
int cmp = r01Key.compareTo(r02Key);
if (cmp == 0) {
matchProc();
readR01();
readR02();
} else if (cmp < 0) {
r01OutProc();
readR01();
} else {
r02OutProc();
readR02();
}
}
stpProc();
}
private void initProc() {
msgOut(CNS_MSGSTR, "", "");
msgCompiled();
d01.reset();
Sub01DatSub.execute(d01);
if (d01.getD01FkiCod() != 0) {
msgOut(CNS_MSGSUBEEK, "SUB01DAT", String.valueOf(d01.getD01FkiCod()));
abend();
return;
}
try {
r01In = FileIo.openReader(R01_PATH);
r02In = FileIo.openReader(R02_PATH);
w01Out = FileIo.openWriter(W01_PATH);
w02Out = FileIo.openWriter(W02_PATH);
w03Out = FileIo.openWriter(W03_PATH);
} catch (IOException e) {
throw new RuntimeException("FILE OPEN ERROR", e);
}
readR01();
readR02();
}
private void readR01() {
try {
String line = r01In.readLine();
if (line == null) {
r01Key = HIGH;
} else {
r01Rec.fromRecordLine(line);
r01Key = pad8(r01Rec.getApplId());
cunR01Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R01 ERROR", e);
}
}
private void readR02() {
try {
String line = r02In.readLine();
if (line == null) {
r02Key = HIGH;
} else {
r02Rec.fromRecordLine(line);
r02Key = pad8(r02Rec.getApplId());
cunR02Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R02 ERROR", e);
}
}
private void matchProc() {
Zan05Rec rec = new Zan05Rec();
rec.setErrCategory(String.valueOf(CNS_ERR_CAT04));
rec.setErrDetail("CANCEL-MATCH: " + r01Rec.getApplId() + " "
+ r01Rec.getEmpId() + " " + r01Rec.getApplDate() + " "
+ r01Rec.getStartTime() + " " + r01Rec.getEndTime());
try {
w03Out.write(rec.toRecordLine());
w03Out.newLine();
cunW03Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W03 ERROR", e);
}
}
private void r01OutProc() {
Zan02Rec rec = new Zan02Rec();
rec.setApplId(r01Rec.getApplId());
rec.setEmpId(r01Rec.getEmpId());
rec.setApplDate(r01Rec.getApplDate());
rec.setStartTime(r01Rec.getStartTime());
rec.setEndTime(r01Rec.getEndTime());
rec.setStatus(r01Rec.getStatus());
rec.setOvtType(r01Rec.getOvtType());
rec.setProcSeq(String.valueOf(CNS_PROC_SEQ01));
try {
w01Out.write(rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
}
private void r02OutProc() {
Zan04Rec rec = new Zan04Rec();
rec.setApplId(r02Rec.getApplId());
try {
w02Out.write(rec.toRecordLine());
w02Out.newLine();
cunW02Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W02 ERROR", e);
}
}
private void stpProc() {
try {
if (r01In != null) {
r01In.close();
}
if (r02In != null) {
r02In.close();
}
if (w01Out != null) {
w01Out.close();
}
if (w02Out != null) {
w02Out.close();
}
if (w03Out != null) {
w03Out.close();
}
} catch (IOException e) {
throw new RuntimeException("CLOSE ERROR", e);
}
msgInkCnt("ZAN04R01", cunR01Inn);
msgInkCnt("ZAN04R02", cunR02Inn);
msgOutCnt("ZAN04W01", cunW01Out);
msgOutCnt("ZAN04W02", cunW02Out);
msgOutCnt("ZAN04W03", cunW03Out);
msgOut(CNS_MSGFIN, "", "");
}
private void msgOut(final int code, final String p1, final String p2) {
m00.reset();
m00.setM00MsgCod(code);
m00.setM00Umkdats2201(p1);
m00.setM00Umkdats2202(p2);
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgCompiled() {
m00.reset();
m00.setM00MsgCod(CNS_MSGKEYINF);
m00.setM00Umkdats2201("2026-06-15");
m00.setM00Umkdats2202("COMPILED");
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgInkCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGIINKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgOutCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGOUTKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void abend() {
e01.reset();
e01.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(e01);
}
private String pad8(final String s) {
if (s == null) {
return " ";
}
if (s.length() >= 8) {
return s.substring(0, 8);
}
StringBuilder sb = new StringBuilder(s);
while (sb.length() < 8) {
sb.append(' ');
}
return sb.toString();
}
}
+275
View File
@@ -0,0 +1,275 @@
import dto.Zan02Rec;
import dto.Zan03Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import dto.ZantimacPar;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* ============================================================
* 銆怌OBOL鈫扟ava銆慞ROGRAM-ID. ZAN05CAL 鈫?绫?Zan05CalMain
* ============================================================
* 娈嬫キ鏅傞枔闆嗚▓: OVT-SORTED2(R01, APPL-ID+PROC-SEQ 鏄囬爢) 銈掋偔銉笺儢銉偆銈泦瑷堛仐銆? * 鍚屼竴 APPL-ID 鍐呫伄鍏ㄦ槑绱般伄鍔犵彮鏅傞枔銈掔绠椼仐銇?OVT-SUMMARY(W01) 銇?1 銉偝銉笺儔鍑哄姏銆? * 绌嶇畻鍒嗏啋鏅傚埢涓搞倎銇?SUB05TIM(mode=2: 0.1h 鍗樹綅鍒囨崹) 銈掍娇鐢ㄣ€? * 鈥昏ō瑷堟浉婧栨嫚銆侰OBOL 銇ㄦ暣鍚堛€? */
public class Zan05CalMain {
private static final String PRGIDX = "ZAN05CAL";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGSUBEEK = 5;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final int CNS_KN0002 = 2;
private static final int CNS_TIMMODE02 = 2;
private static final int CNS_ABD999 = 999;
private static final String HIGH = "zzzzzzzz"; // 8脳'z' = HIGH-VALUE
private static final String R01_PATH = "data/ZAN05R01.txt";
private static final String W01_PATH = "data/ZAN05W01.txt";
private long cunR01Inn = 0;
private long cunW01Out = 0;
private String r01Key = HIGH;
private String prevApplId = "";
private boolean groupActive = false;
private String groupStart = "";
private long accumMin = 0;
private String lastApplId = "";
private String lastEmpId = "";
private String lastApplDate = "";
private String lastEnd = "";
private String lastOvtType = "";
private final ZandatacPar d01 = new ZandatacPar();
private final ZanmsgacPar m00 = new ZanmsgacPar();
private final ZanendacPar e01 = new ZanendacPar();
private Zan02Rec r01Rec = new Zan02Rec();
private BufferedReader r01In;
private BufferedWriter w01Out;
public static void main(final String[] args) {
new Zan05CalMain().run();
}
private void run() {
initProc();
while (!(HIGH.equals(r01Key) && !groupActive)) {
if (HIGH.equals(r01Key)) {
if (groupActive) {
outGroupProc();
groupActive = false;
}
} else if (!r01Key.equals(prevApplId)) {
if (groupActive) {
outGroupProc();
}
prevApplId = r01Rec.getApplId();
lastApplId = r01Rec.getApplId();
lastEmpId = r01Rec.getEmpId();
lastApplDate = r01Rec.getApplDate();
lastEnd = r01Rec.getEndTime();
lastOvtType = r01Rec.getOvtType();
groupStart = r01Rec.getStartTime();
groupActive = true;
accumProc();
readR01();
} else {
accumProc();
lastApplId = r01Rec.getApplId();
lastEmpId = r01Rec.getEmpId();
lastApplDate = r01Rec.getApplDate();
lastEnd = r01Rec.getEndTime();
lastOvtType = r01Rec.getOvtType();
readR01();
}
}
stpProc();
}
private void initProc() {
msgOut(CNS_MSGSTR, "", "");
msgCompiled();
d01.reset();
Sub01DatSub.execute(d01);
if (d01.getD01FkiCod() != 0) {
msgOut(CNS_MSGSUBEEK, "SUB01DAT", String.valueOf(d01.getD01FkiCod()));
abend();
return;
}
try {
r01In = FileIo.openReader(R01_PATH);
w01Out = FileIo.openWriter(W01_PATH);
} catch (IOException e) {
throw new RuntimeException("FILE OPEN ERROR", e);
}
readR01();
if (!HIGH.equals(r01Key)) {
prevApplId = r01Rec.getApplId();
lastApplId = r01Rec.getApplId();
lastEmpId = r01Rec.getEmpId();
lastApplDate = r01Rec.getApplDate();
lastEnd = r01Rec.getEndTime();
lastOvtType = r01Rec.getOvtType();
groupStart = r01Rec.getStartTime();
groupActive = true;
}
}
private void readR01() {
try {
String line = r01In.readLine();
if (line == null) {
r01Key = HIGH;
} else {
r01Rec.fromRecordLine(line);
r01Key = pad8(r01Rec.getApplId());
cunR01Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R01 ERROR", e);
}
}
private void accumProc() {
int start = parseInt(r01Rec.getStartTime(), 0);
int end = parseInt(r01Rec.getEndTime(), 0);
int sh = start / 100;
int sm = start % 100;
int eh = end / 100;
int em = end % 100;
int diff = (eh - sh) * 60 + (em - sm);
accumMin += diff;
}
private void outGroupProc() {
double tempHours = accumMin / 60.0;
ZantimacPar t = new ZantimacPar();
t.setT01Timhrs(tempHours);
t.setT01Timrrc(CNS_TIMMODE02);
Sub05TimSub.execute(t);
double outHours = t.getT01Timout();
Zan03Rec rec = new Zan03Rec();
rec.setApplId(lastApplId);
rec.setEmpId(lastEmpId);
rec.setApplDate(lastApplDate);
rec.setStartTime(groupStart);
rec.setEndTime(lastEnd);
rec.setOvtHoursFromDouble(outHours);
rec.setOvtType(lastOvtType);
try {
w01Out.write(rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
accumMin = 0;
}
private void stpProc() {
try {
if (r01In != null) {
r01In.close();
}
if (w01Out != null) {
w01Out.close();
}
} catch (IOException e) {
throw new RuntimeException("CLOSE ERROR", e);
}
msgInkCnt("ZAN05R01", cunR01Inn);
msgOutCnt("ZAN05W01", cunW01Out);
msgOut(CNS_MSGFIN, "", "");
}
private void msgOut(final int code, final String p1, final String p2) {
m00.reset();
m00.setM00MsgCod(code);
m00.setM00Umkdats2201(p1);
m00.setM00Umkdats2202(p2);
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgCompiled() {
m00.reset();
m00.setM00MsgCod(CNS_MSGKEYINF);
m00.setM00Umkdats2201("2026-06-15");
m00.setM00Umkdats2202("COMPILED");
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgInkCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGIINKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgOutCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGOUTKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void abend() {
e01.reset();
e01.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(e01);
}
private String pad8(final String s) {
if (s == null) {
return " ";
}
if (s.length() >= 8) {
return s.substring(0, 8);
}
StringBuilder sb = new StringBuilder(s);
while (sb.length() < 8) {
sb.append(' ');
}
return sb.toString();
}
private int parseInt(final String s, final int def) {
if (s == null) {
return def;
}
String t = s.trim();
if (t.isEmpty()) {
return def;
}
try {
return Integer.parseInt(t);
} catch (NumberFormatException e) {
return def;
}
}
}
+502
View File
@@ -0,0 +1,502 @@
import dto.Zan03Rec;
import dto.Zan04Rec;
import dto.Zan05Rec;
import dto.ZandatacPar;
import dto.ZanendacPar;
import dto.ZanmsgacPar;
import util.ConvUtil;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* ============================================================
* 銆怌OBOL鈫扟ava銆慞ROGRAM-ID. ZAN06UPD 鈫?绫?Zan06UpdMain
* ============================================================
* 娈嬫キ绲辫▓DB鏇存柊: OVT-SUMMARY(R01) 銈?OVT_APPLICATIONS 銇?INSERT(閲嶈鏅?UPDATE)
* 銇椼€丱VT-MONTHLY 銇湀娆¢泦瑷堛倰鍙嶆槧銆侽VT-DBCLEAN(R02) 銇┎褰撶敵璜嬨倰鍙栨秷鐘舵厠銇? * 鏇存柊銇椼€丱VT-MONTHLY 銇嬨倝鍔犵彮鏅傞枔銈掓笡绠椼仚銈嬨€? * 鎺ョ稓鍏? data/OVERTIME.DB (SQLite)銆傘儐銉笺儢銉笉鍦ㄦ檪銇?CREATE TABLE IF NOT EXISTS 銇ц嚜鍕曟绡夈€? * 鈥昏ō瑷堟浉婧栨嫚銆侰OBOL 銇ㄦ暣鍚堛€? */
public class Zan06UpdMain {
private static final String PRGIDX = "ZAN06UPD";
private static final int CNS_MSGSTR = 1;
private static final int CNS_MSGFIN = 2;
private static final int CNS_MSGSUBEEK = 5;
private static final int CNS_MSGIINKES = 6;
private static final int CNS_MSGOUTKES = 7;
private static final int CNS_MSGKEYINF = 33;
private static final int CNS_ABD999 = 999;
private static final int CNS_KN0002 = 2;
private static final int CNS_COMMIT_CNT = 50;
private static final String CNS_STATUS_ACTIVE = "0";
private static final String CNS_STATUS_CANCEL = "9";
private static final String DB_URL = "jdbc:sqlite:data/OVERTIME.DB";
private static final String R01_PATH = "data/ZAN06R01.txt";
private static final String R02_PATH = "data/ZAN06R02.txt";
private static final String W01_PATH = "data/ZAN06W01.txt";
private long cunR01Inn = 0;
private long cunR02Inn = 0;
private long cunW01Out = 0;
private long cunDbxIns = 0;
private long cunDbxUpd = 0;
private long cunCommit = 0;
private boolean r01Eof = false;
private boolean r02Eof = false;
private String wrkApplDateN = "";
private String wrkYearMonth = "";
private String wrkMonth = "";
private int wrkMonthValid = 0;
private String wrkSqlApplId = "";
private String wrkSqlEmpId = "";
private String wrkSqlApplDate = "";
private String wrkSqlYearMonth = "";
private String wrkSqlStartTime = "";
private String wrkSqlEndTime = "";
private BigDecimal wrkSqlOvtHours = BigDecimal.ZERO;
private String wrkSqlOvtType = "";
private String wrkSqlStatus = "";
private int sqlcode = 0;
private int wrkErrCategory = 0;
private String wrkErrDetail = "";
private final ZandatacPar d01 = new ZandatacPar();
private final ZanmsgacPar m00 = new ZanmsgacPar();
private final ZanendacPar e01 = new ZanendacPar();
private Zan03Rec r01Rec = new Zan03Rec();
private Zan04Rec r02Rec = new Zan04Rec();
private BufferedReader r01In;
private BufferedReader r02In;
private BufferedWriter w01Out;
private Connection conn;
public static void main(final String[] args) {
new Zan06UpdMain().run();
}
private void run() {
initProc();
while (!(r01Eof && r02Eof)) {
if (!r01Eof) {
summaryProc();
readR01();
} else if (!r02Eof) {
dbCleanProc();
readR02();
}
}
stpProc();
}
private void initProc() {
msgOut(CNS_MSGSTR, "", "");
msgCompiled();
d01.reset();
Sub01DatSub.execute(d01);
if (d01.getD01FkiCod() != 0) {
msgOut(CNS_MSGSUBEEK, "SUB01DAT", String.valueOf(d01.getD01FkiCod()));
abend();
return;
}
try {
r01In = FileIo.openReader(R01_PATH);
r02In = FileIo.openReader(R02_PATH);
w01Out = FileIo.openWriter(W01_PATH);
} catch (IOException e) {
throw new RuntimeException("FILE OPEN ERROR", e);
}
try {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection(DB_URL);
conn.setAutoCommit(false);
createTables();
} catch (Exception e) {
throw new RuntimeException("DB CONNECT ERROR: " + e.getMessage(), e);
}
readR01();
readR02();
}
private void createTables() throws SQLException {
try (PreparedStatement ps = conn.prepareStatement(
"CREATE TABLE IF NOT EXISTS OVT_APPLICATIONS ("
+ "APPL_ID CHAR(8) NOT NULL PRIMARY KEY, EMP_ID CHAR(8) NOT NULL, "
+ "APPL_DATE CHAR(8) NOT NULL, OVT_TYPE CHAR(1) NOT NULL, "
+ "START_TIME CHAR(4) NOT NULL, END_TIME CHAR(4) NOT NULL, "
+ "OVT_HOURS DECIMAL(4,1) NOT NULL, STATUS CHAR(1) NOT NULL, "
+ "UPDATED_AT TIMESTAMP NOT NULL)")) {
ps.executeUpdate();
}
try (PreparedStatement ps = conn.prepareStatement(
"CREATE TABLE IF NOT EXISTS OVT_MONTHLY ("
+ "EMP_ID CHAR(8) NOT NULL, YEAR_MONTH CHAR(6) NOT NULL, "
+ "OVT_TYPE CHAR(1) NOT NULL, OVT_HOURS DECIMAL(6,1) NOT NULL, "
+ "OVT_COUNT INTEGER NOT NULL, UPDATED_AT TIMESTAMP NOT NULL, "
+ "PRIMARY KEY (EMP_ID, YEAR_MONTH, OVT_TYPE))")) {
ps.executeUpdate();
}
}
private void readR01() {
try {
String line = r01In.readLine();
if (line == null) {
r01Eof = true;
} else {
r01Rec.fromRecordLine(line);
cunR01Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R01 ERROR", e);
}
}
private void readR02() {
try {
String line = r02In.readLine();
if (line == null) {
r02Eof = true;
} else {
r02Rec.fromRecordLine(line);
cunR02Inn++;
}
} catch (IOException e) {
throw new RuntimeException("READ R02 ERROR", e);
}
}
// 2100SUMMARYSOR
private void summaryProc() {
wrkSqlApplId = r01Rec.getApplId();
wrkSqlEmpId = r01Rec.getEmpId();
wrkSqlApplDate = r01Rec.getApplDate();
wrkApplDateN = r01Rec.getApplDate();
wrkMonth = wrkApplDateN.length() >= 6 ? wrkApplDateN.substring(4, 6) : "";
wrkMonthValid = 0;
for (int idx = 1; idx <= 12 && wrkMonthValid == 0; idx++) {
if (String.format("%02d", idx).equals(wrkMonth)) {
wrkMonthValid = 1;
}
}
if (wrkMonthValid == 0) {
wrkErrCategory = 10;
wrkErrDetail = "INVALID MONTH APPL-ID=" + wrkSqlApplId;
writeW01(wrkErrCategory, wrkErrDetail);
abend();
return;
}
wrkYearMonth = wrkApplDateN.length() >= 6 ? wrkApplDateN.substring(0, 6) : "";
wrkSqlYearMonth = wrkYearMonth;
wrkSqlStartTime = r01Rec.getStartTime();
wrkSqlEndTime = r01Rec.getEndTime();
wrkSqlOvtHours = parseOvt(r01Rec.getOvtHours());
wrkSqlOvtType = r01Rec.getOvtType();
wrkSqlStatus = CNS_STATUS_ACTIVE;
String insSql = "INSERT INTO OVT_APPLICATIONS "
+ "(APPL_ID, EMP_ID, APPL_DATE, OVT_TYPE, START_TIME, END_TIME, "
+ "OVT_HOURS, STATUS, UPDATED_AT) VALUES (?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)";
try (PreparedStatement ps = conn.prepareStatement(insSql)) {
ps.setString(1, wrkSqlApplId);
ps.setString(2, wrkSqlEmpId);
ps.setString(3, wrkSqlApplDate);
ps.setString(4, wrkSqlOvtType);
ps.setString(5, wrkSqlStartTime);
ps.setString(6, wrkSqlEndTime);
ps.setBigDecimal(7, wrkSqlOvtHours);
ps.setString(8, wrkSqlStatus);
ps.executeUpdate();
cunDbxIns++;
} catch (SQLException ex) {
sqlcode = ex.getErrorCode();
String updSql = "UPDATE OVT_APPLICATIONS SET STATUS=?, UPDATED_AT=CURRENT_TIMESTAMP "
+ "WHERE APPL_ID=?";
try (PreparedStatement ps = conn.prepareStatement(updSql)) {
ps.setString(1, wrkSqlStatus);
ps.setString(2, wrkSqlApplId);
ps.executeUpdate();
} catch (SQLException ex2) {
dbErr();
return;
}
cunDbxUpd++;
}
cunCommit++;
monthlyUpsert();
if (cunCommit >= CNS_COMMIT_CNT) {
commitDb();
}
}
// 2110MONTHLYUPSOR
private void monthlyUpsert() {
String selSql = "SELECT OVT_HOURS, OVT_COUNT FROM OVT_MONTHLY "
+ "WHERE EMP_ID=? AND YEAR_MONTH=? AND OVT_TYPE=?";
try (PreparedStatement ps = conn.prepareStatement(selSql)) {
ps.setString(1, wrkSqlEmpId);
ps.setString(2, wrkSqlYearMonth);
ps.setString(3, wrkSqlOvtType);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
String updSql = "UPDATE OVT_MONTHLY SET OVT_HOURS=OVT_HOURS + ?, "
+ "OVT_COUNT=OVT_COUNT + 1, UPDATED_AT=CURRENT_TIMESTAMP "
+ "WHERE EMP_ID=? AND YEAR_MONTH=? AND OVT_TYPE=?";
try (PreparedStatement ups = conn.prepareStatement(updSql)) {
ups.setBigDecimal(1, wrkSqlOvtHours);
ups.setString(2, wrkSqlEmpId);
ups.setString(3, wrkSqlYearMonth);
ups.setString(4, wrkSqlOvtType);
ups.executeUpdate();
}
} else {
String insSql = "INSERT INTO OVT_MONTHLY "
+ "(EMP_ID, YEAR_MONTH, OVT_TYPE, OVT_HOURS, OVT_COUNT, UPDATED_AT) "
+ "VALUES (?,?,?,?,1,CURRENT_TIMESTAMP)";
try (PreparedStatement ins = conn.prepareStatement(insSql)) {
ins.setString(1, wrkSqlEmpId);
ins.setString(2, wrkSqlYearMonth);
ins.setString(3, wrkSqlOvtType);
ins.setBigDecimal(4, wrkSqlOvtHours);
ins.executeUpdate();
}
}
}
} catch (SQLException ex) {
sqlcode = ex.getErrorCode();
dbErr();
}
}
// 2200DBCLEANSOR
private void dbCleanProc() {
wrkSqlApplId = r02Rec.getApplId();
String selSql = "SELECT EMP_ID, APPL_DATE, OVT_TYPE, OVT_HOURS FROM OVT_APPLICATIONS "
+ "WHERE APPL_ID=?";
boolean found = false;
try (PreparedStatement ps = conn.prepareStatement(selSql)) {
ps.setString(1, wrkSqlApplId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
found = true;
wrkSqlEmpId = rs.getString(1);
wrkSqlApplDate = rs.getString(2);
wrkSqlOvtType = rs.getString(3);
wrkSqlOvtHours = rs.getBigDecimal(4);
if (wrkSqlOvtHours == null) {
wrkSqlOvtHours = BigDecimal.ZERO;
}
}
}
} catch (SQLException ex) {
sqlcode = ex.getErrorCode();
dbErr();
return;
}
if (!found) {
wrkErrCategory = 20;
wrkErrDetail = "ORPHAN CANCEL APPL-ID=" + wrkSqlApplId;
writeW01(wrkErrCategory, wrkErrDetail);
abend();
return;
}
wrkYearMonth = wrkSqlApplDate.length() >= 6 ? wrkSqlApplDate.substring(0, 6) : "";
wrkSqlYearMonth = wrkYearMonth;
String updSql = "UPDATE OVT_APPLICATIONS SET STATUS=?, UPDATED_AT=CURRENT_TIMESTAMP "
+ "WHERE APPL_ID=?";
try (PreparedStatement ps = conn.prepareStatement(updSql)) {
ps.setString(1, CNS_STATUS_CANCEL);
ps.setString(2, wrkSqlApplId);
ps.executeUpdate();
} catch (SQLException ex) {
sqlcode = ex.getErrorCode();
dbErr();
return;
}
cunDbxUpd++;
cunCommit++;
monthlySub();
if (cunCommit >= CNS_COMMIT_CNT) {
commitDb();
}
}
// 2210MONTHLYSUBSOR
private void monthlySub() {
// COBOL: PERFORM UNTIL WRK-IDX > 3 OR SQLCODE = 0 → 成功(SQLCODE=0)で抜ける。
// 失敗(SQL例外)時のみリトライ。
int retryCnt = 3;
sqlcode = 0;
for (int idx = 1; idx <= retryCnt; idx++) {
String updSql = "UPDATE OVT_MONTHLY SET OVT_HOURS=OVT_HOURS - ?, "
+ "OVT_COUNT=OVT_COUNT - 1, UPDATED_AT=CURRENT_TIMESTAMP "
+ "WHERE EMP_ID=? AND YEAR_MONTH=? AND OVT_TYPE=?";
try (PreparedStatement ps = conn.prepareStatement(updSql)) {
ps.setBigDecimal(1, wrkSqlOvtHours);
ps.setString(2, wrkSqlEmpId);
ps.setString(3, wrkSqlYearMonth);
ps.setString(4, wrkSqlOvtType);
ps.executeUpdate();
sqlcode = 0;
break;
} catch (SQLException ex) {
sqlcode = ex.getErrorCode();
try {
conn.rollback();
} catch (SQLException ex2) {
// ignore
}
}
}
if (sqlcode != 0) {
wrkErrCategory = 21;
wrkErrDetail = "MONTHLY SUBTRACT FAIL APPL-ID=" + wrkSqlApplId;
writeW01(wrkErrCategory, wrkErrDetail);
abend();
return;
}
cunDbxUpd++;
cunCommit++;
if (cunCommit >= CNS_COMMIT_CNT) {
commitDb();
}
}
private void commitDb() {
try {
conn.commit();
} catch (SQLException ex) {
sqlcode = ex.getErrorCode();
dbErr();
return;
}
cunCommit = 0;
}
// 9100DBERRSOR
private void dbErr() {
wrkErrCategory = 30;
wrkErrDetail = "DB ERROR SQLCODE=" + sqlcode + " APPL-ID=" + wrkSqlApplId;
writeW01(wrkErrCategory, wrkErrDetail);
try {
conn.rollback();
} catch (SQLException ex2) {
// ignore
}
cunCommit = 0;
abend();
}
private void writeW01(final int category, final String detail) {
Zan05Rec rec = new Zan05Rec();
rec.setErrCategory(String.valueOf(category));
rec.setErrDetail(detail);
try {
w01Out.write(rec.toRecordLine());
w01Out.newLine();
cunW01Out++;
} catch (IOException e) {
throw new RuntimeException("WRITE W01 ERROR", e);
}
}
private void stpProc() {
if (cunCommit > 0) {
commitDb();
}
try {
if (r01In != null) {
r01In.close();
}
if (r02In != null) {
r02In.close();
}
if (w01Out != null) {
w01Out.close();
}
if (conn != null) {
conn.close();
}
} catch (Exception e) {
throw new RuntimeException("CLOSE ERROR", e);
}
msgInkCnt("ZAN06R01", cunR01Inn);
msgInkCnt("ZAN06R02", cunR02Inn);
msgInkCnt("INS", cunDbxIns);
msgInkCnt("UPD", cunDbxUpd);
msgOutCnt("ZAN06W01", cunW01Out);
msgOut(CNS_MSGFIN, "", "");
}
private void msgOut(final int code, final String p1, final String p2) {
m00.reset();
m00.setM00MsgCod(code);
m00.setM00Umkdats2201(p1);
m00.setM00Umkdats2202(p2);
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgCompiled() {
m00.reset();
m00.setM00MsgCod(CNS_MSGKEYINF);
m00.setM00Umkdats2201("2026-06-16");
m00.setM00Umkdats2202("COMPILED");
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgInkCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGIINKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void msgOutCnt(final String dd, final long cnt) {
m00.reset();
m00.setM00MsgCod(CNS_MSGOUTKES);
m00.setM00Umkdats2201(dd);
m00.setM00Umkdats2202(String.valueOf(cnt));
m00.setM00Umkdats2203(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2204(String.valueOf(CNS_KN0002));
m00.setM00Umkdats2205(PRGIDX);
Sub02MsgSub.execute(m00);
}
private void abend() {
e01.reset();
e01.setE01AbdCod(CNS_ABD999);
Sub03EndSub.execute(e01);
}
private BigDecimal parseOvt(final String s) {
String t = (s == null) ? "" : s.trim();
if (!ConvUtil.isNumeric(t) || t.isEmpty()) {
return BigDecimal.ZERO;
}
return new BigDecimal(t).movePointLeft(1);
}
}
+64
View File
@@ -0,0 +1,64 @@
package dto;
import java.math.BigDecimal;
/**
* ============================================================
* 【COBOL→Java 标准例】记录布局类(DTO)
* ============================================================
* COBOL 的 01 层记录定义 → 一个 DTO 类,各 05 层项目 → 字段。
*
* 01 EMPREC.
* 05 EMP-ID PIC X(5). 社员番号
* 05 EMP-NAME PIC X(20). 氏名
* 05 EMP-DEPT PIC X(2). 部门CD
* 05 EMP-SALARY PIC 9(7)V99. 给料(隐含小数2位)
*
* 固定长记录的拆分在读取侧用 substring 完成,见 FileSample#parseRec。
*/
public class EmpRec {
/** 05 EMP-ID PIC X(5). */
private String empId = "";
/** 05 EMP-NAME PIC X(20). */
private String empName = "";
/** 05 EMP-DEPT PIC X(2). */
private String empDept = "";
/** 05 EMP-SALARY PIC 9(7)V99. → 隐含小数点,用 BigDecimal 承载 */
private BigDecimal empSalary = BigDecimal.ZERO;
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getEmpDept() {
return empDept;
}
public void setEmpDept(String empDept) {
this.empDept = empDept;
}
public BigDecimal getEmpSalary() {
return empSalary;
}
public void setEmpSalary(BigDecimal empSalary) {
this.empSalary = empSalary;
}
}
+177
View File
@@ -0,0 +1,177 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN01REC(勤怠: 休暇申請中間レコード WORK-LEAVE 80B
* ============================================================
* 原定义:COPY KIN01REC REPLACING ==(A)== BY ==W01 → FD W01OUTFIL 的 01 W01OUTREC。
* 文件属性:KIN01W01 / 順編成 / F(固定长) / 80B
*
* 03 (A)APPL-ID PIC 9(009). → applId
* 03 (A)EMP-ID PIC X(008). → empId
* 03 (A)LEAVE-TYPE PIC X(002). → leaveType
* 03 (A)START-DATE PIC 9(008). → startDate
* 03 (A)START-TIME PIC 9(004). → startTime
* 03 (A)END-DATE PIC 9(008). → endDate
* 03 (A)END-TIME PIC 9(004). → endTime
* 03 (A)STATUS PIC X(001). → status
* 03 (A)FILLER PIC X(036). → filler
*/
public class Kin01Rec {
/** 03 W01APPL-ID PIC 9(009). */
private int applId;
/** 03 W01EMP-ID PIC X(008). */
private String empId = "";
/** 03 W01LEAVE-TYPE PIC X(002). */
private String leaveType = "";
/** 03 W01START-DATE PIC 9(008). YYYYMMDD */
private int startDate;
/** 03 W01START-TIME PIC 9(004). HHMM */
private int startTime;
/** 03 W01END-DATE PIC 9(008). YYYYMMDD */
private int endDate;
/** 03 W01END-TIME PIC 9(004). HHMM */
private int endTime;
/** 03 W01STATUS PIC X(001). '1'=有効 / '9'=取消 */
private String status = "";
/** 03 W01FILLER PIC X(036). INITIALIZE 时为 SPACE */
private String filler = "";
/**
* INITIALIZE W01OUTREC 相当:数值→0、文字→SPACE。
*/
public void reset() {
applId = 0;
empId = "";
leaveType = "";
startDate = 0;
startTime = 0;
endDate = 0;
endTime = 0;
status = "";
filler = "";
}
/**
* WRITE W01OUTREC 用:固定长 80B 序列化。
* 各项目按 05 层字节位置拼接(PIC 转换统一走 ConvUtil)。
*/
public String toRecordLine() {
return ConvUtil.padLeftZero(applId, 9) // APPL-ID 9(009) [1..9]
+ ConvUtil.padRight(empId, 8) // EMP-ID X(008) [10..17]
+ ConvUtil.padRight(leaveType, 2) // LEAVE-TYPE X(002) [18..19]
+ ConvUtil.padLeftZero(startDate, 8)// START-DATE 9(008) [20..27]
+ ConvUtil.padLeftZero(startTime, 4)// START-TIME 9(004) [28..31]
+ ConvUtil.padLeftZero(endDate, 8) // END-DATE 9(008) [32..39]
+ ConvUtil.padLeftZero(endTime, 4) // END-TIME 9(004) [40..43]
+ ConvUtil.padRight(status, 1) // STATUS X(001) [44]
+ ConvUtil.padRight(filler, 36); // FILLER X(036) [45..80]
}
public int getApplId() {
return applId;
}
public void setApplId(int applId) {
this.applId = applId;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public String getLeaveType() {
return leaveType;
}
public void setLeaveType(String leaveType) {
this.leaveType = leaveType;
}
public int getStartDate() {
return startDate;
}
public void setStartDate(int startDate) {
this.startDate = startDate;
}
public int getStartTime() {
return startTime;
}
public void setStartTime(int startTime) {
this.startTime = startTime;
}
public int getEndDate() {
return endDate;
}
public void setEndDate(int endDate) {
this.endDate = endDate;
}
public int getEndTime() {
return endTime;
}
public void setEndTime(int endTime) {
this.endTime = endTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = filler;
}
/**
* READ R01INNREC 用:80B 固定長行を解析(COPY KIN01REC → R01 项目)。
* 位置は toRecordLine() と対称:
* APPL-ID[0..9) EMP-ID[9..17) LEAVE-TYPE[17..19) START-DATE[19..27)
* START-TIME[27..31) END-DATE[31..39) END-TIME[39..43) STATUS[43..44)
* FILLER[44..80)
*/
public void fromRecordLine(final String line) {
reset();
String s = (line == null) ? "" : line;
while (s.length() < 80) {
s = s + " ";
}
applId = Integer.parseInt(s.substring(0, 9).trim());
empId = s.substring(9, 17);
leaveType = s.substring(17, 19);
startDate = Integer.parseInt(s.substring(19, 27).trim());
startTime = Integer.parseInt(s.substring(27, 31).trim());
endDate = Integer.parseInt(s.substring(31, 39).trim());
endTime = Integer.parseInt(s.substring(39, 43).trim());
status = s.substring(43, 44);
filler = s.substring(44, 80);
}
}
+137
View File
@@ -0,0 +1,137 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN02REC(勤怠: 日別展開休暇レコード LEAVE-DAILY 80B
* ============================================================
* 原定义:COPY KIN02REC REPLACING ==(A)== BY ==W01 → FD W01OUTFIL 的 01 W01OUTREC。
* 文件属性:KIN02W01.DAT / 順編成 / F(固定长) / 80B
*
* 03 (A)EMP-ID PIC X(008). → empId
* 03 (A)DATE PIC 9(008). → dateYYYYMMDD
* 03 (A)LEAVE-TYPE PIC X(002). → leaveType
* 03 (A)START-TIME PIC 9(004). → startTimeHHMM
* 03 (A)END-TIME PIC 9(004). → endTimeHHMM
* 03 (A)APPL-ID PIC 9(009). → applId
* 03 (A)FILLER PIC X(045). → filler
*/
public class Kin02Rec {
/** 03 W01EMP-ID PIC X(008). */
private String empId = "";
/** 03 W01DATE PIC 9(008). YYYYMMDD */
private int date;
/** 03 W01LEAVE-TYPE PIC X(002). */
private String leaveType = "";
/** 03 W01START-TIME PIC 9(004). HHMM */
private int startTime;
/** 03 W01END-TIME PIC 9(004). HHMM */
private int endTime;
/** 03 W01APPL-ID PIC 9(009). */
private int applId;
/** 03 W01FILLER PIC X(045). INITIALIZE 时为 SPACE */
private String filler = "";
/** INITIALIZE W01OUTREC 相当:数值→0、文字→SPACE。 */
public void reset() {
empId = "";
date = 0;
leaveType = "";
startTime = 0;
endTime = 0;
applId = 0;
filler = "";
}
/** WRITE W01OUTREC 用:固定长 80B 序列化。 */
public String toRecordLine() {
return ConvUtil.padRight(empId, 8) // EMP-ID X(008) [1..8]
+ ConvUtil.padLeftZero(date, 8) // DATE 9(008) [9..16]
+ ConvUtil.padRight(leaveType, 2) // LEAVE-TYPE X(002) [17..18]
+ ConvUtil.padLeftZero(startTime, 4)// START-TIME 9(004) [19..22]
+ ConvUtil.padLeftZero(endTime, 4) // END-TIME 9(004) [23..26]
+ ConvUtil.padLeftZero(applId, 9) // APPL-ID 9(009) [27..35]
+ ConvUtil.padRight(filler, 45); // FILLER X(045) [36..80]
}
/**
* READ R02INNFIL 用:80B固定長行から編集(数値項目は数値化、長さ不足は右空白埋め)。
*/
public void fromRecordLine(final String line) {
String s = (line == null) ? "" : line;
if (s.length() < 80) {
s = ConvUtil.padRight(s, 80);
}
empId = s.substring(0, 8); // EMP-ID X(008) [1..8]
date = Integer.parseInt(s.substring(8, 16)); // DATE 9(008) [9..16]
leaveType = s.substring(16, 18); // LEAVE-TYPE X(002) [17..18]
startTime = Integer.parseInt(s.substring(18, 22)); // START-TIME 9(004) [19..22]
endTime = Integer.parseInt(s.substring(22, 26)); // END-TIME 9(004) [23..26]
applId = Integer.parseInt(s.substring(26, 35)); // APPL-ID 9(009) [27..35]
filler = s.substring(35, 80); // FILLER X(045) [36..80]
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public int getDate() {
return date;
}
public void setDate(int date) {
this.date = date;
}
public String getLeaveType() {
return leaveType;
}
public void setLeaveType(String leaveType) {
this.leaveType = leaveType;
}
public int getStartTime() {
return startTime;
}
public void setStartTime(int startTime) {
this.startTime = startTime;
}
public int getEndTime() {
return endTime;
}
public void setEndTime(int endTime) {
this.endTime = endTime;
}
public int getApplId() {
return applId;
}
public void setApplId(int applId) {
this.applId = applId;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = filler;
}
}
+192
View File
@@ -0,0 +1,192 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN03REC(勤怠: 打刻休暇照合レコード KIN-LEAVE 160B FB
* ============================================================
* 原定义:COPY KIN03REC REPLACING ==(A)== BY ==W01 → FD W01OUTFIL 的 01 W01OUTREC。
* 文件属性:KIN05W01 / 順編成 / FB / 160B
*
* 03 (A)EMP-ID PIC X(008). → empId
* 03 (A)WORK-DATE PIC 9(008). → workDate
* 03 (A)STR-TIME PIC 9(004). → strTime
* 03 (A)END-TIME PIC 9(004). → endTime
* 03 (A)DEPT-ID PIC X(004). → deptId
* 03 (A)TERMINAL PIC X(006). → terminal
* 03 (A)FILLER-1 PIC X(046). → filler1
* 03 (A)LEAVE-TYPE PIC X(002). → leaveType
* 03 (A)LEAVE-STR-TIME PIC 9(004). → leaveStrTime
* 03 (A)LEAVE-END-TIME PIC 9(004). → leaveEndTime
* 03 (A)LEAVE-HOURS PIC 9(004)V9(001). → leaveHours5桁、小数点以下1桁内蔵)
* 03 (A)APPLICATION-ID PIC 9(009). → applicationId
* 03 (A)FILLER-2 PIC X(056). → filler2
*/
public class Kin03Rec {
/** 03 W01EMP-ID PIC X(008). */
private String empId = "";
/** 03 W01WORK-DATE PIC 9(008). */
private String workDate = "";
/** 03 W01STR-TIME PIC 9(004). */
private String strTime = "";
/** 03 W01END-TIME PIC 9(004). */
private String endTime = "";
/** 03 W01DEPT-ID PIC X(004). */
private String deptId = "";
/** 03 W01TERMINAL PIC X(006). */
private String terminal = "";
/** 03 W01LEAVE-TYPE PIC X(002). */
private String leaveType = "";
/** 03 W01LEAVE-STR-TIME PIC 9(004). */
private int leaveStrTime;
/** 03 W01LEAVE-END-TIME PIC 9(004). */
private int leaveEndTime;
/** 03 W01LEAVE-HOURS PIC 9(004)V9(001). */
private int leaveHours;
/** 03 W01APPLICATION-ID PIC 9(009). */
private int applicationId;
/**
* INITIALIZE W01OUTREC 相当。
*/
public void reset() {
empId = "";
workDate = "";
strTime = "";
endTime = "";
deptId = "";
terminal = "";
leaveType = "";
leaveStrTime = 0;
leaveEndTime = 0;
leaveHours = 0;
applicationId = 0;
}
/**
* WRITE W01OUTREC 用:固定長记录序列化(160B)。
* 8+8+4+4+4+6+46+2+4+4+5+9+56 = 160。
*/
public String toRecordLine() {
return ConvUtil.padRight(empId, 8) // EMP-ID X(008) [1..8]
+ ConvUtil.padLeftZero(workDate, 8) // WORK-DATE 9(008) [9..16] 前ゼロ埋め
+ ConvUtil.padLeftZero(strTime, 4) // STR-TIME 9(004) [17..20] 前ゼロ埋め
+ ConvUtil.padLeftZero(endTime, 4) // END-TIME 9(004) [21..24] 前ゼロ埋め
+ ConvUtil.padRight(deptId, 4) // DEPT-ID X(004) [25..28]
+ ConvUtil.padRight(terminal, 6) // TERMINAL X(006) [29..34]
+ ConvUtil.padRight("", 46) // FILLER-1 X(046) [35..80]
+ ConvUtil.padRight(leaveType, 2) // LEAVE-TYPE X(002) [81..82]
+ ConvUtil.padLeftZero(leaveStrTime, 4) // LEAVE-STR-TIME 9(004) [83..86]
+ ConvUtil.padLeftZero(leaveEndTime, 4) // LEAVE-END-TIME 9(004) [87..90]
+ ConvUtil.padLeftZero(leaveHours, 5) // LEAVE-HOURS 9(004)V9(1) [91..95]
+ ConvUtil.padLeftZero(applicationId, 9)// APPLICATION-ID 9(009) [96..104]
+ ConvUtil.padRight("", 56); // FILLER-2 X(056) [105..160]
}
/** READ R02INNREC 用:160B 固定長文字列からの復元(R02=KIN-LEAVE / KIN03REC)。 */
public void fromRecordLine(final String line) {
empId = ConvUtil.safeSub(line, 0, 8); // EMP-ID X(008) [1..8]
workDate = ConvUtil.safeSub(line, 8, 16); // WORK-DATE 9(008) [9..16]
strTime = ConvUtil.safeSub(line, 16, 20); // STR-TIME 9(004) [17..20]
endTime = ConvUtil.safeSub(line, 20, 24); // END-TIME 9(004) [21..24]
deptId = ConvUtil.safeSub(line, 24, 28); // DEPT-ID X(004) [25..28]
terminal = ConvUtil.safeSub(line, 28, 34); // TERMINAL X(006) [29..34]
// FILLER-1 X(046) [35..80]
leaveType = ConvUtil.safeSub(line, 80, 82); // LEAVE-TYPE X(002) [81..82]
leaveStrTime = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 82, 86), 0); // [83..86]
leaveEndTime = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 86, 90), 0); // [87..90]
leaveHours = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 90, 95), 0); // [91..95]
applicationId = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 95, 104), 0); // [96..104]
// FILLER-2 X(056) [105..160]
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = (workDate == null) ? "" : workDate;
}
public String getStrTime() {
return strTime;
}
public void setStrTime(String strTime) {
this.strTime = (strTime == null) ? "" : strTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = (endTime == null) ? "" : endTime;
}
public String getDeptId() {
return deptId;
}
public void setDeptId(String deptId) {
this.deptId = (deptId == null) ? "" : deptId;
}
public String getTerminal() {
return terminal;
}
public void setTerminal(String terminal) {
this.terminal = (terminal == null) ? "" : terminal;
}
public String getLeaveType() {
return leaveType;
}
public void setLeaveType(String leaveType) {
this.leaveType = (leaveType == null) ? "" : leaveType;
}
public int getLeaveStrTime() {
return leaveStrTime;
}
public void setLeaveStrTime(int leaveStrTime) {
this.leaveStrTime = leaveStrTime;
}
public int getLeaveEndTime() {
return leaveEndTime;
}
public void setLeaveEndTime(int leaveEndTime) {
this.leaveEndTime = leaveEndTime;
}
public int getLeaveHours() {
return leaveHours;
}
public void setLeaveHours(int leaveHours) {
this.leaveHours = leaveHours;
}
public int getApplicationId() {
return applicationId;
}
public void setApplicationId(int applicationId) {
this.applicationId = applicationId;
}
}
+129
View File
@@ -0,0 +1,129 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN04REC(勤怠: 打刻編集レコード EDITED-PUNCH 80B FB
* ============================================================
* 原定义:COPY KIN04REC REPLACING ==(A)== BY ==W01 → FD W01OUTFIL 的 01 W01OUTREC。
* 原定义:COPY KIN04REC REPLACING ==(A)== BY ==O1 → WS-W01RECWRITE FROM 元)。
* 文件属性:KIN04W01 / 順編成 / FB / 80B
*
* 03 (A)EMP-ID PIC X(008). → empId
* 03 (A)WORK-DATE PIC 9(008). → workDate
* 03 (A)STR-TIME PIC 9(004). → strTime
* 03 (A)END-TIME PIC 9(004). → endTime
* 03 (A)DEPT-ID PIC X(004). → deptId
* 03 (A)TERMINAL PIC X(006). → terminal
* 03 (A)FILLER PIC X(046). → filler
*/
public class Kin04Rec {
/** 03 W01EMP-ID PIC X(008). */
private String empId = "";
/** 03 W01WORK-DATE PIC 9(008). */
private String workDate = "";
/** 03 W01STR-TIME PIC 9(004). */
private String strTime = "";
/** 03 W01END-TIME PIC 9(004). */
private String endTime = "";
/** 03 W01DEPT-ID PIC X(004). */
private String deptId = "";
/** 03 W01TERMINAL PIC X(006). */
private String terminal = "";
/**
* INITIALIZE W01OUTREC 相当。
*/
public void reset() {
empId = "";
workDate = "";
strTime = "";
endTime = "";
deptId = "";
terminal = "";
}
/**
* WRITE W01OUTREC 用:固定長记录序列化(8 + 8 + 4 + 4 + 4 + 6 + 46 = 80B)。
*/
public String toRecordLine() {
return ConvUtil.padRight(empId, 8) // EMP-ID X(008) [1..8]
+ ConvUtil.padRight(workDate, 8) // WORK-DATE 9(008) [9..16]
+ ConvUtil.padRight(strTime, 4) // STR-TIME 9(004) [17..20]
+ ConvUtil.padRight(endTime, 4) // END-TIME 9(004) [21..24]
+ ConvUtil.padRight(deptId, 4) // DEPT-ID X(004) [25..28]
+ ConvUtil.padRight(terminal, 6) // TERMINAL X(006) [29..34]
+ ConvUtil.padRight("", 46); // FILLER X(046) [35..80]
}
/**
* READ R01INNFIL 用:80B固定長行から編集(左詰め、長さ不足は右空白埋め)。
*/
public void fromRecordLine(final String line) {
String s = (line == null) ? "" : line;
if (s.length() < 80) {
s = ConvUtil.padRight(s, 80);
}
empId = s.substring(0, 8); // EMP-ID X(008) [1..8]
workDate = s.substring(8, 16); // WORK-DATE 9(008) [9..16]
strTime = s.substring(16, 20); // STR-TIME 9(004) [17..20]
endTime = s.substring(20, 24); // END-TIME 9(004) [21..24]
deptId = s.substring(24, 28); // DEPT-ID X(004) [25..28]
terminal = s.substring(28, 34); // TERMINAL X(006) [29..34]
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public String getWorkDate() {
return workDate;
}
public void setWorkDate(String workDate) {
this.workDate = (workDate == null) ? "" : workDate;
}
public String getStrTime() {
return strTime;
}
public void setStrTime(String strTime) {
this.strTime = (strTime == null) ? "" : strTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = (endTime == null) ? "" : endTime;
}
public String getDeptId() {
return deptId;
}
public void setDeptId(String deptId) {
this.deptId = (deptId == null) ? "" : deptId;
}
public String getTerminal() {
return terminal;
}
public void setTerminal(String terminal) {
this.terminal = (terminal == null) ? "" : terminal;
}
}
+55
View File
@@ -0,0 +1,55 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN05REC(勤怠: エラーログレコード KERR-LOG VB可変長)
* ============================================================
* 原定义:COPY KIN05REC REPLACING ==(A)== BY ==W02 → FD W02OUTFIL 的 01 W02OUTREC。
* 文件属性:KIN01W02 / 順編成 / V(可变长) / 最大200B
* ※VB 记录的 RDW 由文本行长度天然体现,Java 侧不输出长度前缀。
*
* 03 (A)ERR-CATEGORY PIC 9(002). → errCategory
* 03 (A)ERR-DETAIL PIC X(198). → errDetail
*/
public class Kin05Rec {
/** 03 W02ERR-CATEGORY PIC 9(002). */
private int errCategory;
/** 03 W02ERR-DETAIL PIC X(198). */
private String errDetail = "";
/**
* INITIALIZE W02OUTREC 相当。
*/
public void reset() {
errCategory = 0;
errDetail = "";
}
/**
* WRITE W02OUTREC 用:可变长记录序列化(2 + 198 = 200B)。
*/
public String toRecordLine() {
return ConvUtil.padLeftZero(errCategory, 2) // ERR-CATEGORY 9(002) [1..2]
+ ConvUtil.padRight(errDetail, 198); // ERR-DETAIL X(198) [3..200]
}
public int getErrCategory() {
return errCategory;
}
public void setErrCategory(int errCategory) {
this.errCategory = errCategory;
}
public String getErrDetail() {
return errDetail;
}
public void setErrDetail(String errDetail) {
this.errDetail = errDetail;
}
}
+83
View File
@@ -0,0 +1,83 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN06REC(勤怠: 出勤日カレンダーレコード WORK-DAY-FILE 80B
* ============================================================
* 原定义:COPY KIN06REC REPLACING ==(A)== BY ==OUT → FD OUTRECFIL 的 01 OUTREC。
* 文件属性:KIN06W01 / 順編成 / F / 80B
*
* 03 (A)EMP-ID PIC X(008). → empId
* 03 (A)DATE PIC 9(008). → dateYYYYMMDD
* 03 (A)DAY-OF-WEEK PIC 9(001). → dayOfWeek1=月…7=日)
* 03 (A)FILLER PIC X(063). → filler
*/
public class Kin06Rec {
/** 03 OUTEMP-ID PIC X(008). */
private String empId = "";
/** 03 OUTDATE PIC 9(008). YYYYMMDD */
private int date;
/** 03 OUTDAY-OF-WEEK PIC 9(001). 1=月…7=日 */
private int dayOfWeek;
/** 03 OUTFILLER PIC X(063). */
private String filler = "";
/** INITIALIZE OUTREC 相当。 */
public void reset() {
empId = "";
date = 0;
dayOfWeek = 0;
filler = "";
}
/** WRITE OUTREC 用:固定長 80B 序列化。8+8+1+63=80。 */
public String toRecordLine() {
return ConvUtil.padRight(empId, 8) // EMP-ID X(008) [1..8]
+ ConvUtil.padLeftZero(date, 8) // DATE 9(008) [9..16]
+ ConvUtil.padLeftZero(dayOfWeek, 1) // DAY-OF-WEEK 9(001) [17]
+ ConvUtil.padRight(filler, 63); // FILLER X(063) [18..80]
}
/** READ OUTREC 用:80B 固定長文字列からの復元(R01=WORK-DAY-FILE)。 */
public void fromRecordLine(final String line) {
empId = ConvUtil.safeSub(line, 0, 8);
date = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 8, 16), 0);
dayOfWeek = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 16, 17), 0);
filler = ConvUtil.safeSub(line, 17, 80);
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public int getDate() {
return date;
}
public void setDate(int date) {
this.date = date;
}
public int getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+159
View File
@@ -0,0 +1,159 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN07REC(勤怠: 日別勤怠計算レコード DAILY-RECORD 200B
* ============================================================
* 原定义:COPY KIN07REC REPLACING ==(A)== BY ==SD → FD W01OUTFIL 的 01 W01OUTREC。
* 文件属性:KIN07W01 / 順編成 / F / 200B
*
* 03 (A)EMP-ID PIC 9(008). → empId
* 03 (A)DATE PIC 9(008). → date
* 03 (A)TIME-IN PIC 9(004). → timeIn
* 03 (A)TIME-OUT PIC 9(004). → timeOut
* 03 (A)ANNUAL-H PIC 9(004)V9(001). → annualH0.1h単位整数)
* 03 (A)PERSONAL-H PIC 9(004)V9(001). → personalH
* 03 (A)OFFICIAL-H PIC 9(004)V9(001). → officialH
* 03 (A)SICK-H PIC 9(004)V9(001). → sickH
* 03 (A)ABSENT-H PIC 9(004)V9(001). → absentH
* 03 (A)FILLER PIC X(151). → filler
*
* 各 *_H は「0.1h 単位の整数(tenth)」で保持(例: 2.0h = 20)。
*/
public class Kin07Rec {
private String empId = "";
private int date;
private int timeIn;
private int timeOut;
private int annualH;
private int personalH;
private int officialH;
private int sickH;
private int absentH;
private String filler = "";
/** INITIALIZE W01OUTREC 相当。 */
public void reset() {
empId = "";
date = 0;
timeIn = 0;
timeOut = 0;
annualH = 0;
personalH = 0;
officialH = 0;
sickH = 0;
absentH = 0;
filler = "";
}
/** WRITE W01OUTREC 用:固定長 200B 序列化。8+8+4+4+5*5+151=200。 */
public String toRecordLine() {
return ConvUtil.padRight(empId, 8) // EMP-ID X(008) [1..8]
+ ConvUtil.padLeftZero(date, 8) // DATE 9(008) [9..16]
+ ConvUtil.padLeftZero(timeIn, 4) // TIME-IN 9(004) [17..20]
+ ConvUtil.padLeftZero(timeOut, 4) // TIME-OUT 9(004) [21..24]
+ ConvUtil.padLeftZero(annualH, 5) // ANNUAL-H 9(4)V9(1) [25..29]
+ ConvUtil.padLeftZero(personalH, 5) // PERSONAL-H 9(4)V9(1) [30..34]
+ ConvUtil.padLeftZero(officialH, 5) // OFFICIAL-H 9(4)V9(1) [35..39]
+ ConvUtil.padLeftZero(sickH, 5) // SICK-H 9(4)V9(1) [40..44]
+ ConvUtil.padLeftZero(absentH, 5) // ABSENT-H 9(4)V9(1) [45..49]
+ ConvUtil.padRight(filler, 151); // FILLER X(151) [50..200]
}
/** READ R01INNREC 用:200B 固定長文字列からの復元(R01=DAILY-RECORD / KIN07REC)。 */
public void fromRecordLine(final String line) {
empId = ConvUtil.safeSub(line, 0, 8); // EMP-ID X(008) [1..8]
date = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 8, 16), 0); // DATE 9(008) [9..16]
timeIn = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 16, 20), 0); // TIME-IN 9(004) [17..20]
timeOut = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 20, 24), 0); // TIME-OUT 9(004) [21..24]
annualH = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 24, 29), 0); // ANNUAL-H 9(4)V9(1) [25..29]
personalH = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 29, 34), 0); // PERSONAL-H [30..34]
officialH = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 34, 39), 0); // OFFICIAL-H [35..39]
sickH = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 39, 44), 0); // SICK-H [40..44]
absentH = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 44, 49), 0); // ABSENT-H [45..49]
filler = ConvUtil.safeSub(line, 49, 200); // FILLER X(151) [50..200]
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public int getDate() {
return date;
}
public void setDate(int date) {
this.date = date;
}
public int getTimeIn() {
return timeIn;
}
public void setTimeIn(int timeIn) {
this.timeIn = timeIn;
}
public int getTimeOut() {
return timeOut;
}
public void setTimeOut(int timeOut) {
this.timeOut = timeOut;
}
public int getAnnualH() {
return annualH;
}
public void setAnnualH(int annualH) {
this.annualH = annualH;
}
public int getPersonalH() {
return personalH;
}
public void setPersonalH(int personalH) {
this.personalH = personalH;
}
public int getOfficialH() {
return officialH;
}
public void setOfficialH(int officialH) {
this.officialH = officialH;
}
public int getSickH() {
return sickH;
}
public void setSickH(int sickH) {
this.sickH = sickH;
}
public int getAbsentH() {
return absentH;
}
public void setAbsentH(int absentH) {
this.absentH = absentH;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+121
View File
@@ -0,0 +1,121 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KIN08REC(勤怠: 欠勤集計中間ファイル ABSENCE-SUMMARY 80B
* ============================================================
* 原定义:COPY KIN08REC REPLACING ==(A)== BY ==W01 → FD W01OUTFIL 的 01 W01OUTREC。
* 文件属性:KIN08W01 / 順編成 / F / 80B
*
* 03 (A)EMP-ID PIC X(008). → empId
* 03 (A)YEAR-MONTH PIC X(006). → yearMonth
* 03 (A)ANNUAL-LEAVE-H PIC 9(004)V9(001). → annualH0.1h単位整数)
* 03 (A)PERSONAL-LEAVE-H PIC 9(004)V9(001). → personalH
* 03 (A)OFFICIAL-LEAVE-H PIC 9(004)V9(001). → officialH
* 03 (A)SICK-LEAVE-H PIC 9(004)V9(001). → sickH
* 03 (A)ABSENT-H PIC 9(004)V9(001). → absentH
* 03 (A)FILLER PIC X(041). → filler
*
* 各 *_H は「0.1h 単位の整数(tenth)」で保持(例: 5.0h = 50)。
*/
public class Kin08Rec {
private String empId = "";
private String yearMonth = "";
private int annualH;
private int personalH;
private int officialH;
private int sickH;
private int absentH;
private String filler = "";
/** INITIALIZE W01OUTREC 相当。 */
public void reset() {
empId = "";
yearMonth = "";
annualH = 0;
personalH = 0;
officialH = 0;
sickH = 0;
absentH = 0;
filler = "";
}
/** WRITE W01OUTREC 用:固定長 80B 序列化。8+6+5*5+41=80。 */
public String toRecordLine() {
return ConvUtil.padRight(empId, 8) // EMP-ID X(008) [1..8]
+ ConvUtil.padRight(yearMonth, 6) // YEAR-MONTH X(006) [9..14]
+ ConvUtil.padLeftZero(annualH, 5) // ANNUAL-LEAVE-H 9(4)V9(1) [15..19]
+ ConvUtil.padLeftZero(personalH, 5) // PERSONAL-LEAVE-H 9(4)V9(1) [20..24]
+ ConvUtil.padLeftZero(officialH, 5) // OFFICIAL-LEAVE-H 9(4)V9(1) [25..29]
+ ConvUtil.padLeftZero(sickH, 5) // SICK-LEAVE-H 9(4)V9(1) [30..34]
+ ConvUtil.padLeftZero(absentH, 5) // ABSENT-H 9(4)V9(1) [35..39]
+ ConvUtil.padRight(filler, 41); // FILLER X(041) [40..80]
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public String getYearMonth() {
return yearMonth;
}
public void setYearMonth(String yearMonth) {
this.yearMonth = (yearMonth == null) ? "" : yearMonth;
}
public int getAnnualH() {
return annualH;
}
public void setAnnualH(int annualH) {
this.annualH = annualH;
}
public int getPersonalH() {
return personalH;
}
public void setPersonalH(int personalH) {
this.personalH = personalH;
}
public int getOfficialH() {
return officialH;
}
public void setOfficialH(int officialH) {
this.officialH = officialH;
}
public int getSickH() {
return sickH;
}
public void setSickH(int sickH) {
this.sickH = sickH;
}
public int getAbsentH() {
return absentH;
}
public void setAbsentH(int absentH) {
this.absentH = absentH;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+140
View File
@@ -0,0 +1,140 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU01RECEMP-RECORD 社員マスタ固定長レコード 80B)
* ============================================================
* 01 W01OUTREC.FD W01OUTFIL / COPY KYU01REC REPLACING ==(A)== BY ==W01==
* 03 W01EMP-ID PIC X(008). → empId
* 03 W01EMP-NAME PIC X(040). → empName
* 03 W01DEPT-CODE PIC X(002). → deptCode
* 03 W01REGION-CODE PIC X(002). → regionCode
* 03 W01CATEGORY-CODE PIC X(003). → categoryCode
* 03 W01BASE-SALARY PIC 9(009). → baseSalary
* 03 W01HOURLY-RATE PIC 9(007). → hourlyRate
* 03 W01DEPENDENT-COUNT PIC 9(002). → dependentCount
* 03 W01STATUS PIC X(001). → status
* 03 FILLER PIC X(006). → 固定スペース
*/
public class Kyu01Rec {
/** 03 W01EMP-ID PIC X(008). */
private String empId = "";
/** 03 W01EMP-NAME PIC X(040). */
private String empName = "";
/** 03 W01DEPT-CODE PIC X(002). */
private String deptCode = "";
/** 03 W01REGION-CODE PIC X(002). */
private String regionCode = "";
/** 03 W01CATEGORY-CODE PIC X(003). */
private String categoryCode = "";
/** 03 W01BASE-SALARY PIC 9(009). */
private int baseSalary;
/** 03 W01HOURLY-RATE PIC 9(007). */
private int hourlyRate;
/** 03 W01DEPENDENT-COUNT PIC 9(002). */
private int dependentCount;
/** 03 W01STATUS PIC X(001). */
private String status = "";
/** INITIALIZE W01OUTREC 相当:数値→0、文字→SPACE。 */
public void reset() {
empId = "";
empName = "";
deptCode = "";
regionCode = "";
categoryCode = "";
baseSalary = 0;
hourlyRate = 0;
dependentCount = 0;
status = "";
}
public void setEmpId(final String v) {
empId = ConvUtil.padRight(v, 8);
}
public void setEmpName(final String v) {
empName = ConvUtil.padRight(v, 40);
}
public void setDeptCode(final String v) {
deptCode = ConvUtil.padRight(v, 2);
}
public void setRegionCode(final String v) {
regionCode = ConvUtil.padRight(v, 2);
}
public void setCategoryCode(final String v) {
categoryCode = ConvUtil.padRight(v, 3);
}
public void setBaseSalary(final int v) {
baseSalary = v;
}
public void setHourlyRate(final int v) {
hourlyRate = v;
}
public void setDependentCount(final int v) {
dependentCount = v;
}
public void setStatus(final String v) {
status = ConvUtil.padRight(v, 1);
}
public String getEmpId() {
return empId;
}
public String getEmpName() {
return empName;
}
public String getDeptCode() {
return deptCode;
}
public String getRegionCode() {
return regionCode;
}
public String getCategoryCode() {
return categoryCode;
}
public int getBaseSalary() {
return baseSalary;
}
public int getHourlyRate() {
return hourlyRate;
}
public int getDependentCount() {
return dependentCount;
}
public String getStatus() {
return status;
}
/** WRK-CSV-ACCUM(1:80) 相当の 80B 固定長レコード行を生成(FORMAT WRITE)。 */
public String toRecordLine() {
return empId
+ empName
+ deptCode
+ regionCode
+ categoryCode
+ ConvUtil.padLeftZero(baseSalary, 9)
+ ConvUtil.padLeftZero(hourlyRate, 7)
+ ConvUtil.padLeftZero(dependentCount, 2)
+ status
+ ConvUtil.padRight("", 6); // FILLER X(006)
}
}
+94
View File
@@ -0,0 +1,94 @@
package dto;
import java.math.BigDecimal;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU02RECABSENCE-AGGREGATE 欠勤集約レコード 80B
* ============================================================
* 01 R02INNREC.FD R02INNFIL / COPY KYU02REC REPLACING ==(A)== BY ==R02==
* 03 R02EMP-ID PIC X(008). → empId
* 03 R02YEAR-MONTH PIC X(006). → yearMonth
* 03 R02TOTAL-HOURS PIC 9(004)V9(001). → totalHoursBigDecimal 小数点以下1桁)
* 03 R02ABSENT-COUNT PIC 9(002). → absentCount
* 03 FILLER PIC X(059). → 固定スペース
*
* レコード長: 8+6+5+2+59 = 80B(固定長)。
* 読込時に固定長行を各項目に分解する parse() と、書込用 toRecordLine() を提供。
*/
public class Kyu02Rec {
/** 03 R02EMP-ID PIC X(008). */
private String empId = "";
/** 03 R02YEAR-MONTH PIC X(006). */
private String yearMonth = "";
/** 03 R02TOTAL-HOURS PIC 9(004)V9(001).(小数点以下1桁) */
private BigDecimal totalHours = BigDecimal.ZERO;
/** 03 R02ABSENT-COUNT PIC 9(002). */
private int absentCount;
/** INITIALIZE R02INNREC 相当:数値→0、文字→SPACE。 */
public void reset() {
empId = "";
yearMonth = "";
totalHours = BigDecimal.ZERO;
absentCount = 0;
}
/**
* 固定長行(80B)を各項目に分解する。
* 位置: EMP-ID(1:8) / YEAR-MONTH(9:14) / TOTAL-HOURS(15:19) / ABSENT-COUNT(20:21) / FILLER(22:80)。
* @param line 読込レコード行(不足分は空文字として扱う)
*/
public static Kyu02Rec parse(final String line) {
final Kyu02Rec r = new Kyu02Rec();
r.empId = ConvUtil.padRight(ConvUtil.safeSub(line, 0, 8), 8);
r.yearMonth = ConvUtil.padRight(ConvUtil.safeSub(line, 8, 14), 6);
// TOTAL-HOURS は 9(4)V9(1) → 5桁数値文字列を小数点以下1桁の BigDecimal に復元
r.totalHours = ConvUtil.toDecimal(ConvUtil.safeSub(line, 14, 19), 1);
r.absentCount = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 19, 21), 0);
return r;
}
public void setEmpId(final String v) {
empId = ConvUtil.padRight(v, 8);
}
public void setYearMonth(final String v) {
yearMonth = ConvUtil.padRight(v, 6);
}
public void setTotalHours(final BigDecimal v) {
totalHours = (v == null) ? BigDecimal.ZERO : v;
}
public void setAbsentCount(final int v) {
absentCount = v;
}
public String getEmpId() {
return empId;
}
public String getYearMonth() {
return yearMonth;
}
public BigDecimal getTotalHours() {
return totalHours;
}
public int getAbsentCount() {
return absentCount;
}
/** 80B 固定長レコード行を生成(FORMAT WRITE 相当)。 */
public String toRecordLine() {
return empId
+ yearMonth
+ ConvUtil.formatZoned(totalHours, 4, 1)
+ ConvUtil.padLeftZero(absentCount, 2)
+ ConvUtil.padRight("", 59); // FILLER X(059)
}
}
+120
View File
@@ -0,0 +1,120 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU03RECSALARY-DETAIL 給与明細レコード 240B
* ============================================================
* 01 R01INNREC.FD R01INNFIL / COPY KYU03REC REPLACING ==(A)== BY ==R01==
* 03 R01EMP-ID PIC X(008). → empId
* 03 R01EMP-NAME PIC X(040). → empName
* 03 R01DEPT-CODE PIC X(002). → deptCode
* 03 R01BASE-SALARY PIC 9(009). → baseSalary
* 03 R01OVT-AMOUNT PIC 9(009). → ovtAmount
* 03 R01HOLIDAY-AMOUNT PIC 9(009). → holidayAmount
* 03 R01ABSENT-DEDUCT PIC 9(009). → absentDeduct
* 03 R01GROSS-PAYMENT PIC 9(009). → grossPayment
* 03 R01OVT-HOURS PIC 9(004)V9(001). → ovtHours5桁ゾーン整数)
* 03 R01ABSENT-HOURS PIC 9(004)V9(001). → absentHours5桁ゾーン整数)
* 03 R01ATTEND-TYPE PIC X(001). → attendType
* 03 R01ALLOWANCE-TYPE PIC X(003). → allowanceType
* 03 FILLER PIC X(131). → 固定長レイアウト維持用(非保持)
*
* 入力ファイルは FB 240B 固定長。setRecordLine() で位置ごとに切出し
* (COBOL の参照修正/固定長 MOVE 相当:05 層定義に合わせた substring)。
*/
public class Kyu03Rec {
/** 03 R01EMP-ID PIC X(008). */
private String empId = "";
/** 03 R01EMP-NAME PIC X(040). */
private String empName = "";
/** 03 R01DEPT-CODE PIC X(002). */
private String deptCode = "";
/** 03 R01BASE-SALARY PIC 9(009). */
private int baseSalary;
/** 03 R01OVT-AMOUNT PIC 9(009). */
private int ovtAmount;
/** 03 R01HOLIDAY-AMOUNT PIC 9(009). */
private int holidayAmount;
/** 03 R01ABSENT-DEDUCT PIC 9(009). */
private int absentDeduct;
/** 03 R01GROSS-PAYMENT PIC 9(009). */
private int grossPayment;
/** 03 R01OVT-HOURS PIC 9(004)V9(001)5桁ゾーン整数). */
private long ovtHours;
/** 03 R01ABSENT-HOURS PIC 9(004)V9(001)5桁ゾーン整数). */
private long absentHours;
/** 03 R01ATTEND-TYPE PIC X(001). */
private String attendType = "";
/** 03 R01ALLOWANCE-TYPE PIC X(003). */
private String allowanceType = "";
/**
* 240B 固定長行を 05 層定義の位置で切出してセット(READ R01INNFIL 相当)。
* @param line 物理レコード行(FB 240B;短い場合は safeSub で欠損部を空文字扱い)
*/
public void setRecordLine(final String line) {
empId = ConvUtil.safeSub(line, 0, 8); // (A)EMP-ID X(008)
empName = ConvUtil.safeSub(line, 8, 48); // (A)EMP-NAME X(040)
deptCode = ConvUtil.safeSub(line, 48, 50); // (A)DEPT-CODE X(002)
baseSalary = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 50, 59), 0); // (A)BASE-SALARY 9(009)
ovtAmount = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 59, 68), 0); // (A)OVT-AMOUNT 9(009)
holidayAmount = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 68, 77), 0);
absentDeduct = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 77, 86), 0);
grossPayment = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 86, 95), 0); // (A)GROSS-PAYMENT 9(009)
ovtHours = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 95, 100), 0); // 9(004)V9(001)
absentHours = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 100, 105), 0);
attendType = ConvUtil.safeSub(line, 105, 106); // (A)ATTEND-TYPE X(001)
allowanceType = ConvUtil.safeSub(line, 106, 109); // (A)ALLOWANCE-TYPE X(003)
}
public String getEmpId() {
return empId;
}
public String getEmpName() {
return empName;
}
public String getDeptCode() {
return deptCode;
}
public int getBaseSalary() {
return baseSalary;
}
public int getOvtAmount() {
return ovtAmount;
}
public int getHolidayAmount() {
return holidayAmount;
}
public int getAbsentDeduct() {
return absentDeduct;
}
public int getGrossPayment() {
return grossPayment;
}
public long getOvtHours() {
return ovtHours;
}
public long getAbsentHours() {
return absentHours;
}
public String getAttendType() {
return attendType;
}
public String getAllowanceType() {
return allowanceType;
}
}
+169
View File
@@ -0,0 +1,169 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU04RECSALARY-NET 差引支給額レコード 200B)※プロジェクト共通DTO
* ============================================================
* 使用元:KYU05DED(出力) / KYU06UPD(入力R02) / KYU07DIV(入力) / KYU08PRI(入力R01) / KYU09MRG(入力M01/M02)
* したがって本クラスが KYU04REC の唯一の正DOT であり、全プログラムで共有する。
*
* 01 R0xOUTREC.FD / COPY KYU04REC REPLACING ==(A)== BY ==R0x==
* 03 R0xEMP-ID PIC X(008). -> empId / getR01EmpId
* 03 R0xREC-TYPE PIC X(001). -> recType / getR01RecType
* 03 R0xEMP-NAME PIC X(040). -> empName / getR01EmpName
* 03 R0xDEPT-CODE PIC X(002). -> deptCode / getR01DeptCode
* 03 R0xREGION-CODE PIC X(002). -> regionCode / getR01RegionCode
* 03 R0xCATEGORY-CODE PIC X(003). -> categoryCode / getR01CategoryCode
* 03 R0xGROSS-PAYMENT PIC 9(009). -> grossPayment / getR01GrossPayment
* 03 R0xINCOME-TAX PIC 9(009). -> incomeTax / getR01IncomeTax
* 03 R0xINSURANCE PIC 9(009). -> insurance / getR01Insurance
* 03 R0xRESIDENT-TAX PIC 9(009). -> residentTax / getR01ResidentTax
* 03 R0xNET-PAYMENT PIC 9(009). -> netPayment / getR01NetPayment
* 03 R0xTAXABLE-INCOME PIC 9(009). -> taxableIncome / getR01TaxableIncome
* 03 R0xPAY-AMOUNT PIC 9(009). -> payAmount / getR01PayAmount
* 03 FILLER PIC X(081). -> 固定スペース
*
* 各プログラムの参照名(R0xEMP-ID 等)の違いを吸収するため、標準名(getEmpId等)と
* R01接頭辞名(getR01EmpId等)の両方を提供する(同一フィールドへの別名アクセサ)。
* fromRecordLine / parseRecord はいずれも 200B 固定長を解析(等価)。
*/
public class Kyu04Rec {
private String empId = "";
private String recType = "";
private String empName = "";
private String deptCode = "";
private String regionCode = "";
private String categoryCode = "";
private long grossPayment;
private long incomeTax;
private long insurance;
private long residentTax;
private long netPayment;
private long taxableIncome;
private long payAmount;
/** INITIALIZE 相当。 */
public void reset() {
empId = "";
recType = "";
empName = "";
deptCode = "";
regionCode = "";
categoryCode = "";
grossPayment = 0;
incomeTax = 0;
insurance = 0;
residentTax = 0;
netPayment = 0;
taxableIncome = 0;
payAmount = 0;
}
// ---------- 標準アクセサ(getXxx / setXxx ----------
public void setEmpId(final String v) { empId = ConvUtil.padRight(v, 8); }
public String getEmpId() { return empId; }
public void setRecType(final String v) { recType = ConvUtil.padRight(v, 1); }
public String getRecType() { return recType; }
public void setEmpName(final String v) { empName = ConvUtil.padRight(v, 40); }
public String getEmpName() { return empName; }
public void setDeptCode(final String v) { deptCode = ConvUtil.padRight(v, 2); }
public String getDeptCode() { return deptCode; }
public void setRegionCode(final String v) { regionCode = ConvUtil.padRight(v, 2); }
public String getRegionCode() { return regionCode; }
public void setCategoryCode(final String v) { categoryCode = ConvUtil.padRight(v, 3); }
public String getCategoryCode() { return categoryCode; }
public void setGrossPayment(final long v) { grossPayment = v; }
public long getGrossPayment() { return grossPayment; }
public void setIncomeTax(final long v) { incomeTax = v; }
public long getIncomeTax() { return incomeTax; }
public void setInsurance(final long v) { insurance = v; }
public long getInsurance() { return insurance; }
public void setResidentTax(final long v) { residentTax = v; }
public long getResidentTax() { return residentTax; }
public void setNetPayment(final long v) { netPayment = v; }
public long getNetPayment() { return netPayment; }
public void setTaxableIncome(final long v) { taxableIncome = v; }
public long getTaxableIncome() { return taxableIncome; }
public void setPayAmount(final long v) { payAmount = v; }
public long getPayAmount() { return payAmount; }
// ---------- R01接頭辞別名(他PGMの参照名互換) ----------
public void setR01EmpId(final String v) { setEmpId(v); }
public String getR01EmpId() { return empId; }
public void setR01RecType(final String v) { setRecType(v); }
public String getR01RecType() { return recType; }
public void setR01EmpName(final String v) { setEmpName(v); }
public String getR01EmpName() { return empName; }
public void setR01DeptCode(final String v) { setDeptCode(v); }
public String getR01DeptCode() { return deptCode; }
public void setR01RegionCode(final String v) { setRegionCode(v); }
public String getR01RegionCode() { return regionCode; }
public void setR01CategoryCode(final String v) { setCategoryCode(v); }
public String getR01CategoryCode() { return categoryCode; }
public void setR01GrossPayment(final long v) { grossPayment = v; }
public long getR01GrossPayment() { return grossPayment; }
public void setR01IncomeTax(final long v) { incomeTax = v; }
public long getR01IncomeTax() { return incomeTax; }
public void setR01Insurance(final long v) { insurance = v; }
public long getR01Insurance() { return insurance; }
public void setR01ResidentTax(final long v) { residentTax = v; }
public long getR01ResidentTax() { return residentTax; }
public void setR01NetPayment(final long v) { netPayment = v; }
public long getR01NetPayment() { return netPayment; }
public void setR01TaxableIncome(final long v) { taxableIncome = v; }
public long getR01TaxableIncome() { return taxableIncome; }
public void setR01PayAmount(final long v) { payAmount = v; }
public long getR01PayAmount() { return payAmount; }
// ---------- 固定長レコード解析 ----------
private static String sub(final String s, final int a, final int b) {
String x = (s == null) ? "" : s;
if (x.length() < b) {
x = ConvUtil.padRight(x, b);
}
return x.substring(a, b);
}
/** 200B 固定長 SALARY-NET レコードを解析(COPY KYU04REC 05層順)。 */
public void fromRecordLine(final String line) {
empId = sub(line, 0, 8);
recType = sub(line, 8, 9);
empName = sub(line, 9, 49);
deptCode = sub(line, 49, 51);
regionCode = sub(line, 51, 53);
categoryCode = sub(line, 53, 56);
grossPayment = ConvUtil.parseIntSafe(sub(line, 56, 65), 0);
incomeTax = ConvUtil.parseIntSafe(sub(line, 65, 74), 0);
insurance = ConvUtil.parseIntSafe(sub(line, 74, 83), 0);
residentTax = ConvUtil.parseIntSafe(sub(line, 83, 92), 0);
netPayment = ConvUtil.parseIntSafe(sub(line, 92, 101), 0);
taxableIncome = ConvUtil.parseIntSafe(sub(line, 101, 110), 0);
payAmount = ConvUtil.parseIntSafe(sub(line, 110, 119), 0);
}
/** parseRecord は fromRecordLine と等価(PGM間の命名差を吸収)。 */
public void parseRecord(final String line) {
fromRecordLine(line);
}
/** 200B 固定長レコード行を生成(FORMAT WRITE)。 */
public String toRecordLine() {
return ConvUtil.padRight(empId, 8)
+ ConvUtil.padRight(recType, 1)
+ ConvUtil.padRight(empName, 40)
+ ConvUtil.padRight(deptCode, 2)
+ ConvUtil.padRight(regionCode, 2)
+ ConvUtil.padRight(categoryCode, 3)
+ ConvUtil.padLeftZero(grossPayment, 9)
+ ConvUtil.padLeftZero(incomeTax, 9)
+ ConvUtil.padLeftZero(insurance, 9)
+ ConvUtil.padLeftZero(residentTax, 9)
+ ConvUtil.padLeftZero(netPayment, 9)
+ ConvUtil.padLeftZero(taxableIncome, 9)
+ ConvUtil.padLeftZero(payAmount, 9)
+ ConvUtil.padRight("", 81);
}
}
+144
View File
@@ -0,0 +1,144 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU05RECPAYSLIP 給与明細印刷レコード 200B)
* ============================================================
* 01 W01OUTREC.FD W01OUTFIL / COPY KYU05REC REPLACING ==(A)== BY ==W01==
* 03 W01EMP-ID PIC X(008). → w01EmpId
* 03 FILLER PIC X(002). → 固定スペース
* 03 W01EMP-NAME PIC X(020) → w01EmpNameJUSTIFIED RIGHT で右詰め編集)
* 03 FILLER PIC X(002). → 固定スペース
* 03 W01BASIC-SALARY PIC Z(008)9 → w01BasicSalary(編集出力)
* 03 FILLER PIC X(002). → 固定スペース
* 03 W01NET-PAYMENT PIC Z(008)9 → w01NetPayment(編集出力)
* 03 FILLER PIC X(002). → 固定スペース
* 03 W01PAGE-TITLE PIC X(040). → w01PageTitle
* 03 FILLER PIC X(106). → 固定スペース
*
* 編集仕様:
* - W01EMP-NAME は X(020) JUSTIFIED RIGHT → 右詰め(元が 40B の場合は右端20Bを保持)。
* - W01BASIC-SALARY / W01NET-PAYMENT は PIC Z(008)9 BLANK WHEN ZERO →
* 9桁の内先頭8桁が Z(ゼロ抑制→SPACE)、最終1桁は 9(常時表示)。
* 値が 0 の場合は全体を SPACE 化(BLANK WHEN ZERO)。
*
* 本 DTO は明細行(DETAIL-LINE)の出力に使用する。DETAIL-LINE は KYU05REC レイアウトと
* 先頭54B が一致し、PAGE-TITLE を SPACE とすることで尾部146B(FILLER) も一致する。
* ヘッダ行(HEADER-LINE)/フッタ行(FOOTER-LINE)は KYU05REC 布局と異なるため
* 呼出側(Kyu08PriMain)で個別に編集する。
*/
public class Kyu05Rec {
/** 03 W01EMP-ID PIC X(008). */
private String w01EmpId = "";
/** 03 W01EMP-NAME PIC X(020) JUSTIFIED RIGHT.(生値を保持、編集時に右詰め) */
private String w01EmpName = "";
/** 03 W01BASIC-SALARY PIC Z(008)9 BLANK WHEN ZERO. */
private int w01BasicSalary;
/** 03 W01NET-PAYMENT PIC Z(008)9 BLANK WHEN ZERO. */
private int w01NetPayment;
/** 03 W01PAGE-TITLE PIC X(040). */
private String w01PageTitle = "";
/** INITIALIZE W01OUTREC 相当:数値→0、文字→SPACE。 */
public void reset() {
w01EmpId = "";
w01EmpName = "";
w01BasicSalary = 0;
w01NetPayment = 0;
w01PageTitle = "";
}
public void setW01EmpId(final String v) {
w01EmpId = ConvUtil.padRight(v, 8);
}
public void setW01EmpName(final String v) {
w01EmpName = (v == null) ? "" : v;
}
public void setW01BasicSalary(final int v) {
w01BasicSalary = v;
}
public void setW01NetPayment(final int v) {
w01NetPayment = v;
}
public void setW01PageTitle(final String v) {
w01PageTitle = ConvUtil.padRight(v, 40);
}
public String getW01EmpId() {
return w01EmpId;
}
public String getW01EmpName() {
return w01EmpName;
}
public int getW01BasicSalary() {
return w01BasicSalary;
}
public int getW01NetPayment() {
return w01NetPayment;
}
public String getW01PageTitle() {
return w01PageTitle;
}
/**
* PIC Z(n)9 BLANK WHEN ZERO 編集。
* @param val 数値
* @param zCount Z の桁数(表示桁数 = zCount + 1
* @return zCount+1 桁の編集済文字列(先頭 zCount 桁はゼロ抑制→SPACE、最終桁は常時表示)
*/
private static String editZ(final int val, final int zCount) {
final int len = zCount + 1;
if (val == 0) {
// BLANK WHEN ZERO:全体を SPACE 化
return ConvUtil.padRight("", len);
}
final String s = ConvUtil.padLeftZero(val, len);
final char[] ca = s.toCharArray();
for (int i = 0; i < zCount; i++) {
if (ca[i] == '0') {
ca[i] = ' ';
} else {
break;
}
}
return new String(ca);
}
/** X(len) JUSTIFIED RIGHT 編集(元が長い場合は右端 len 桁を保持)。 */
private static String rightJustify(final String v, final int len) {
String s = (v == null) ? "" : v;
if (s.length() > len) {
return s.substring(s.length() - len);
}
final StringBuilder sb = new StringBuilder();
while (sb.length() < len - s.length()) {
sb.append(' ');
}
return sb.append(s).toString();
}
/** 200B 固定長レコード行(明細行)を生成。 */
public String toRecordLine() {
return w01EmpId
+ ConvUtil.padRight("", 2) // FILLER X(002)
+ rightJustify(w01EmpName, 20) // W01EMP-NAME X(020) JUSTIFIED RIGHT
+ ConvUtil.padRight("", 2) // FILLER X(002)
+ editZ(w01BasicSalary, 8) // W01BASIC-SALARY Z(008)9
+ ConvUtil.padRight("", 2) // FILLER X(002)
+ editZ(w01NetPayment, 8) // W01NET-PAYMENT Z(008)9
+ ConvUtil.padRight("", 2) // FILLER X(002)
+ w01PageTitle // W01PAGE-TITLE X(040)
+ ConvUtil.padRight("", 106); // FILLER X(106)
}
}
+68
View File
@@ -0,0 +1,68 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU06RECPAYMENT-DATA 支給データレコード 200B)
* ============================================================
* 01 W01OUTREC.FD W01OUTFIL / COPY KYU06REC REPLACING ==(A)== BY ==W01==
* 03 W01EMP-ID PIC X(008). → empId
* 03 W01PAY-TYPE PIC X(001). → payType'T'=支給合算)
* 03 W01AMOUNT PIC 9(009). → amount(給与+賞与)
* 03 W01BANK-CODE PIC X(004). → bankCode
* 03 W01ACCOUNT-NO PIC 9(008). → accountNo
* 03 FILLER PIC X(170). → 固定スペース
*/
public class Kyu06Rec {
/** 03 W01EMP-ID PIC X(008). */
private String empId = "";
/** 03 W01PAY-TYPE PIC X(001). */
private String payType = "";
/** 03 W01AMOUNT PIC 9(009). */
private int amount;
/** 03 W01BANK-CODE PIC X(004). */
private String bankCode = "";
/** 03 W01ACCOUNT-NO PIC 9(008). */
private int accountNo;
/** INITIALIZE W01OUTREC 相当:数値→0、文字→SPACE。 */
public void reset() {
empId = "";
payType = "";
amount = 0;
bankCode = "";
accountNo = 0;
}
public void setEmpId(final String v) {
empId = ConvUtil.padRight(v, 8);
}
public void setPayType(final String v) {
payType = ConvUtil.padRight(v, 1);
}
public void setAmount(final int v) {
amount = v;
}
public void setBankCode(final String v) {
bankCode = ConvUtil.padRight(v, 4);
}
public void setAccountNo(final int v) {
accountNo = v;
}
/** 200B 固定長レコード行を生成(FORMAT WRITE)。 */
public String toRecordLine() {
return empId
+ payType
+ ConvUtil.padLeftZero(amount, 9)
+ bankCode
+ ConvUtil.padLeftZero(accountNo, 8)
+ ConvUtil.padRight("", 170); // FILLER X(170)
}
}
+216
View File
@@ -0,0 +1,216 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU04RECSALARY-NET / DEPT-OUT 差引支給額レコード 200B)
* ============================================================
* KYU07DIV 専用DTO。COPY KYU04REC はプロジェクト内に別レイアウトの
* dto.Kyu04RecDB-VIEW)が既存のため、本プログラム固有名 Kyu07R01Rec とする。
* 01 R01INNREC.FD R01INNFIL / COPY KYU04REC REPLACING ==(A)== BY ==R01==
* 03 R01EMP-ID PIC X(008). → empId
* 03 R01REC-TYPE PIC X(001). → recType
* 03 R01EMP-NAME PIC X(040). → empName
* 03 R01DEPT-CODE PIC X(002). → deptCode
* 03 R01REGION-CODE PIC X(002). → regionCode
* 03 R01CATEGORY-CODE PIC X(003). → categoryCode
* 03 R01GROSS-PAYMENT PIC 9(009). → grossPayment
* 03 R01INCOME-TAX PIC 9(009). → incomeTax
* 03 R01INSURANCE PIC 9(009). → insurance
* 03 R01RESIDENT-TAX PIC 9(009). → residentTax
* 03 R01NET-PAYMENT PIC 9(009). → netPayment
* 03 R01TAXABLE-INCOME PIC 9(009). → taxableIncome
* 03 R01PAY-AMOUNT PIC 9(009). → payAmount
* 03 FILLER PIC X(081). → 固定スペース
* ※本レコードは R01(SALARY-NET)入力と D01〜D50(DEPT-OUT)出力で共通利用される。
* DEPT-OUT 出力は「本レコードをそのまま 200B 出力」する(設計書 出力レコード定義準拠)。
*/
public class Kyu07R01Rec {
/** 03 R01EMP-ID PIC X(008). */
private String empId = "";
/** 03 R01REC-TYPE PIC X(001). */
private String recType = "";
/** 03 R01EMP-NAME PIC X(040). */
private String empName = "";
/** 03 R01DEPT-CODE PIC X(002).(分割キー) */
private String deptCode = "";
/** 03 R01REGION-CODE PIC X(002). */
private String regionCode = "";
/** 03 R01CATEGORY-CODE PIC X(003). */
private String categoryCode = "";
/** 03 R01GROSS-PAYMENT PIC 9(009). */
private int grossPayment;
/** 03 R01INCOME-TAX PIC 9(009). */
private int incomeTax;
/** 03 R01INSURANCE PIC 9(009). */
private int insurance;
/** 03 R01RESIDENT-TAX PIC 9(009). */
private int residentTax;
/** 03 R01NET-PAYMENT PIC 9(009). */
private int netPayment;
/** 03 R01TAXABLE-INCOME PIC 9(009). */
private int taxableIncome;
/** 03 R01PAY-AMOUNT PIC 9(009). */
private int payAmount;
/** INITIALIZE R01INNREC 相当:数値→0、文字→SPACE。 */
public void reset() {
empId = "";
recType = "";
empName = "";
deptCode = "";
regionCode = "";
categoryCode = "";
grossPayment = 0;
incomeTax = 0;
insurance = 0;
residentTax = 0;
netPayment = 0;
taxableIncome = 0;
payAmount = 0;
}
/**
* 固定長行(200B)から各項目を解析する(READ R01INNFIL INTO R01INNREC 相当)。
* 不足分は SPACE / 0 で補完。
*/
public void setRecordLine(final String line) {
empId = ConvUtil.padRight(ConvUtil.safeSub(line, 0, 8), 8);
recType = ConvUtil.padRight(ConvUtil.safeSub(line, 8, 9), 1);
empName = ConvUtil.padRight(ConvUtil.safeSub(line, 9, 49), 40);
deptCode = ConvUtil.padRight(ConvUtil.safeSub(line, 49, 51), 2);
regionCode = ConvUtil.padRight(ConvUtil.safeSub(line, 51, 53), 2);
categoryCode = ConvUtil.padRight(ConvUtil.safeSub(line, 53, 56), 3);
grossPayment = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 56, 65), 0);
incomeTax = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 65, 74), 0);
insurance = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 74, 83), 0);
residentTax = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 83, 92), 0);
netPayment = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 92, 101), 0);
taxableIncome = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 101, 110), 0);
payAmount = ConvUtil.parseIntSafe(ConvUtil.safeSub(line, 110, 119), 0);
}
public void setEmpId(final String v) {
empId = ConvUtil.padRight(v, 8);
}
public void setRecType(final String v) {
recType = ConvUtil.padRight(v, 1);
}
public void setEmpName(final String v) {
empName = ConvUtil.padRight(v, 40);
}
public void setDeptCode(final String v) {
deptCode = ConvUtil.padRight(v, 2);
}
public void setRegionCode(final String v) {
regionCode = ConvUtil.padRight(v, 2);
}
public void setCategoryCode(final String v) {
categoryCode = ConvUtil.padRight(v, 3);
}
public void setGrossPayment(final int v) {
grossPayment = v;
}
public void setIncomeTax(final int v) {
incomeTax = v;
}
public void setInsurance(final int v) {
insurance = v;
}
public void setResidentTax(final int v) {
residentTax = v;
}
public void setNetPayment(final int v) {
netPayment = v;
}
public void setTaxableIncome(final int v) {
taxableIncome = v;
}
public void setPayAmount(final int v) {
payAmount = v;
}
public String getEmpId() {
return empId;
}
public String getRecType() {
return recType;
}
public String getEmpName() {
return empName;
}
public String getDeptCode() {
return deptCode;
}
public String getRegionCode() {
return regionCode;
}
public String getCategoryCode() {
return categoryCode;
}
public int getGrossPayment() {
return grossPayment;
}
public int getIncomeTax() {
return incomeTax;
}
public int getInsurance() {
return insurance;
}
public int getResidentTax() {
return residentTax;
}
public int getNetPayment() {
return netPayment;
}
public int getTaxableIncome() {
return taxableIncome;
}
public int getPayAmount() {
return payAmount;
}
/** 200B 固定長レコード行を生成(DEPT-OUT 出力 / WRITE ... FROM R01INNREC 相当)。 */
public String toRecordLine() {
return empId
+ recType
+ empName
+ deptCode
+ regionCode
+ categoryCode
+ ConvUtil.padLeftZero(grossPayment, 9)
+ ConvUtil.padLeftZero(incomeTax, 9)
+ ConvUtil.padLeftZero(insurance, 9)
+ ConvUtil.padLeftZero(residentTax, 9)
+ ConvUtil.padLeftZero(netPayment, 9)
+ ConvUtil.padLeftZero(taxableIncome, 9)
+ ConvUtil.padLeftZero(payAmount, 9)
+ ConvUtil.padRight("", 81); // FILLER X(081)
}
}
+47
View File
@@ -0,0 +1,47 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】COPY KYU99RECERROR-LOG エラーログレコード VB 200B)
* ============================================================
* 01 W02OUTREC.FD W02OUTFIL / COPY KYU99REC REPLACING ==(A)== BY ==W02==
* 03 W02ERR-CATEGORY PIC X(008). → errCategory'CSV-ERR' / 'EMP-ERR ' 等)
* 03 W02ERR-DETAIL PIC X(192). → errDetail(エラー内容)
* ※全 KYU プログラム共用。
*/
public class Kyu99Rec {
/** 03 W02ERR-CATEGORY PIC X(008). */
private String errCategory = "";
/** 03 W02ERR-DETAIL PIC X(192). */
private String errDetail = "";
/** INITIALIZE W02OUTREC 相当。 */
public void reset() {
errCategory = "";
errDetail = "";
}
public void setErrCategory(final String v) {
errCategory = ConvUtil.padRight(v, 8);
}
public void setErrDetail(final String v) {
errDetail = ConvUtil.padRight(v, 192);
}
public String getErrCategory() {
return errCategory;
}
public String getErrDetail() {
return errDetail;
}
/** 200B 固定長レコード行を生成。 */
public String toRecordLine() {
return errCategory + errDetail;
}
}
+115
View File
@@ -0,0 +1,115 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】ZAN01REC 加班申請レコード(80B, FB)
* ============================================================
* 使用プログラム: ZAN01CHK(W01/W02) ZAN02CHK(R01/W01) ZAN03CHK(R01/W01) ZAN04MAT(R01/R02)
* レイアウト: APPL-ID X(8) / EMP-ID 9(8) / APPL-DATE 9(8) /
* START-TIME 9(4) / END-TIME 9(4) / STATUS X(1) /
* OVT-TYPE X(1) / FILLER X(46)
*/
public class Zan01Rec {
private String applId = "";
private String empId = "";
private String applDate = "";
private String startTime = "";
private String endTime = "";
private String status = "";
private String ovtType = "";
private String filler = "";
public Zan01Rec() {
}
/** 固定長行(80B) → 各項目. */
public void fromRecordLine(final String line) {
applId = ConvUtil.safeSub(line, 0, 8).trim();
empId = ConvUtil.safeSub(line, 8, 16).trim();
applDate = ConvUtil.safeSub(line, 16, 24).trim();
startTime = ConvUtil.safeSub(line, 24, 28).trim();
endTime = ConvUtil.safeSub(line, 28, 32).trim();
status = ConvUtil.safeSub(line, 32, 33);
ovtType = ConvUtil.safeSub(line, 33, 34);
filler = ConvUtil.safeSub(line, 34, 80);
}
/** 各項目 → 固定長行(80B). */
public String toRecordLine() {
return ConvUtil.padRight(applId, 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(empId, 0), 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(applDate, 0), 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(startTime, 0), 4)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(endTime, 0), 4)
+ (status.isEmpty() ? " " : status.substring(0, 1))
+ (ovtType.isEmpty() ? " " : ovtType.substring(0, 1))
+ ConvUtil.padRight(filler, 46);
}
public String getApplId() {
return applId;
}
public void setApplId(String applId) {
this.applId = (applId == null) ? "" : applId;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public String getApplDate() {
return applDate;
}
public void setApplDate(String applDate) {
this.applDate = (applDate == null) ? "" : applDate;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = (startTime == null) ? "" : startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = (endTime == null) ? "" : endTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = (status == null) ? "" : status;
}
public String getOvtType() {
return ovtType;
}
public void setOvtType(String ovtType) {
this.ovtType = (ovtType == null) ? "" : ovtType;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+124
View File
@@ -0,0 +1,124 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】ZAN02REC マッチング結果レコード(80B, FB)
* ============================================================
* 使用プログラム: ZAN04MAT(W01/OVT-MATCHED) ZAN05CAL(R01/OVT-SORTED2)
* レイアウト: APPL-ID X(8) / EMP-ID 9(8) / APPL-DATE 9(8) /
* START-TIME 9(4) / END-TIME 9(4) / STATUS X(1) /
* OVT-TYPE X(1) / PROC-SEQ 9(2) / FILLER X(44)
*/
public class Zan02Rec {
private String applId = "";
private String empId = "";
private String applDate = "";
private String startTime = "";
private String endTime = "";
private String status = "";
private String ovtType = "";
private String procSeq = "";
private String filler = "";
public Zan02Rec() {
}
public void fromRecordLine(final String line) {
applId = ConvUtil.safeSub(line, 0, 8).trim();
empId = ConvUtil.safeSub(line, 8, 16).trim();
applDate = ConvUtil.safeSub(line, 16, 24).trim();
startTime = ConvUtil.safeSub(line, 24, 28).trim();
endTime = ConvUtil.safeSub(line, 28, 32).trim();
status = ConvUtil.safeSub(line, 32, 33);
ovtType = ConvUtil.safeSub(line, 33, 34);
procSeq = ConvUtil.safeSub(line, 34, 36).trim();
filler = ConvUtil.safeSub(line, 36, 80);
}
public String toRecordLine() {
return ConvUtil.padRight(applId, 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(empId, 0), 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(applDate, 0), 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(startTime, 0), 4)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(endTime, 0), 4)
+ (status.isEmpty() ? " " : status.substring(0, 1))
+ (ovtType.isEmpty() ? " " : ovtType.substring(0, 1))
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(procSeq, 0), 2)
+ ConvUtil.padRight(filler, 44);
}
public String getApplId() {
return applId;
}
public void setApplId(String applId) {
this.applId = (applId == null) ? "" : applId;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public String getApplDate() {
return applDate;
}
public void setApplDate(String applDate) {
this.applDate = (applDate == null) ? "" : applDate;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = (startTime == null) ? "" : startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = (endTime == null) ? "" : endTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = (status == null) ? "" : status;
}
public String getOvtType() {
return ovtType;
}
public void setOvtType(String ovtType) {
this.ovtType = (ovtType == null) ? "" : ovtType;
}
public String getProcSeq() {
return procSeq;
}
public void setProcSeq(String procSeq) {
this.procSeq = (procSeq == null) ? "" : procSeq;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+119
View File
@@ -0,0 +1,119 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】ZAN03REC 集約結果レコード(80B, FB)
* ============================================================
* 使用プログラム: ZAN05CAL(W01/OVT-SUMMARY) ZAN06UPD(R01)
* レイアウト: APPL-ID X(8) / EMP-ID 9(8) / APPL-DATE 9(8) /
* START-TIME 9(4) / END-TIME 9(4) / OVT-HOURS 9(4)V9(1) /
* OVT-TYPE X(1) / FILLER X(42)
*/
public class Zan03Rec {
private String applId = "";
private String empId = "";
private String applDate = "";
private String startTime = "";
private String endTime = "";
private String ovtHours = ""; // 9(4)V9(1) → 5桁数字文字列
private String ovtType = "";
private String filler = "";
public Zan03Rec() {
}
public void fromRecordLine(final String line) {
applId = ConvUtil.safeSub(line, 0, 8).trim();
empId = ConvUtil.safeSub(line, 8, 16).trim();
applDate = ConvUtil.safeSub(line, 16, 24).trim();
startTime = ConvUtil.safeSub(line, 24, 28).trim();
endTime = ConvUtil.safeSub(line, 28, 32).trim();
ovtHours = ConvUtil.safeSub(line, 32, 37).trim();
ovtType = ConvUtil.safeSub(line, 37, 38);
filler = ConvUtil.safeSub(line, 38, 80);
}
public String toRecordLine() {
return ConvUtil.padRight(applId, 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(empId, 0), 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(applDate, 0), 8)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(startTime, 0), 4)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(endTime, 0), 4)
+ ConvUtil.padLeftZero(ConvUtil.parseIntSafe(ovtHours, 0), 5)
+ (ovtType.isEmpty() ? " " : ovtType.substring(0, 1))
+ ConvUtil.padRight(filler, 42);
}
/** OVT-HOURS を0.1h単位の数値(double)から9(4)V9(1)文字列に設定(切捨). */
public void setOvtHoursFromDouble(final double hrs) {
long tenth = (long) (hrs * 10.0);
this.ovtHours = ConvUtil.padLeftZero(tenth, 5);
}
public String getApplId() {
return applId;
}
public void setApplId(String applId) {
this.applId = (applId == null) ? "" : applId;
}
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = (empId == null) ? "" : empId;
}
public String getApplDate() {
return applDate;
}
public void setApplDate(String applDate) {
this.applDate = (applDate == null) ? "" : applDate;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = (startTime == null) ? "" : startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = (endTime == null) ? "" : endTime;
}
public String getOvtHours() {
return ovtHours;
}
public void setOvtHours(String ovtHours) {
this.ovtHours = (ovtHours == null) ? "" : ovtHours;
}
public String getOvtType() {
return ovtType;
}
public void setOvtType(String ovtType) {
this.ovtType = (ovtType == null) ? "" : ovtType;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+45
View File
@@ -0,0 +1,45 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】ZAN04REC DBCLEAN対象レコード(80B, FB
* ============================================================
* 使用プログラム: ZAN04MAT(W02/OVT-DBCLEAN) ZAN06UPD(R02)
* レイアウト: APPL-ID X(8) / FILLER X(72)
*/
public class Zan04Rec {
private String applId = "";
private String filler = "";
public Zan04Rec() {
}
public void fromRecordLine(final String line) {
applId = ConvUtil.safeSub(line, 0, 8).trim();
filler = ConvUtil.safeSub(line, 8, 80);
}
public String toRecordLine() {
return ConvUtil.padRight(applId, 8)
+ ConvUtil.padRight(filler, 72);
}
public String getApplId() {
return applId;
}
public void setApplId(String applId) {
this.applId = (applId == null) ? "" : applId;
}
public String getFiller() {
return filler;
}
public void setFiller(String filler) {
this.filler = (filler == null) ? "" : filler;
}
}
+47
View File
@@ -0,0 +1,47 @@
package dto;
import util.ConvUtil;
/**
* ============================================================
* 【COBOL→Java】ZAN05REC エラーログレコード(VB 200B)
* ============================================================
* 使用プログラム: ZAN01CHK(W03) ZAN02CHK(W02) ZAN03CHK(W02)
* ZAN04MAT(W03) ZAN06UPD(W01)
* レイアウト: ERR-CATEGORY 9(2) / ERR-DETAIL X(198)
*/
public class Zan05Rec {
private String errCategory = "";
private String errDetail = "";
public Zan05Rec() {
}
public void fromRecordLine(final String line) {
errCategory = ConvUtil.safeSub(line, 0, 2).trim();
errDetail = ConvUtil.safeSub(line, 2, 200);
}
/** VBレコード出力用(200B): カテゴリ(2) + 詳細(198). */
public String toRecordLine() {
return ConvUtil.padLeftZero(ConvUtil.parseIntSafe(errCategory, 0), 2)
+ ConvUtil.padRight(errDetail, 198);
}
public String getErrCategory() {
return errCategory;
}
public void setErrCategory(String errCategory) {
this.errCategory = (errCategory == null) ? "" : errCategory;
}
public String getErrDetail() {
return errDetail;
}
public void setErrDetail(String errDetail) {
this.errDetail = (errDetail == null) ? "" : errDetail;
}
}
+53
View File
@@ -0,0 +1,53 @@
package dto;
/**
* ============================================================
* 【COBOL→Java】COPY ZANCHKACSUB04CHK 項目チェック用連絡領域)
* ============================================================
* 01 C01CHKPAR.
* 03 C01CHKTYP PIC X(008). → c01ChkTyp(チェックタイプ DATE/TIME/NUM/EMPID
* 03 C01CHKDAT PIC X(080). → c01ChkDat(チェック対象データ)
* 03 C01CHKRRC PIC 9(004). → c01ChkRrc(復帰コード 0=正常)
*/
public class ZanchkacPar {
/** 03 C01CHKTYP PIC X(008). */
private String c01ChkTyp = "";
/** 03 C01CHKDAT PIC X(080). */
private String c01ChkDat = "";
/** 03 C01CHKRRC PIC 9(004). */
private int c01ChkRrc;
/** INITIALIZE C01CHKPAR 相当。 */
public void reset() {
c01ChkTyp = "";
c01ChkDat = "";
c01ChkRrc = 0;
}
public String getC01ChkTyp() {
return c01ChkTyp;
}
public void setC01ChkTyp(String c01ChkTyp) {
this.c01ChkTyp = (c01ChkTyp == null) ? "" : c01ChkTyp;
}
public String getC01ChkDat() {
return c01ChkDat;
}
public void setC01ChkDat(String c01ChkDat) {
this.c01ChkDat = (c01ChkDat == null) ? "" : c01ChkDat;
}
public int getC01ChkRrc() {
return c01ChkRrc;
}
public void setC01ChkRrc(int c01ChkRrc) {
this.c01ChkRrc = c01ChkRrc;
}
}
+40
View File
@@ -0,0 +1,40 @@
package dto;
/**
* ============================================================
* 【COBOL→Java】COPY ZANDATACSUB01DAT 運用日付取得用連絡領域)
* ============================================================
* 01 D01UBSPAR.
* 03 D01FKICOD PIC S9(004) COMP. → d01FkiCod(復帰コード 0=正常)
* 03 D01UBSUDATE PIC 9(008). → d01UbsUdate(運用日付 YYYYMMDD
*/
public class ZandatacPar {
/** 03 D01FKICOD PIC S9(004) COMP. */
private int d01FkiCod;
/** 03 D01UBSUDATE PIC 9(008). */
private int d01UbsUdate;
/** INITIALIZE D01UBSPAR 相当。 */
public void reset() {
d01FkiCod = 0;
d01UbsUdate = 0;
}
public int getD01FkiCod() {
return d01FkiCod;
}
public void setD01FkiCod(int d01FkiCod) {
this.d01FkiCod = d01FkiCod;
}
public int getD01UbsUdate() {
return d01UbsUdate;
}
public void setD01UbsUdate(int d01UbsUdate) {
this.d01UbsUdate = d01UbsUdate;
}
}
+27
View File
@@ -0,0 +1,27 @@
package dto;
/**
* ============================================================
* 【COBOL→Java】COPY ZANENDACSUB03END ABEND処理用連絡領域)
* ============================================================
* 01 E01ABDPAR.
* 03 E01ABDCOD PIC 9(003). → e01AbdCodABENDコード)
*/
public class ZanendacPar {
/** 03 E01ABDCOD PIC 9(003). */
private int e01AbdCod;
/** INITIALIZE E01ABDPAR 相当。 */
public void reset() {
e01AbdCod = 0;
}
public int getE01AbdCod() {
return e01AbdCod;
}
public void setE01AbdCod(int e01AbdCod) {
this.e01AbdCod = e01AbdCod;
}
}
+150
View File
@@ -0,0 +1,150 @@
package dto;
/**
* ============================================================
* 【COBOL→Java】COPY ZANMSGACSUB02MSG メッセージ出力用連絡領域)
* ============================================================
* 01 M00MHOPAR.
* 03 M00MSGCOD PIC 9(003). → m00MsgCod(メッセージ番号)
* 03 M00UMKDATS22-01 PIC X(030). → m00Umkdats2201(置換項目1
* ~ (置換項目2~10 同型)
* 03 M00UMKDATS22-10 PIC X(030). → m00Umkdats2210
*/
public class ZanmsgacPar {
/** 03 M00MSGCOD PIC 9(003). */
private int m00MsgCod;
/** 03 M00UMKDATS22-01 PIC X(030). */
private String m00Umkdats2201 = "";
/** 03 M00UMKDATS22-02 PIC X(030). */
private String m00Umkdats2202 = "";
/** 03 M00UMKDATS22-03 PIC X(030). */
private String m00Umkdats2203 = "";
/** 03 M00UMKDATS22-04 PIC X(030). */
private String m00Umkdats2204 = "";
/** 03 M00UMKDATS22-05 PIC X(030). */
private String m00Umkdats2205 = "";
/** 03 M00UMKDATS22-06 PIC X(030). */
private String m00Umkdats2206 = "";
/** 03 M00UMKDATS22-07 PIC X(030). */
private String m00Umkdats2207 = "";
/** 03 M00UMKDATS22-08 PIC X(030). */
private String m00Umkdats2208 = "";
/** 03 M00UMKDATS22-09 PIC X(030). */
private String m00Umkdats2209 = "";
/** 03 M00UMKDATS22-10 PIC X(030). */
private String m00Umkdats2210 = "";
/** INITIALIZE M00MHOPAR 相当:数値→0、文字→SPACE。 */
public void reset() {
m00MsgCod = 0;
m00Umkdats2201 = "";
m00Umkdats2202 = "";
m00Umkdats2203 = "";
m00Umkdats2204 = "";
m00Umkdats2205 = "";
m00Umkdats2206 = "";
m00Umkdats2207 = "";
m00Umkdats2208 = "";
m00Umkdats2209 = "";
m00Umkdats2210 = "";
}
public int getM00MsgCod() {
return m00MsgCod;
}
public void setM00MsgCod(int m00MsgCod) {
this.m00MsgCod = m00MsgCod;
}
public String getM00Umkdats2201() {
return m00Umkdats2201;
}
public void setM00Umkdats2201(String v) {
this.m00Umkdats2201 = (v == null) ? "" : v;
}
public String getM00Umkdats2202() {
return m00Umkdats2202;
}
public void setM00Umkdats2202(String v) {
this.m00Umkdats2202 = (v == null) ? "" : v;
}
public String getM00Umkdats2203() {
return m00Umkdats2203;
}
public void setM00Umkdats2203(String v) {
this.m00Umkdats2203 = (v == null) ? "" : v;
}
public String getM00Umkdats2204() {
return m00Umkdats2204;
}
public void setM00Umkdats2204(String v) {
this.m00Umkdats2204 = (v == null) ? "" : v;
}
public String getM00Umkdats2205() {
return m00Umkdats2205;
}
public void setM00Umkdats2205(String v) {
this.m00Umkdats2205 = (v == null) ? "" : v;
}
public String getM00Umkdats2206() {
return m00Umkdats2206;
}
public void setM00Umkdats2206(String v) {
this.m00Umkdats2206 = (v == null) ? "" : v;
}
public String getM00Umkdats2207() {
return m00Umkdats2207;
}
public void setM00Umkdats2207(String v) {
this.m00Umkdats2207 = (v == null) ? "" : v;
}
public String getM00Umkdats2208() {
return m00Umkdats2208;
}
public void setM00Umkdats2208(String v) {
this.m00Umkdats2208 = (v == null) ? "" : v;
}
public String getM00Umkdats2209() {
return m00Umkdats2209;
}
public void setM00Umkdats2209(String v) {
this.m00Umkdats2209 = (v == null) ? "" : v;
}
public String getM00Umkdats2210() {
return m00Umkdats2210;
}
public void setM00Umkdats2210(String v) {
this.m00Umkdats2210 = (v == null) ? "" : v;
}
}
+51
View File
@@ -0,0 +1,51 @@
package dto;
/**
* ============================================================
* 【COBOL→Java】COPY ZANTIMACSUB05TIM 時刻丸め用連絡領域)
* ============================================================
* 01 T01TIMPAR.
* 03 T01TIMHRS PIC 9(004)V9(001). → t01Timhrs(入力時間、1桁小数)
* 03 T01TIMOUT PIC 9(004)V9(001). → t01Timout(出力時間、1桁小数)
* 03 T01TIMRRC PIC 9(004). → t01Timrrc(丸めモード)
*/
public class ZantimacPar {
/** 03 T01TIMHRS PIC 9(004)V9(001). */
private double t01Timhrs;
/** 03 T01TIMOUT PIC 9(004)V9(001). */
private double t01Timout;
/** 03 T01TIMRRC PIC 9(004). */
private int t01Timrrc;
/** INITIALIZE T01TIMPAR 相当。 */
public void reset() {
t01Timhrs = 0;
t01Timout = 0;
t01Timrrc = 0;
}
public double getT01Timhrs() {
return t01Timhrs;
}
public void setT01Timhrs(double v) {
this.t01Timhrs = v;
}
public double getT01Timout() {
return t01Timout;
}
public void setT01Timout(double v) {
this.t01Timout = v;
}
public int getT01Timrrc() {
return t01Timrrc;
}
public void setT01Timrrc(int v) {
this.t01Timrrc = v;
}
}
+144
View File
@@ -0,0 +1,144 @@
package util;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* ============================================================
* 【COBOL→Java 标准例】PIC 数据转换工具类
* ============================================================
* COBOL 的 PIC 子句与 Java 类型之间的转换统一收敛到本类,
* 保证全项目转换规则一致。
*
* 基本类型对应规则:
* PIC X(n) → String (定长字符串,不足右补空格)
* PIC 9(n) → int / long (无小数数值)
* PIC 9(n)V9(m) → BigDecimal (带隐含小数点的金额等)
* PIC S9(...) → 同上(符号由 Java 类型的符号承载)
* 88 级条件名 → static final 常量 + isXxx() 判定方法
*/
public final class ConvUtil {
private ConvUtil() {
}
/**
* PIC X(n):字符串左对齐、右侧补空格到指定长度。
* COBOL: MOVE "ABC" TO WS-X-10 (WS-X-10 PIC X(10))
*/
public static String padRight(final String val, final int len) {
String s = (val == null) ? "" : val;
if (s.length() >= len) {
return s.substring(0, len);
}
StringBuilder sb = new StringBuilder(s);
while (sb.length() < len) {
sb.append(' ');
}
return sb.toString();
}
/**
* PIC 9(n):数值左对齐(左侧补零)到指定长度。
* COBOL: MOVE 123 TO WS-N-5 (WS-N-5 PIC 9(5)) → "00123"
* 超长时高位截断,与 COBOL 高位丢失的行为一致。
*/
public static String padLeftZero(final long val, final int len) {
return padLeftZeroStr(Long.toString(val), len);
}
/**
* PIC 9(n):数字文字列左对齐(左侧补零)到指定长度。
* COBOL: MOVE "20260610" TO WS-DATE (WS-DATE PIC 9(8)) → "20260610"
* null / 空文字はそのまま(呼出側が満桁ゼロ埋めを保証しない場合の安全性)。
*/
public static String padLeftZero(final String val, final int len) {
return padLeftZeroStr(val, len);
}
private static String padLeftZeroStr(final String digits, final int len) {
String s = (digits == null) ? "" : digits;
if (s.length() >= len) {
return s.substring(s.length() - len);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len - s.length(); i++) {
sb.append('0');
}
return sb.append(s).toString();
}
/**
* PIC 9(n)V9(m) → BigDecimal:外部数据是"无小数点的数字串",按隐含小数位还原。
* COBOL: WS-SALARY PIC 9(7)V99,文件中为 "003500000" → 35000.00
* SPACE / LOW-VALUE / 非数字按 0 处理。
*/
public static BigDecimal toDecimal(final String zoned, final int scale) {
String s = (zoned == null) ? "" : zoned.trim();
if (!isNumeric(s)) {
return BigDecimal.ZERO;
}
return new BigDecimal(s).movePointLeft(scale);
}
/**
* BigDecimal → PIC 9(n)V9(m) 定长数字串:四舍五入(ROUNDED)、去小数点、左补零。
* COBOL: COMPUTE WS-X ROUNDED = ... ROUNDED 对应 HALF_UP
*/
public static String formatZoned(final BigDecimal val, final int intLen, final int scale) {
BigDecimal v = (val == null ? BigDecimal.ZERO : val)
.setScale(scale, RoundingMode.HALF_UP)
.movePointRight(scale)
.setScale(0, RoundingMode.DOWN);
return padLeftZeroStr(v.toBigInteger().toString(), intLen + scale);
}
/**
* NUMERIC 检查。
* COBOL: IF WS-X IS NUMERIC ...
*/
public static boolean isNumeric(final String s) {
if (s == null || s.isEmpty()) {
return false;
}
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c < '0' || c > '9') {
return false;
}
}
return true;
}
/**
* 固定長行から [start, end) を安全に切り出し(省略時は空文字)。
* COBOL の参照変更(WRK-X(m:n))の読取り相当。
*/
public static String safeSub(final String line, final int start, final int end) {
if (line == null) {
return "";
}
int s = Math.max(0, start);
int e = Math.min(line.length(), end);
if (s >= e) {
return "";
}
return line.substring(s, e);
}
/**
* 数字文字列を int に変換(非数字/空は def を返す)。
* COBOL: MOVE '0000123' TO WS-N9 相当の安全変換。
*/
public static int parseIntSafe(final String s, final int def) {
String t = (s == null) ? "" : s.trim();
if (!isNumeric(t) || t.isEmpty()) {
return def;
}
try {
return Integer.parseInt(t);
} catch (NumberFormatException e) {
return def;
}
}
}