Skip to content

Commit 4dec649

Browse files
buty4649claude
andauthored
system: master data を全件取得へ変更 (#41)
* 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> * system: master data の --type を simple / user の文字列に変更 数値指定 (0/1) は意味がすぐに分からないため、simple (簡易マスタ) / user (ユーザ固有マスタ) の文字列値を受け取るように変更。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a3fb1d4 commit 4dec649

1 file changed

Lines changed: 150 additions & 47 deletions

File tree

cmd/system_master.go

Lines changed: 150 additions & 47 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

22-
systemMasterDataType int
23-
systemMasterDataRows int
24-
systemMasterDataOffset int
26+
systemMasterDataType string
2527
systemMasterDataFormat string
2628
systemMasterDataFileName string
2729
systemMasterDataDelimiter string
@@ -67,8 +69,8 @@ var systemMasterDataCmd = &cobra.Command{
6769
Long: `Export master rows via GET /api/v1/system/master/{master_code}/data.
6870
6971
--type (required) selects the master kind:
70-
0 simple master
71-
1 user-specific master (pass the table_name as <master_code>)
72+
simple simple master
73+
user user-specific master (pass the table_name as <master_code>)
7274
7375
--format defaults to json. Use --format csv for CSV output; the CSV
7476
payload is written to stdout (or --output FILE / DIR/).`,
@@ -117,16 +119,14 @@ func init() {
117119
sf.StringVar(&systemMasterShowJQ, "jq", "", "apply a gojq filter to the JSON response (forces JSON output)")
118120

119121
df := systemMasterDataCmd.Flags()
120-
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)")
122+
df.StringVar(&systemMasterDataType, "type", "", "master type: simple | user (required)")
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

@@ -203,8 +203,14 @@ func runSystemMasterData(cmd *cobra.Command, args []string) error {
203203
if masterCode == "" {
204204
return fmt.Errorf("master_code is required")
205205
}
206-
if systemMasterDataType != 0 && systemMasterDataType != 1 {
207-
return fmt.Errorf("--type must be 0 (simple) or 1 (user-specific), got %d", systemMasterDataType)
206+
var masterType int
207+
switch strings.ToLower(strings.TrimSpace(systemMasterDataType)) {
208+
case "simple":
209+
masterType = 0
210+
case "user":
211+
masterType = 1
212+
default:
213+
return fmt.Errorf("--type must be simple or user, got %q", systemMasterDataType)
208214
}
209215
format := strings.ToLower(strings.TrimSpace(systemMasterDataFormat))
210216
switch format {
@@ -218,60 +224,51 @@ func runSystemMasterData(cmd *cobra.Command, args []string) error {
218224
return fmt.Errorf("--title and --no-title are mutually exclusive")
219225
}
220226

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-
243227
client, err := newClientFromFlags(cmd.Context())
244228
if err != nil {
245229
return err
246230
}
247-
filename, body, _, err := client.GetMasterData(cmd.Context(), masterCode, format, p)
248-
if err != nil {
249-
return err
250-
}
251231

252232
if format == "json" {
233+
merged, err := fetchAllMasterDataJSON(cmd.Context(), client, masterCode, masterType)
234+
if err != nil {
235+
return err
236+
}
253237
if systemMasterDataJQ != "" {
254-
return runJQ(json.RawMessage(body), systemMasterDataJQ)
238+
return runJQ(json.RawMessage(merged), systemMasterDataJQ)
255239
}
256240
switch systemMasterDataOutput {
257241
case "", "-":
258-
_, werr := os.Stdout.Write(body)
242+
_, werr := os.Stdout.Write(merged)
259243
return werr
260244
}
261-
dst := resolveDownloadPath(systemMasterDataOutput, fallbackName(filename, masterCode+".json"), 0)
262-
if err := os.WriteFile(dst, body, 0o600); err != nil {
245+
dst := resolveDownloadPath(systemMasterDataOutput, masterCode+".json", 0)
246+
if err := os.WriteFile(dst, merged, 0o600); err != nil {
263247
return fmt.Errorf("write master data: %w", err)
264248
}
265-
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(body))
249+
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(merged))
266250
return nil
267251
}
268252

269-
// CSV
270-
if systemMasterDataOutput == "-" {
271-
_, werr := os.Stdout.Write(body)
272-
return werr
253+
p := xpoint.MasterDataParams{
254+
MasterType: masterType,
255+
FileName: systemMasterDataFileName,
256+
Delimiter: systemMasterDataDelimiter,
257+
Fields: systemMasterDataFields,
273258
}
274-
if systemMasterDataOutput == "" && !isTerminal(os.Stdout) {
259+
if systemMasterDataNoTitle {
260+
b := false
261+
p.Title = &b
262+
} else if cmd.Flags().Changed("title") {
263+
v := systemMasterDataTitle
264+
p.Title = &v
265+
}
266+
filename, body, err := fetchAllMasterDataCSV(cmd.Context(), client, masterCode, p)
267+
if err != nil {
268+
return err
269+
}
270+
271+
if systemMasterDataOutput == "" || systemMasterDataOutput == "-" {
275272
_, werr := os.Stdout.Write(body)
276273
return werr
277274
}
@@ -344,6 +341,112 @@ func runSystemMasterUpload(cmd *cobra.Command, args []string) error {
344341
return writeJSON(os.Stdout, res)
345342
}
346343

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

0 commit comments

Comments
 (0)