ken_nogi/XVBA/新版着工要因閲覧シート/vba-files/Module/Module1.bas
Kenichiro NOGI 1eb1a7dc87 fix: BuildViewFromSheetをセル座標指定からテーブル名(FilterTable/SorterTable)参照に変更
Filter表・Sorter表の配置をユーザーが移動したところ、固定セル座標(F3/J3)読み取りのため条件が反映されずViewが空になっていた。
ListObjects("FilterTable")/("SorterTable")をテーブル名で取得し、1列目=項目名・2列目=値/昇降順という列位置ベースで読むよう変更。テーブルをシート上のどこに配置しても動作する。
デバッグ用に追加していたリクエストボディ出力ログも削除。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 09:43:27 +09:00

580 lines
19 KiB
QBasic
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Attribute VB_Name = "Module1"
Option Explicit
Public apiKey As String
Public baseURL As String
Public yearFilter As String
Public tableId As String
Public defaultSh As Worksheet
Function init()
'Pleasanter API KEY
apiKey = "6504c8a807677a3a576e10327f3c19876c55736ee45d4a845796b9e7f5e087bfd4bd0d8184863da8cf1733ca635111432ab334aea59102b06a96ee6d2c05190d"
baseURL = "https://nextoffice.Next-hd.co.jp"
tableId = Range("着工要因ID")
Set defaultSh = ThisWorkbook.Sheets("基本情報")
End Function
'******************************************************************************
Sub run()
'初期化処理
Call init
On Error GoTo Cleanup
defaultSh.Range("D4").Value = Now
defaultSh.Range("D3").Value = "取込処理中..."
Application.ScreenUpdating = False '画面更新停止
Application.Calculation = xlManual '自動計算停止
'------------------------------------------
Debug.Print "処理開始"
Dim exportSuccess As Boolean
Dim getSuccess As Boolean
exportSuccess = runExportFlow()
getSuccess = runGetFlow()
Debug.Print "処理終了"
'------------------------------------------
Cleanup:
Application.Calculation = xlAutomatic '自動計算再開
Application.ScreenUpdating = True '画面更新再開
defaultSh.Range("D5").Value = Now
defaultSh.Range("D6").Value = (defaultSh.Range("D5").Value - defaultSh.Range("D4").Value) * 86400
If Err.Number <> 0 Then
defaultSh.Range("D3").Value = "取込処理失敗(エラー): " & Err.Description
Err.Clear
ElseIf Not (exportSuccess And getSuccess) Then
defaultSh.Range("D3").Value = "取込処理失敗(データ取得エラー)"
Else
defaultSh.Range("D3").Value = "取込処理完了"
End If
End Sub
'******************************************************************************
'Export方式CSV全件取得でのデータ取込処理
'成功時True、データを取得できなかった場合Falseを返す
Function runExportFlow() As Boolean
Dim resData As Variant
Dim t0 As Double, t1 As Double, t2 As Double
Debug.Print "データ取得処理開始Export方式"
t0 = Timer
resData = exportCSVData(tableId)
t1 = Timer
Debug.Print " exportCSVData(API通信) 所要時間: " & Format(t1 - t0, "0.000") & "秒"
If IsEmpty(resData) Or resData = "" Then
Debug.Print "データが取得できませんでした"
runExportFlow = False
Else
Call exportCSVDataToSheet(resData, tableId & "E")
t2 = Timer
Debug.Print " exportCSVDataToSheet(パース+書込) 所要時間: " & Format(t2 - t1, "0.000") & "秒"
runExportFlow = True
End If
End Function
'******************************************************************************
'Get方式api-record-get-multiでのデータ取込処理
'成功時True、データを取得できなかった場合Falseを返す
Function runGetFlow() As Boolean
Dim records As Collection
Dim t0 As Double, t1 As Double, t2 As Double
Debug.Print "データ取得処理開始Get方式"
t0 = Timer
Set records = getRecordsData(tableId)
t1 = Timer
Debug.Print " getRecordsData(API通信) 所要時間: " & Format(t1 - t0, "0.000") & "秒"
If records Is Nothing Then
Debug.Print "データが取得できませんでした"
runGetFlow = False
ElseIf records.count = 0 Then
Debug.Print "データが取得できませんでした"
runGetFlow = False
Else
Call getRecordsDataToSheet(records, tableId & "G")
t2 = Timer
Debug.Print " getRecordsDataToSheet(書込) 所要時間: " & Format(t2 - t1, "0.000") & "秒"
runGetFlow = True
End If
End Function
'******************************************************************************
'CSVデータを取得
Function exportCSVData(tableId As String) As Variant
'共通変数
Dim apiUrl As String
Dim apiUrlParam As String
'リクエストURL
apiUrl = baseURL & "/pleasanter/api/items/" & tableId & "/"
apiUrlParam = "export"
'ヘッダ
Dim apiHeaders As New Dictionary
apiHeaders.Add "Content-Type", "application/json;charset=utf-8"
'リクエストデータ
Dim apiBody As New Dictionary
apiBody.Add "ApiVersion", "1.1"
apiBody.Add "ApiKey", apiKey
apiBody.Add "ExportId", "1"
'HTTPリクエスト送信メソッド呼び出し
Dim res As Object
Set res = callRestApi("POST", apiUrl, apiUrlParam, apiHeaders, apiBody)
If res("StatusCode") = 200 Then
Debug.Print "CSVデータの取得に成功しました"
exportCSVData = res("Response")("Content")
End If
End Function
'******************************************************************************
'CSVデータをシートに書き込む
Function exportCSVDataToSheet(csvData As Variant, targetSheet As String)
Debug.Print "CSVデータをシートに出力開始: " & targetSheet
'------------------------------------------
'CSVデータを「テーブル」部分に出力する処理
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Sheets(targetSheet)
On Error Goto 0
If ws Is Nothing Then
Debug.Print "シートが存在しないため新規作成: " & targetSheet
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.count))
ws.Name = targetSheet
End If
'CSVデータをテーブルに挿入ダブルクォート内の改行・カンマに対応したCSVパース
Dim i As Long, j As Long
Dim arr() As Variant
Dim maxCols As Long
Dim rows As Collection
Set rows = ParseCsv(csvData)
If rows.count <= 1 Then
MsgBox "データが存在しません(ヘッダのみ): " & targetSheet, vbExclamation
Exit Function
End If
maxCols = 0
Dim rowArr As Variant
For Each rowArr In rows
If UBound(rowArr) + 1 > maxCols Then
maxCols = UBound(rowArr) + 1
End If
Next rowArr
' 二次元配列を初期化
ReDim arr(0 To rows.count - 1, 0 To maxCols - 1)
' 配列に格納
i = 0
For Each rowArr In rows
For j = LBound(rowArr) To UBound(rowArr)
arr(i, j) = rowArr(j)
Next j
i = i + 1
Next rowArr
'テーブルListObject取得
Dim tbl As ListObject
On Error Resume Next
Set tbl = ws.ListObjects(targetSheet)
On Error Goto 0
If tbl Is Nothing Then
Debug.Print "テーブルが存在しないため新規作成: " & targetSheet
Dim headerRange As Range
Set headerRange = ws.Range("A1").Resize(1, maxCols)
For j = 0 To maxCols - 1
headerRange.Cells(1, j + 1).Value = arr(0, j)
Next j
Set tbl = ws.ListObjects.Add(xlSrcRange, headerRange, , xlYes)
tbl.Name = targetSheet
End If
'テーブルのデータ部分をクリア
If Not tbl.DataBodyRange Is Nothing Then
tbl.DataBodyRange.ClearContents
End If
' テーブルにデータを追加(ヘッダ行(arr(0,*))を除いたデータ部分のみをDataBodyRangeに書き込む
tbl.Resize tbl.Range.Resize(rows.count, maxCols)
Dim dataArr() As Variant
ReDim dataArr(1 To rows.count - 1, 1 To maxCols)
For i = 1 To rows.count - 1
For j = 1 To maxCols
dataArr(i, j) = arr(i, j - 1)
Next j
Next i
tbl.DataBodyRange.Value = dataArr
'------------------------------------------
Debug.Print "CSVデータをシートに出力完了: " & targetSheet
End Function
'******************************************************************************
'CSV文字列をパースし、行ごとのフィールド配列を格納したCollectionを返す
'ダブルクォートで囲まれたフィールド内の改行・カンマ・エスケープされた""に対応RFC4180準拠
Private Function ParseCsv(Byval csvText As String) As Collection
Dim rows As New Collection
Dim fields As Collection
Set fields = New Collection
Dim field As String
Dim inQuotes As Boolean
Dim i As Long, ch As String, nextCh As String
Dim textLen As Long
textLen = Len(csvText)
inQuotes = False
field = ""
i = 1
Do While i <= textLen
ch = Mid(csvText, i, 1)
If inQuotes Then
If ch = """" Then
nextCh = Mid(csvText, i + 1, 1)
If nextCh = """" Then
field = field & """"
i = i + 1
Else
inQuotes = False
End If
Else
field = field & ch
End If
Else
Select Case ch
Case """"
inQuotes = True
Case ","
fields.Add field
field = ""
Case vbCr, vbLf
If ch = vbCr And Mid(csvText, i + 1, 1) = vbLf Then i = i + 1
fields.Add field
field = ""
If Not (fields.count = 1 And fields(1) = "") Then
rows.Add CollectionToArray(fields)
End If
Set fields = New Collection
Case Else
field = field & ch
End Select
End If
i = i + 1
Loop
'最終フィールド・行(末尾に改行が無い場合)
If field <> "" Or fields.count > 0 Then
fields.Add field
rows.Add CollectionToArray(fields)
End If
Set ParseCsv = rows
End Function
'Collection1次元を0始まりのVariant配列に変換
Private Function CollectionToArray(Byval col As Collection) As Variant
Dim arr() As Variant
ReDim arr(0 To col.count - 1)
Dim k As Long
For k = 1 To col.count
arr(k - 1) = col(k)
Next k
CollectionToArray = arr
End Function
'******************************************************************************
'基本情報シート上の「FilterTable」「SorterTable」という名前のExcelテーブルListObject
'テーブル名で探して読み取り、APIリクエストのViewパラメータに渡すDictionaryを組み立てる。
'セルの絶対位置には依存しないテーブルを移動しても動作する。各テーブルは1列目=項目名、2列目=値/昇降順とする。
'Filter・Sorterともに0件の場合はNothingを返すViewパラメータ自体を付与しないため
Function BuildViewFromSheet() As Object
Dim colFilter As New Dictionary
Dim filterTbl As ListObject
On Error Resume Next
Set filterTbl = defaultSh.ListObjects("FilterTable")
On Error GoTo 0
If Not filterTbl Is Nothing Then
If Not filterTbl.DataBodyRange Is Nothing Then
Dim r As Long
For r = 1 To filterTbl.DataBodyRange.Rows.count
Dim filterKey As String
filterKey = Trim(filterTbl.DataBodyRange.Cells(r, 1).Value)
If filterKey <> "" Then
colFilter.Add filterKey, "[" & Chr(34) & CStr(filterTbl.DataBodyRange.Cells(r, 2).Value) & Chr(34) & "]"
End If
Next r
End If
End If
Dim colSorter As New Dictionary
Dim sorterTbl As ListObject
On Error Resume Next
Set sorterTbl = defaultSh.ListObjects("SorterTable")
On Error GoTo 0
If Not sorterTbl Is Nothing Then
If Not sorterTbl.DataBodyRange Is Nothing Then
Dim r2 As Long
For r2 = 1 To sorterTbl.DataBodyRange.Rows.count
Dim sorterKey As String
sorterKey = Trim(sorterTbl.DataBodyRange.Cells(r2, 1).Value)
If sorterKey <> "" Then
colSorter.Add sorterKey, sorterTbl.DataBodyRange.Cells(r2, 2).Value
End If
Next r2
End If
End If
If colFilter.count = 0 And colSorter.count = 0 Then
Set BuildViewFromSheet = Nothing
Exit Function
End If
Dim viewObj As New Dictionary
If colFilter.count > 0 Then
viewObj.Add "ColumnFilterHash", colFilter
End If
If colSorter.count > 0 Then
viewObj.Add "ColumnSorterHash", colSorter
End If
Set BuildViewFromSheet = viewObj
End Function
'******************************************************************************
'Get方式api-record-get-multiでレコードを取得する
'成功時、レコードDictionaryのCollection(Response.Data)を返す
Function getRecordsData(tableId As String) As Collection
Dim apiUrl As String
Dim apiUrlParam As String
apiUrl = baseURL & "/pleasanter/api/items/" & tableId & "/"
apiUrlParam = "get"
Dim apiHeaders As New Dictionary
apiHeaders.Add "Content-Type", "application/json;charset=utf-8"
Dim apiBody As New Dictionary
apiBody.Add "ApiVersion", "1.1"
apiBody.Add "ApiKey", apiKey
Dim viewObj As Object
Set viewObj = BuildViewFromSheet()
If Not viewObj Is Nothing Then
apiBody.Add "View", viewObj
End If
Dim res As Object
Set res = callRestApi("POST", apiUrl, apiUrlParam, apiHeaders, apiBody)
If res("StatusCode") = 200 Then
Debug.Print "レコードの取得に成功しました"
Set getRecordsData = res("Response")("Data")
End If
End Function
'******************************************************************************
'レコードDictionary内のネストした*HashClassHash、NumHash、DateHash、DescriptionHash、CheckHashなど
'展開し、1階層のDictionaryにフラット化する
Function FlattenRecord(record As Dictionary) As Dictionary
Dim flat As New Dictionary
Dim key As Variant
For Each key In record.Keys
If TypeName(record(key)) = "Dictionary" Then
Dim innerKey As Variant
For Each innerKey In record(key).Keys
flat.Add innerKey, record(key)(innerKey)
Next innerKey
Else
flat.Add key, record(key)
End If
Next key
Set FlattenRecord = flat
End Function
'******************************************************************************
'Get方式で取得したレコードをシートに書き込むexportCSVDataToSheetとは独立実装
'ヘッダはフラット化後の1件目レコードのキー一覧をそのまま使用する内部キー名のまま、日本語ラベル変換はしない
Function getRecordsDataToSheet(records As Collection, targetSheet As String)
Debug.Print "レコードをシートに出力開始: " & targetSheet
If records.count = 0 Then
MsgBox "データが存在しません: " & targetSheet, vbExclamation
Exit Function
End If
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Sheets(targetSheet)
On Error GoTo 0
If ws Is Nothing Then
Debug.Print "シートが存在しないため新規作成: " & targetSheet
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.count))
ws.Name = targetSheet
End If
Dim flatRecords As New Collection
Dim rec As Dictionary
For Each rec In records
flatRecords.Add FlattenRecord(rec)
Next rec
Dim headerDict As Dictionary
Set headerDict = flatRecords(1)
Dim headerKeys As Variant
headerKeys = headerDict.Keys
Dim maxCols As Long
maxCols = headerDict.count
Dim i As Long, j As Long
Dim dataArr() As Variant
ReDim dataArr(1 To flatRecords.count, 1 To maxCols)
For i = 1 To flatRecords.count
Dim curRec As Dictionary
Set curRec = flatRecords(i)
For j = 1 To maxCols
dataArr(i, j) = NormalizeGetValue(curRec(headerKeys(j - 1)))
Next j
Next i
Dim tbl As ListObject
On Error Resume Next
Set tbl = ws.ListObjects(targetSheet)
On Error GoTo 0
Dim headerRange As Range
Set headerRange = ws.Range("A1").Resize(1, maxCols)
For j = 0 To maxCols - 1
headerRange.Cells(1, j + 1).Value = headerKeys(j)
Next j
If tbl Is Nothing Then
Debug.Print "テーブルが存在しないため新規作成: " & targetSheet
Set tbl = ws.ListObjects.Add(xlSrcRange, headerRange, , xlYes)
tbl.Name = targetSheet
End If
If Not tbl.DataBodyRange Is Nothing Then
tbl.DataBodyRange.ClearContents
End If
tbl.Resize tbl.Range.Resize(flatRecords.count + 1, maxCols)
tbl.DataBodyRange.Value = dataArr
Debug.Print "レコードのシート出力完了: " & targetSheet
End Function
'******************************************************************************
'Get方式で取得した値のうち、ISO8601形式の日付時刻文字列を正規化する
'YYYY-MM-DDThh:mm:ss形式 → "yyyy/mm/dd"、未設定を示す1899-12-30は空欄にする
'オブジェクト値JSON配列等は空欄にする
Function NormalizeGetValue(ByVal v As Variant) As Variant
If IsObject(v) Then
NormalizeGetValue = ""
Exit Function
End If
Dim s As String
s = CStr(v)
If Len(s) >= 10 And Mid(s, 5, 1) = "-" And Mid(s, 8, 1) = "-" And Mid(s, 11, 1) = "T" Then
If Left(s, 10) = "1899-12-30" Then
NormalizeGetValue = ""
Else
NormalizeGetValue = Mid(s, 1, 4) & "/" & Mid(s, 6, 2) & "/" & Mid(s, 9, 2)
End If
Else
NormalizeGetValue = v
End If
End Function
'******************************************************************************
'REST API呼出処理
' method GetかPOSTか
' url REST APIのURL
' urlParam リクエストパラメータオプション
' headers ヘッダオプション
Function callRestApi(Byval method As String, Byval url As String, Optional Byval urlParam As String = "", Optional Byval headers As Dictionary = Null, Optional Byval body As Dictionary = Null) As Object
'HTTPリクエストのオブジェクトを定義
Dim objHTTP As Object
Set objHTTP = New XMLHTTP60
'HTTPリクエストの接続先を設定
objHTTP.Open method, url & urlParam, False
'リクエストヘッダーを設定複数ある場合はsetRequestHeaderを複数書けば良いのだ
Dim i As Long
For i = 0 To headers.count - 1
objHTTP.setRequestHeader headers.keys(i), headers.items(i)
Next i
'リクエスト送信
objHTTP.send JsonConverter.ConvertToJson(body)
Do While objHTTP.readyState < 4
DoEvents
Loop
'レスポンスの文字列objHTTP.responseTextをJsonに変換して返却
Set callRestApi = JsonConverter.ParseJson(objHTTP.responseText)
End Function
'******************************************************************************
'基本情報シートにFilter表(F2:G2)・Sorter表(H2:I2)のテーブルをセットアップする
'新版着工要因閲覧シートv0.1.xlsm上で手動実行する想定run()からは呼ばない)
'既にテーブルが存在する場合は何もしない
Sub SetupFilterSorterTables()
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("基本情報")
Dim filterTbl As ListObject
On Error Resume Next
Set filterTbl = ws.ListObjects("FilterTable")
On Error GoTo 0
If filterTbl Is Nothing Then
Dim filterHeader As Range
Set filterHeader = ws.Range("F2:G2")
filterHeader.Cells(1, 1).Value = "列名"
filterHeader.Cells(1, 2).Value = "値"
Set filterTbl = ws.ListObjects.Add(xlSrcRange, filterHeader, , xlYes)
filterTbl.Name = "FilterTable"
End If
Dim sorterTbl As ListObject
On Error Resume Next
Set sorterTbl = ws.ListObjects("SorterTable")
On Error GoTo 0
If sorterTbl Is Nothing Then
Dim sorterHeader As Range
Set sorterHeader = ws.Range("J2:K2")
sorterHeader.Cells(1, 1).Value = "列名"
sorterHeader.Cells(1, 2).Value = "昇降順"
Set sorterTbl = ws.ListObjects.Add(xlSrcRange, sorterHeader, , xlYes)
sorterTbl.Name = "SorterTable"
End If
End Sub