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 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 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() { // COBOL 通り: RESET は MONTHLY_ABSENCE のみ DELETE(DAILY_RECORDS は削除せず通常 INSERT) // ※RESET 再実行時に DAILY_RECORDS 既存行は PK 違反の可能性(COBOL も同様/運用上は事前クリア想定) 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 と同一結果)、 // 他は ADD(V9(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 時の既存レコード削除(COBOL 通り MONTHLY_ABSENCE のみ。DAILY_RECORDS は削除せず通常 INSERT). */ private void resetDelete() { 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); return; } } /** * 設計書 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エラー処理 9100DBERRSOR(ROLLBACK + 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(); } /** メッセージ編集出力 4000MSGOUTSOR(SUB02MSG 呼出). */ 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); } }