Skip to content

Commit 5f86260

Browse files
authored
document: 書類ビュー (docview/openview/statusview) 取得サブコマンドを追加 (#27)
- `xp document docview` (GET /api/v1/documents/docview) 新規書類作成用HTMLビュー取得 - `xp document docview-upload` (POST /multiapi/v1/documents/docview) 事前入力値・添付ファイル付きでHTMLビューを取得 (multipart) - `xp document openview` (GET /api/v1/documents/{docid}/openview) 書類表示HTML取得 - `xp document statusview` (GET /api/v1/documents/{docid}/statusview) 承認進捗状況HTML取得 - HTMLダウンロード共通ヘルパー downloadBytes を追加し、既存 DownloadPDF も整理 - 対応する schema JSON (document.docview / document.docview.upload / document.openview / document.statusview) を同梱
1 parent 4c80cae commit 5f86260

File tree

7 files changed

+648
-7
lines changed

7 files changed

+648
-7
lines changed

cmd/document.go

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ var (
5555

5656
docOpenNoBrowser bool
5757

58+
docDocviewFormCode string
59+
docDocviewFormName string
60+
docDocviewRouteCode string
61+
docDocviewFromDocID int
62+
docDocviewProxyUser string
63+
docDocviewOutput string
64+
65+
docDocviewUploadFormCode string
66+
docDocviewUploadFormName string
67+
docDocviewUploadRouteCode string
68+
docDocviewUploadFromDocID int
69+
docDocviewUploadProxyUser string
70+
docDocviewUploadDatas string
71+
docDocviewUploadFile string
72+
docDocviewUploadFileName string
73+
docDocviewUploadRemarks string
74+
docDocviewUploadDetailNo int
75+
docDocviewUploadEvidenceType int
76+
docDocviewUploadOutput string
77+
78+
docOpenviewProxyUser string
79+
docOpenviewOutput string
80+
81+
docStatusviewOutput string
82+
5883
docCommentAddContent string
5984
docCommentAddAttention bool
6085
docCommentAddOutput string
@@ -226,6 +251,59 @@ By default the command prompts for confirmation. Pass --yes to skip it.`,
226251
RunE: runDocumentCommentDelete,
227252
}
228253

254+
var documentDocviewCmd = &cobra.Command{
255+
Use: "docview",
256+
Short: "Fetch the HTML view for creating a new document or a related document",
257+
Long: `Fetch the HTML form used to create a new document or a related document via
258+
GET /api/v1/documents/docview.
259+
260+
Specify the target form with either --form-code or --form-name (form name takes
261+
priority on the server side if both are given). --route-code is required; pass
262+
an empty string ("") for standard forms or "--condroute" for workflow forms
263+
with auto-select routes.
264+
265+
By default the HTML is saved to a file named like "docview-<fcd|formname>.html"
266+
in the current directory. Use --output to override:
267+
--output FILE save to FILE
268+
--output DIR/ save into DIR/ using the default filename
269+
--output - write the HTML to stdout`,
270+
Args: cobra.NoArgs,
271+
RunE: runDocumentDocview,
272+
}
273+
274+
var documentDocviewUploadCmd = &cobra.Command{
275+
Use: "docview-upload",
276+
Short: "Fetch the HTML form for a new document with pre-filled data or attachment (multipart)",
277+
Long: `Fetch the HTML form for creating a new document or a related document with
278+
pre-filled field values and/or a pre-attached file via POST
279+
/multiapi/v1/documents/docview.
280+
281+
Specify the target form with either --form-code or --form-name. --route-code
282+
is required. Pass --datas to supply pre-fill JSON, and --file to attach a
283+
single file (--file-name defaults to basename of --file).`,
284+
Args: cobra.NoArgs,
285+
RunE: runDocumentDocviewUpload,
286+
}
287+
288+
var documentOpenviewCmd = &cobra.Command{
289+
Use: "openview <docid>",
290+
Short: "Fetch the HTML view of a document",
291+
Long: `Fetch the HTML view of a document via GET /api/v1/documents/{docid}/openview.
292+
293+
The HTML is the same rendering as the X-point viewer but cannot be closed by
294+
the in-page close button (use the browser tab/window close instead).`,
295+
Args: cobra.ExactArgs(1),
296+
RunE: runDocumentOpenview,
297+
}
298+
299+
var documentStatusviewCmd = &cobra.Command{
300+
Use: "statusview <docid>",
301+
Short: "Fetch the HTML of the approval status view",
302+
Long: "Fetch the HTML of the approval status view via GET /api/v1/documents/{docid}/statusview.",
303+
Args: cobra.ExactArgs(1),
304+
RunE: runDocumentStatusview,
305+
}
306+
229307
var documentDownloadCmd = &cobra.Command{
230308
Use: "download <docid>",
231309
Short: "Download a document as PDF",
@@ -249,6 +327,10 @@ func init() {
249327
documentCmd.AddCommand(documentDeleteCmd)
250328
documentCmd.AddCommand(documentStatusCmd)
251329
documentCmd.AddCommand(documentDownloadCmd)
330+
documentCmd.AddCommand(documentDocviewCmd)
331+
documentCmd.AddCommand(documentDocviewUploadCmd)
332+
documentCmd.AddCommand(documentOpenviewCmd)
333+
documentCmd.AddCommand(documentStatusviewCmd)
252334
documentCmd.AddCommand(documentOpenCmd)
253335
documentCmd.AddCommand(documentCommentCmd)
254336
documentCommentCmd.AddCommand(documentCommentAddCmd)
@@ -302,6 +384,35 @@ func init() {
302384
of := documentOpenCmd.Flags()
303385
of.BoolVarP(&docOpenNoBrowser, "no-browser", "n", false, "print the URL without launching the browser")
304386

387+
dvf := documentDocviewCmd.Flags()
388+
dvf.StringVar(&docDocviewFormCode, "form-code", "", "form code (fcd); use --form-name instead or in addition (form-name wins)")
389+
dvf.StringVar(&docDocviewFormName, "form-name", "", "form name")
390+
dvf.StringVar(&docDocviewRouteCode, "route-code", "", "approval route code (required; \"\" for standard forms, \"--condroute\" for auto-select workflow routes)")
391+
dvf.IntVar(&docDocviewFromDocID, "from-docid", 0, "source document ID for a related document (0 = omit)")
392+
dvf.StringVar(&docDocviewProxyUser, "proxy-user", "", "proxy applicant user code (代理申請)")
393+
dvf.StringVarP(&docDocviewOutput, "output", "o", "", "output path: FILE, DIR/, or - for stdout (default: docview-<form>.html in current dir)")
394+
395+
duf := documentDocviewUploadCmd.Flags()
396+
duf.StringVar(&docDocviewUploadFormCode, "form-code", "", "form code (fcd)")
397+
duf.StringVar(&docDocviewUploadFormName, "form-name", "", "form name (wins over --form-code when both are given)")
398+
duf.StringVar(&docDocviewUploadRouteCode, "route-code", "", "approval route code (required)")
399+
duf.IntVar(&docDocviewUploadFromDocID, "from-docid", 0, "source document ID for a related document (0 = omit)")
400+
duf.StringVar(&docDocviewUploadProxyUser, "proxy-user", "", "proxy applicant user code")
401+
duf.StringVar(&docDocviewUploadDatas, "datas", "", "pre-fill data JSON: inline, file path, or - for stdin")
402+
duf.StringVar(&docDocviewUploadFile, "file", "", "path to a file to pre-attach (at most one file)")
403+
duf.StringVar(&docDocviewUploadFileName, "file-name", "", "override the attachment filename (default: basename of --file)")
404+
duf.StringVar(&docDocviewUploadRemarks, "remarks", "", "attachment remarks (備考)")
405+
duf.IntVar(&docDocviewUploadDetailNo, "detail-no", 0, "detail row number for an attachment-type form (0 = omit)")
406+
duf.IntVar(&docDocviewUploadEvidenceType, "evidence-type", -1, "電帳法書類区分 0:その他 / 1:電子取引 (-1 = omit; default 1 on server)")
407+
duf.StringVarP(&docDocviewUploadOutput, "output", "o", "", "output path: FILE, DIR/, or - for stdout (default: docview-<form>.html in current dir)")
408+
409+
ovf := documentOpenviewCmd.Flags()
410+
ovf.StringVar(&docOpenviewProxyUser, "proxy-user", "", "proxy user code (代理モードで書類を表示)")
411+
ovf.StringVarP(&docOpenviewOutput, "output", "o", "", "output path: FILE, DIR/, or - for stdout (default: openview-<docid>.html in current dir)")
412+
413+
svf := documentStatusviewCmd.Flags()
414+
svf.StringVarP(&docStatusviewOutput, "output", "o", "", "output path: FILE, DIR/, or - for stdout (default: statusview-<docid>.html in current dir)")
415+
305416
caf := documentCommentAddCmd.Flags()
306417
caf.StringVar(&docCommentAddContent, "content", "", "comment content (required)")
307418
caf.BoolVar(&docCommentAddAttention, "attention", false, "mark as important comment (attentionflg=1)")
@@ -560,6 +671,208 @@ func runDocumentDownload(cmd *cobra.Command, args []string) error {
560671
return nil
561672
}
562673

674+
func runDocumentDocview(cmd *cobra.Command, args []string) error {
675+
if docDocviewFormCode == "" && docDocviewFormName == "" {
676+
return fmt.Errorf("either --form-code or --form-name is required")
677+
}
678+
if !cmd.Flags().Changed("route-code") {
679+
return fmt.Errorf("--route-code is required (use an empty string \"\" for standard forms)")
680+
}
681+
client, err := newClientFromFlags(cmd.Context())
682+
if err != nil {
683+
return err
684+
}
685+
params := xpoint.DocviewParams{
686+
FormCode: docDocviewFormCode,
687+
FormName: docDocviewFormName,
688+
RouteCode: docDocviewRouteCode,
689+
ProxyUser: docDocviewProxyUser,
690+
}
691+
if docDocviewFromDocID != 0 {
692+
v := docDocviewFromDocID
693+
params.FromDocID = &v
694+
}
695+
696+
data, err := client.GetDocview(cmd.Context(), params)
697+
if err != nil {
698+
return err
699+
}
700+
defaultName := defaultDocviewFilename("docview", params.FormCode, params.FormName, 0)
701+
return writeHTMLOutput(docDocviewOutput, defaultName, data)
702+
}
703+
704+
func runDocumentDocviewUpload(cmd *cobra.Command, args []string) error {
705+
if docDocviewUploadFormCode == "" && docDocviewUploadFormName == "" {
706+
return fmt.Errorf("either --form-code or --form-name is required")
707+
}
708+
if !cmd.Flags().Changed("route-code") {
709+
return fmt.Errorf("--route-code is required (use an empty string \"\" for standard forms)")
710+
}
711+
client, err := newClientFromFlags(cmd.Context())
712+
if err != nil {
713+
return err
714+
}
715+
716+
params := xpoint.DocviewMultipartParams{
717+
FormCode: docDocviewUploadFormCode,
718+
FormName: docDocviewUploadFormName,
719+
RouteCode: docDocviewUploadRouteCode,
720+
ProxyUser: docDocviewUploadProxyUser,
721+
}
722+
if docDocviewUploadFromDocID != 0 {
723+
v := docDocviewUploadFromDocID
724+
params.FromDocID = &v
725+
}
726+
if docDocviewUploadDatas != "" {
727+
datas, err := loadStringInput(docDocviewUploadDatas)
728+
if err != nil {
729+
return fmt.Errorf("load --datas: %w", err)
730+
}
731+
params.Datas = datas
732+
}
733+
if docDocviewUploadFile != "" {
734+
content, err := os.ReadFile(docDocviewUploadFile)
735+
if err != nil {
736+
return fmt.Errorf("read --file: %w", err)
737+
}
738+
name := docDocviewUploadFileName
739+
if name == "" {
740+
name = filepath.Base(docDocviewUploadFile)
741+
}
742+
f := &xpoint.DocviewMultipartFile{
743+
Name: name,
744+
Remarks: docDocviewUploadRemarks,
745+
Content: content,
746+
}
747+
if cmd.Flags().Changed("detail-no") {
748+
v := docDocviewUploadDetailNo
749+
f.DetailNo = &v
750+
}
751+
if docDocviewUploadEvidenceType >= 0 {
752+
v := docDocviewUploadEvidenceType
753+
f.EvidenceType = &v
754+
}
755+
params.File = f
756+
}
757+
758+
data, err := client.PostDocviewMultipart(cmd.Context(), params)
759+
if err != nil {
760+
return err
761+
}
762+
defaultName := defaultDocviewFilename("docview", params.FormCode, params.FormName, 0)
763+
return writeHTMLOutput(docDocviewUploadOutput, defaultName, data)
764+
}
765+
766+
func runDocumentOpenview(cmd *cobra.Command, args []string) error {
767+
docID, err := parseDocID(args[0])
768+
if err != nil {
769+
return err
770+
}
771+
client, err := newClientFromFlags(cmd.Context())
772+
if err != nil {
773+
return err
774+
}
775+
data, err := client.GetDocumentOpenview(cmd.Context(), docID, docOpenviewProxyUser)
776+
if err != nil {
777+
return err
778+
}
779+
defaultName := defaultDocviewFilename("openview", "", "", docID)
780+
return writeHTMLOutput(docOpenviewOutput, defaultName, data)
781+
}
782+
783+
func runDocumentStatusview(cmd *cobra.Command, args []string) error {
784+
docID, err := parseDocID(args[0])
785+
if err != nil {
786+
return err
787+
}
788+
client, err := newClientFromFlags(cmd.Context())
789+
if err != nil {
790+
return err
791+
}
792+
data, err := client.GetDocumentStatusview(cmd.Context(), docID)
793+
if err != nil {
794+
return err
795+
}
796+
defaultName := defaultDocviewFilename("statusview", "", "", docID)
797+
return writeHTMLOutput(docStatusviewOutput, defaultName, data)
798+
}
799+
800+
// defaultDocviewFilename builds a reasonable default output filename for
801+
// docview/openview/statusview HTML responses.
802+
func defaultDocviewFilename(prefix, formCode, formName string, docID int) string {
803+
switch {
804+
case docID > 0:
805+
return fmt.Sprintf("%s-%d.html", prefix, docID)
806+
case formCode != "":
807+
return fmt.Sprintf("%s-%s.html", prefix, sanitizeFilename(formCode))
808+
case formName != "":
809+
return fmt.Sprintf("%s-%s.html", prefix, sanitizeFilename(formName))
810+
default:
811+
return prefix + ".html"
812+
}
813+
}
814+
815+
func sanitizeFilename(s string) string {
816+
var b strings.Builder
817+
for _, r := range s {
818+
switch r {
819+
case '/', '\\', ':', '*', '?', '"', '<', '>', '|', '\x00':
820+
b.WriteRune('_')
821+
default:
822+
b.WriteRune(r)
823+
}
824+
}
825+
return b.String()
826+
}
827+
828+
// writeHTMLOutput writes HTML bytes to stdout (out == "-"), to a path given
829+
// by out (FILE or DIR/), or to defaultName in the current directory when out
830+
// is empty.
831+
func writeHTMLOutput(out, defaultName string, data []byte) error {
832+
if out == "-" {
833+
_, err := os.Stdout.Write(data)
834+
return err
835+
}
836+
var dst string
837+
switch {
838+
case out == "":
839+
dst = defaultName
840+
case strings.HasSuffix(out, string(os.PathSeparator)) || strings.HasSuffix(out, "/"):
841+
dst = filepath.Join(out, defaultName)
842+
default:
843+
if info, err := os.Stat(out); err == nil && info.IsDir() {
844+
dst = filepath.Join(out, defaultName)
845+
} else {
846+
dst = out
847+
}
848+
}
849+
if err := os.WriteFile(dst, data, 0o600); err != nil {
850+
return fmt.Errorf("write html: %w", err)
851+
}
852+
fmt.Fprintf(os.Stderr, "saved: %s (%d bytes)\n", dst, len(data))
853+
return nil
854+
}
855+
856+
// loadStringInput accepts an inline string, a file path, or "-" for stdin
857+
// and returns the resulting string content.
858+
func loadStringInput(s string) (string, error) {
859+
if s == "-" {
860+
b, err := io.ReadAll(os.Stdin)
861+
if err != nil {
862+
return "", fmt.Errorf("read stdin: %w", err)
863+
}
864+
return string(b), nil
865+
}
866+
if info, err := os.Stat(s); err == nil && !info.IsDir() {
867+
b, err := os.ReadFile(s)
868+
if err != nil {
869+
return "", fmt.Errorf("read file: %w", err)
870+
}
871+
return string(b), nil
872+
}
873+
return s, nil
874+
}
875+
563876
func runDocumentOpen(_ *cobra.Command, args []string) error {
564877
docID, err := parseDocID(args[0])
565878
if err != nil {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"method": "GET",
3+
"path": "/api/v1/documents/docview",
4+
"summary": "新規書類・関連書類作成 (HTMLビュー取得)",
5+
"description": "書類の新規作成、または関連書類作成のための書類ビューHTMLを取得する。取得したHTMLはそのままブラウザで表示可能。",
6+
"parameters": [
7+
{
8+
"name": "fcd",
9+
"in": "query",
10+
"type": "string",
11+
"description": "フォームコード (fcd と formname のいずれかを指定。両方指定した場合は formname が優先される)"
12+
},
13+
{
14+
"name": "formname",
15+
"in": "query",
16+
"type": "string",
17+
"description": "フォーム名称"
18+
},
19+
{
20+
"name": "routecd",
21+
"in": "query",
22+
"type": "string",
23+
"required": true,
24+
"description": "承認ルートコード。ユーザ選択ルートはルートコードを指定、自動選択ルートは空文字または \"--condroute\"、標準フォームは空文字を指定"
25+
},
26+
{
27+
"name": "fromdocid",
28+
"in": "query",
29+
"type": "integer",
30+
"description": "関連元書類の書類ID (関連書類作成時に指定)"
31+
},
32+
{
33+
"name": "proxy_user",
34+
"in": "query",
35+
"type": "string",
36+
"description": "代理申請者のユーザコード"
37+
}
38+
],
39+
"response": {
40+
"contentType": "text/html",
41+
"description": "書類ビューHTML"
42+
}
43+
}

0 commit comments

Comments
 (0)