diff --git a/backend/app/payroll/vouchers/schemas.py b/backend/app/payroll/vouchers/schemas.py index f98c283..7813ca0 100644 --- a/backend/app/payroll/vouchers/schemas.py +++ b/backend/app/payroll/vouchers/schemas.py @@ -7,8 +7,8 @@ class VoucherExportRequest(BaseModel): """账票エクスポート要求""" start_year: int = Field(..., ge=2000, le=2099) start_month: int = Field(..., ge=1, le=12) - end_year: int = Field(default=None, ge=2000, le=2099) - end_month: int = Field(default=None, ge=1, le=12) + end_year: Optional[int] = Field(default=None) + end_month: Optional[int] = Field(default=None) employee_ids: List[int] = Field(..., min_items=1) voucher_type: str = Field(default="all", pattern="^(all|salary|bonus)$") format: str = Field(default="preview", pattern="^(preview|csv|pdf)$") diff --git a/backend/app/payroll/vouchers/service.py b/backend/app/payroll/vouchers/service.py index d2c6204..f403e9b 100644 --- a/backend/app/payroll/vouchers/service.py +++ b/backend/app/payroll/vouchers/service.py @@ -78,6 +78,8 @@ def get_salary_data_for_period( start_ym = start_year * 100 + start_month end_ym = end_year * 100 + end_month + logger.info(f"給与検索: employee_id={employee_id}, start_ym={start_ym}, end_ym={end_ym}") + cur.execute(""" SELECT e.employee_id, e.employee_code, e.name, @@ -96,6 +98,7 @@ def get_salary_data_for_period( """, (employee_id, start_ym, end_ym)) rows = cur.fetchall() + logger.info(f"給与データ件数: {len(rows)}") data = [] for row in rows: # Handle both dict and tuple formats @@ -192,6 +195,8 @@ def get_bonus_data_for_period( start_ym = start_year * 100 + start_month end_ym = end_year * 100 + end_month + logger.info(f"賞与検索: employee_id={employee_id}, start_ym={start_ym}, end_ym={end_ym}") + cur.execute(""" SELECT e.employee_id, e.employee_code, e.name, @@ -209,6 +214,7 @@ def get_bonus_data_for_period( """, (employee_id, start_ym, end_ym)) rows = cur.fetchall() + logger.info(f"賞与データ件数: {len(rows)}") data = [] for row in rows: # Handle both dict and tuple formats @@ -342,11 +348,18 @@ def build_voucher_summary( def export_vouchers(request: VoucherExportRequest) -> VoucherExportResponse: """账票エクスポート処理(メイン)""" try: + # リクエストログ + end_label = f"{request.end_year:04d}-{request.end_month:02d}" if request.end_year else "None" + logger.info(f"エクスポート要求: start={request.start_year:04d}-{request.start_month:02d}, " + f"end={end_label}, " + f"type={request.voucher_type}, employees={request.employee_ids}") + # 日付範囲検証 start_year, start_month, end_year, end_month = validate_date_range( request.start_year, request.start_month, request.end_year, request.end_month ) + logger.info(f"検証後の日付範囲: {start_year:04d}-{start_month:02d} ~ {end_year:04d}-{end_month:02d}") # 給与・賞与データ取得 salary_data = {} @@ -358,6 +371,7 @@ def export_vouchers(request: VoucherExportRequest) -> VoucherExportResponse: emp_id, start_year, start_month, end_year, end_month ) salary_data[emp_id] = data + logger.info(f"給与データ (emp_id={emp_id}): {len(data)}件") if request.voucher_type in ["all", "bonus"]: for emp_id in request.employee_ids: @@ -365,8 +379,13 @@ def export_vouchers(request: VoucherExportRequest) -> VoucherExportResponse: emp_id, start_year, start_month, end_year, end_month ) bonus_data[emp_id] = data + logger.info(f"賞与データ (emp_id={emp_id}): {len(data)}件") # データの存在判定 + total_salary = sum(len(v) for v in salary_data.values()) + total_bonus = sum(len(v) for v in bonus_data.values()) + logger.info(f"合計: 給与={total_salary}件, 賞与={total_bonus}件") + has_any_data = any( salary_data.get(emp_id, []) or bonus_data.get(emp_id, []) for emp_id in request.employee_ids @@ -376,7 +395,7 @@ def export_vouchers(request: VoucherExportRequest) -> VoucherExportResponse: return VoucherExportResponse( status="no_data", has_data=False, - message="選定條件下、沒有找到任何數據。請檢查日期範圍和従業員選擇。" + message="選定条件下にデータが見つかりません。日付範囲と従業員選択を確認してください。" ) # 摘要構築 @@ -401,7 +420,7 @@ def export_vouchers(request: VoucherExportRequest) -> VoucherExportResponse: # メッセージ構築 emp_with_data = len([s for s in summary if s.has_salary_data or s.has_bonus_data]) - message = f"✓ 成功查詢: {emp_with_data}位従業員有數據" + message = f"✓ 検索成功: {emp_with_data}人の従業員がデータを持ちます" return VoucherExportResponse( status="success", diff --git a/frontend/payroll-calculation.html b/frontend/payroll-calculation.html index 79300c9..7e21560 100644 --- a/frontend/payroll-calculation.html +++ b/frontend/payroll-calculation.html @@ -1731,9 +1731,9 @@ // 年月を解析 const [startYear, startMonth] = startYM.split("-").map(Number); const endYM = document.getElementById("voucherEndYearMonth").value; - const [endYear, endMonth] = endYM - ? endYM.split("-").map(Number) - : [startYear, startMonth]; + const endParts = endYM ? endYM.split("-").map(Number) : null; + const endYear = endParts ? endParts[0] : null; + const endMonth = endParts ? endParts[1] : null; const voucherType = document.querySelector( "input[name='voucherType']:checked", @@ -1751,6 +1751,8 @@ format: "preview", }; + console.log("送信ペイロード:", JSON.stringify(payload, null, 2)); + const response = await fetch(`${API_BASE}/payroll/vouchers/export`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -1758,6 +1760,7 @@ }); const result = await response.json(); + console.log("レスポンス:", result); if (!result.has_data) { alert(result.message || "データが見つかりません"); @@ -1845,9 +1848,8 @@ const generationDate = new Date().toLocaleDateString("ja-JP"); - // グループ化: 従業員ごと、年月ごと - let currentEmployeeId = null; - let currentYearMonth = null; + // グループ化: 従業員ごと、年月ごと、給与/賞与ごと + let currentKey = null; let pageOpen = false; if (result.summary && Array.isArray(result.summary)) { @@ -1864,16 +1866,15 @@ // 給与データの処理 salaries.forEach((salary) => { const salaryYearMonth = `${salary.payroll_year}-${salary.payroll_month}`; + const currentKey_new = `${emp.employee_id}-${salaryYearMonth}-salary`; const paymentDate = `${salary.payroll_year}年${String(salary.payroll_month).padStart(2, "0")}月${getLastDayOfMonth(salary.payroll_year, salary.payroll_month)}日`; - if ( - currentEmployeeId !== emp.employee_id || - currentYearMonth !== salaryYearMonth - ) { + + // 新しいキーが異なる場合は新しいページを開く + if (currentKey !== currentKey_new) { if (pageOpen) { printHtml += ""; } - currentEmployeeId = emp.employee_id; - currentYearMonth = salaryYearMonth; + currentKey = currentKey_new; printHtml += '
'; printHtml += `
帳票生成日: ${generationDate}
`; pageOpen = true; @@ -1978,16 +1979,15 @@ // 賞与データの処理 bonuses.forEach((bonus) => { const bonusYearMonth = `${bonus.bonus_year}-${bonus.bonus_month}`; + const currentKey_new = `${emp.employee_id}-${bonusYearMonth}-bonus`; const bonusPaymentDate = `${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月${getLastDayOfMonth(bonus.bonus_year, bonus.bonus_month)}日`; - if ( - currentEmployeeId !== emp.employee_id || - currentYearMonth !== bonusYearMonth - ) { + + // 新しいキーが異なる場合は新しいページを開く + if (currentKey !== currentKey_new) { if (pageOpen) { printHtml += "
"; } - currentEmployeeId = emp.employee_id; - currentYearMonth = bonusYearMonth; + currentKey = currentKey_new; printHtml += '
'; printHtml += `
帳票生成日: ${generationDate}
`; pageOpen = true; @@ -2101,9 +2101,9 @@ const [startYear, startMonth] = startYM.split("-").map(Number); const endYM = document.getElementById("voucherEndYearMonth").value; - const [endYear, endMonth] = endYM - ? endYM.split("-").map(Number) - : [startYear, startMonth]; + const endParts = endYM ? endYM.split("-").map(Number) : null; + const endYear = endParts ? endParts[0] : null; + const endMonth = endParts ? endParts[1] : null; const voucherType = document.querySelector( "input[name='voucherType']:checked", ).value;