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
+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);
}
}