Skip to content

Commit 7202163

Browse files
buty4649claude
andcommitted
system: master data を全件取得へ変更
--rows / --offset フラグを撤廃し、rows=1000 で offset を自動インクリメン トしてマスタデータを全件取得するように変更。 - JSON: master.total_count を終端判定に使用し、マージ済みレスポンスを出力 - CSV: 2 ページ目以降は title=false で取得してヘッダを含めず連結、空レス ポンスで終了 - 既定出力を stdout に統一 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a3fb1d4 commit 7202163

1 file changed

Lines changed: 138 additions & 41 deletions

File tree

cmd/system_master.go

Lines changed: 138 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cmd
22

33
import (
4+
"bytes"
5+
"context"
46
"encoding/json"
57
"fmt"
68
"io"
@@ -13,15 +15,15 @@ import (
1315
"github.com/spf13/cobra"
1416
)
1517

18+
const masterDataPageSize = 1000
19+
1620
var (
1721
systemMasterListOutput string
1822
systemMasterListJQ string
1923
systemMasterShowOutput string
2024
systemMasterShowJQ string
2125

2226
systemMasterDataType int
23-
systemMasterDataRows int
24-
systemMasterDataOffset int
2527
systemMasterDataFormat string
2628
systemMasterDataFileName string
2729
systemMasterDataDelimiter string
@@ -118,15 +120,13 @@ func init() {
118120

119121
df := systemMasterDataCmd.Flags()
120122
df.IntVar(&systemMasterDataType, "type", -1, "master_type: 0=simple master, 1=user-specific master (required)")
121-
df.IntVar(&systemMasterDataRows, "rows", 0, "number of rows to fetch (0 = omit; server default 100; max 1000)")
122-
df.IntVar(&systemMasterDataOffset, "offset", 0, "offset (0 = omit; server default 0)")
123123
df.StringVar(&systemMasterDataFormat, "format", "json", "output format: json | csv")
124124
df.StringVar(&systemMasterDataFileName, "file-name", "", "CSV file name hint (CSV only; default: {master_code}.csv)")
125125
df.StringVar(&systemMasterDataDelimiter, "delimiter", "", "CSV delimiter: comma | tab (CSV only; default comma)")
126126
df.BoolVar(&systemMasterDataTitle, "title", false, "CSV only (user-specific master): include field names on the first row (default: true)")
127127
df.BoolVar(&systemMasterDataNoTitle, "no-title", false, "CSV only (user-specific master): omit field names from the first row")
128128
df.StringVar(&systemMasterDataFields, "fields", "", "CSV only (simple master): comma-separated list of field names to include")
129-
df.StringVarP(&systemMasterDataOutput, "output", "o", "", "output path: FILE, DIR/, - for stdout (default: stdout for JSON, server-provided filename for CSV)")
129+
df.StringVarP(&systemMasterDataOutput, "output", "o", "", "output path: FILE, DIR/, - for stdout (default: stdout)")
130130
df.StringVar(&systemMasterDataJQ, "jq", "", "apply a gojq filter to the JSON response (JSON format only)")
131131
_ = systemMasterDataCmd.MarkFlagRequired("type")
132132

@@ -218,60 +218,51 @@ func runSystemMasterData(cmd *cobra.Command, args []string) error {
218218
return fmt.Errorf("--title and --no-title are mutually exclusive")
219219
}
220220

221-
p := xpoint.MasterDataParams{MasterType: systemMasterDataType}
222-
if cmd.Flags().Changed("rows") {
223-
v := systemMasterDataRows
224-
p.Rows = &v
225-
}
226-
if cmd.Flags().Changed("offset") {
227-
v := systemMasterDataOffset
228-
p.Offset = &v
229-
}
230-
if format == "csv" {
231-
p.FileName = systemMasterDataFileName
232-
p.Delimiter = systemMasterDataDelimiter
233-
p.Fields = systemMasterDataFields
234-
if systemMasterDataNoTitle {
235-
b := false
236-
p.Title = &b
237-
} else if cmd.Flags().Changed("title") {
238-
v := systemMasterDataTitle
239-
p.Title = &v
240-
}
241-
}
242-
243221
client, err := newClientFromFlags(cmd.Context())
244222
if err != nil {
245223
return err
246224
}
247-
filename, body, _, err := client.GetMasterData(cmd.Context(), masterCode, format, p)
248-
if err != nil {
249-
return err
250-
}
251225

252226
if format == "json" {
227+
merged, err := fetchAllMasterDataJSON(cmd.Context(), client, masterCode, systemMasterDataType)
228+
if err != nil {
229+
return err
230+
}
253231
if systemMasterDataJQ != "" {
254-
return runJQ(json.RawMessage(body), systemMasterDataJQ)
232+
return runJQ(json.RawMessage(merged), systemMasterDataJQ)
255233
}
256234
switch systemMasterDataOutput {
257235
case "", "-":
258-
_, werr := os.Stdout.Write(body)
236+
_, werr := os.Stdout.Write(merged)
259237
return werr
260238
}
261-
dst := resolveDownloadPath(systemMasterDataOutput, fallbackName(filename, masterCode+".json"), 0)
262-
if err := os.WriteFile(dst, body, 0o600); err != nil {
239+
dst := resolveDownloadPath(systemMasterDataOutput, masterCode+".json", 0)
240+
if err := os.WriteFile(dst, merged, 0o600); err != nil {
263241
return fmt.Errorf("write master data: %w", err)
264242
}
265-
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(body))
243+
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(merged))
266244
return nil
267245
}
268246

269-
// CSV
270-
if systemMasterDataOutput == "-" {
271-
_, werr := os.Stdout.Write(body)
272-
return werr
247+
p := xpoint.MasterDataParams{
248+
MasterType: systemMasterDataType,
249+
FileName: systemMasterDataFileName,
250+
Delimiter: systemMasterDataDelimiter,
251+
Fields: systemMasterDataFields,
273252
}
274-
if systemMasterDataOutput == "" && !isTerminal(os.Stdout) {
253+
if systemMasterDataNoTitle {
254+
b := false
255+
p.Title = &b
256+
} else if cmd.Flags().Changed("title") {
257+
v := systemMasterDataTitle
258+
p.Title = &v
259+
}
260+
filename, body, err := fetchAllMasterDataCSV(cmd.Context(), client, masterCode, p)
261+
if err != nil {
262+
return err
263+
}
264+
265+
if systemMasterDataOutput == "" || systemMasterDataOutput == "-" {
275266
_, werr := os.Stdout.Write(body)
276267
return werr
277268
}
@@ -344,6 +335,112 @@ func runSystemMasterUpload(cmd *cobra.Command, args []string) error {
344335
return writeJSON(os.Stdout, res)
345336
}
346337

338+
// fetchAllMasterDataJSON fetches master data as JSON with rows=1000, paging
339+
// until total_count (or a short/empty page) is reached. The merged JSON keeps
340+
// the first page's envelope with the concatenated data array.
341+
func fetchAllMasterDataJSON(ctx context.Context, client *xpoint.Client, masterCode string, masterType int) ([]byte, error) {
342+
rows := masterDataPageSize
343+
offset := 0
344+
p := xpoint.MasterDataParams{MasterType: masterType, Rows: &rows, Offset: &offset}
345+
346+
var (
347+
firstMaster map[string]json.RawMessage
348+
allData []json.RawMessage
349+
totalCount int
350+
haveTotal bool
351+
)
352+
353+
for {
354+
p.Offset = &offset
355+
_, body, _, err := client.GetMasterData(ctx, masterCode, "json", p)
356+
if err != nil {
357+
return nil, err
358+
}
359+
var env struct {
360+
Master map[string]json.RawMessage `json:"master"`
361+
}
362+
if err := json.Unmarshal(body, &env); err != nil {
363+
return nil, fmt.Errorf("parse master data: %w", err)
364+
}
365+
366+
if offset == 0 {
367+
firstMaster = env.Master
368+
if raw, ok := env.Master["total_count"]; ok {
369+
if err := json.Unmarshal(raw, &totalCount); err == nil {
370+
haveTotal = true
371+
}
372+
}
373+
}
374+
375+
var data []json.RawMessage
376+
if raw, ok := env.Master["data"]; ok {
377+
if err := json.Unmarshal(raw, &data); err != nil {
378+
return nil, fmt.Errorf("parse master.data: %w", err)
379+
}
380+
}
381+
allData = append(allData, data...)
382+
383+
if haveTotal && len(allData) >= totalCount {
384+
break
385+
}
386+
if len(data) < masterDataPageSize {
387+
break
388+
}
389+
offset += masterDataPageSize
390+
}
391+
392+
if firstMaster == nil {
393+
firstMaster = map[string]json.RawMessage{}
394+
}
395+
encoded, err := json.Marshal(allData)
396+
if err != nil {
397+
return nil, err
398+
}
399+
firstMaster["data"] = encoded
400+
return json.Marshal(struct {
401+
Master map[string]json.RawMessage `json:"master"`
402+
}{Master: firstMaster})
403+
}
404+
405+
// fetchAllMasterDataCSV fetches master data as CSV with rows=1000, paging
406+
// until the server returns an empty page. The first page honors the caller's
407+
// title setting; subsequent pages force title=false so the server omits the
408+
// header and the bodies can simply be concatenated.
409+
func fetchAllMasterDataCSV(ctx context.Context, client *xpoint.Client, masterCode string, base xpoint.MasterDataParams) (string, []byte, error) {
410+
rows := masterDataPageSize
411+
offset := 0
412+
p := base
413+
p.Rows = &rows
414+
p.Offset = &offset
415+
416+
titleFalse := false
417+
var (
418+
firstFilename string
419+
csvBuf bytes.Buffer
420+
)
421+
for {
422+
p.Offset = &offset
423+
if offset > 0 {
424+
p.Title = &titleFalse
425+
}
426+
filename, body, _, err := client.GetMasterData(ctx, masterCode, "csv", p)
427+
if err != nil {
428+
return "", nil, err
429+
}
430+
if offset == 0 {
431+
firstFilename = filename
432+
csvBuf.Write(body)
433+
} else {
434+
if len(bytes.TrimSpace(body)) == 0 {
435+
break
436+
}
437+
csvBuf.Write(body)
438+
}
439+
offset += masterDataPageSize
440+
}
441+
return firstFilename, csvBuf.Bytes(), nil
442+
}
443+
347444
// fallbackName returns name when non-empty, else alt.
348445
func fallbackName(name, alt string) string {
349446
if name != "" {

0 commit comments

Comments
 (0)