diff --git a/client.go b/client.go index 72de60c..4ff17ac 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" "io/ioutil" + "mime/multipart" "net/http" "net/url" "path" @@ -154,3 +155,92 @@ func (c *Client) call(ctx context.Context, return oauth2Token, json.NewDecoder(r).Decode(&res) } + +func (c *Client) upload(ctx context.Context, + apiPath string, + oauth2Token *oauth2.Token, + queryParams url.Values, + postBody map[string]string, + fileName string, + file []byte, + res interface{}, +) (*oauth2.Token, error) { + // url + u, err := url.Parse(c.config.APIEndpoint) + if err != nil { + return oauth2Token, err + } + u.Path = path.Join(u.Path, APIPath1, apiPath) + u.RawQuery = queryParams.Encode() + body := &bytes.Buffer{} + // form data + mw := multipart.NewWriter(body) + fw, err := mw.CreateFormFile("receipt", fileName) + if err != nil { + return oauth2Token, err + } + _, err = io.Copy(fw, bytes.NewReader(file)) + if err != nil { + return oauth2Token, err + } + for k, v := range postBody { + err = mw.WriteField(k, v) + if err != nil { + return oauth2Token, err + } + } + contentType := mw.FormDataContentType() + err = mw.Close() + if err != nil { + return oauth2Token, err + } + // request + req, err := http.NewRequest(http.MethodPost, u.String(), body) + if err != nil { + return oauth2Token, err + } + // header Content-Type + req.Header.Set("Content-Type", contentType) + req = req.WithContext(ctx) + tokenSource := c.config.Oauth2.TokenSource(ctx, oauth2Token) + httpClient := oauth2.NewClient(ctx, tokenSource) + response, err := httpClient.Do(req) + if err != nil { + return oauth2Token, err + } + defer response.Body.Close() + c.logf("[freee] %s: %s", HeaderXFreeeRequestID, response.Header.Get(HeaderXFreeeRequestID)) + c.logf("[freee] %s: %v %v%v", response.Status, req.Method, req.URL.Host, req.URL.Path) + + var r io.Reader = response.Body + // Parse freee API errors + code := response.StatusCode + if code >= http.StatusBadRequest { + byt, err := ioutil.ReadAll(r) + if err != nil { + // error occured, but ignored. + c.logf("[freee] HTTP response body: %v", err) + } + res := &Error{ + StatusCode: code, + RawError: string(byt), + } + // Check if re-authorization is required + if code == http.StatusUnauthorized { + var e UnauthorizedError + if err := json.NewDecoder(bytes.NewReader(byt)).Decode(&e); err != nil { + c.logf("[freee] HTTP response body: %v", err) + return oauth2Token, res + } + if e.Code == UnauthorizedCodeInvalidAccessToken || + e.Code == UnauthorizedCodeExpiredAccessToken { + res.IsAuthorizationRequired = true + } + } + return oauth2Token, res + } + if res == nil { + return oauth2Token, nil + } + return oauth2Token, json.NewDecoder(r).Decode(&res) +} diff --git a/deals.go b/deals.go index 3d55224..7c6d86d 100644 --- a/deals.go +++ b/deals.go @@ -158,7 +158,7 @@ type DealCreateParamsPayments struct { // will change when the set of required properties is changed func NewDealCreateParams(issueDate string, type_ string, companyID int32, dueDate string, partnerID int32, partnerCode string, refNumber string, - details *[]DealCreateParamsDetails) *DealCreateParams { + receiptIDs []int32, details *[]DealCreateParamsDetails) *DealCreateParams { this := DealCreateParams{} this.IssueDate = issueDate this.Type = type_ @@ -167,6 +167,7 @@ func NewDealCreateParams(issueDate string, type_ string, companyID int32, this.PartnerID = partnerID this.PartnerCode = partnerCode this.RefNumber = refNumber + this.ReceiptIDs = receiptIDs this.Details = details return &this } diff --git a/receipts.go b/receipts.go new file mode 100644 index 0000000..7eea7c4 --- /dev/null +++ b/receipts.go @@ -0,0 +1,85 @@ +package freee + +import ( + "context" + "golang.org/x/oauth2" + "strconv" +) + +const ( + APIPathReceipts = "receipts" +) + +type CreateReceiptParams struct { + // 事業所ID + CompanyID int32 `json:"company_id"` + // メモ (255文字以内) + Description string `json:"description,omitempty"` + // 取引日 (yyyy-mm-dd) + IssueDate string `json:"issue_date"` + // 証憑ファイル + Receipt []byte `json:"receipt"` +} + +// NewReceiptUpdateParams instantiates a new ReceiptUpdateParams object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCreateReceiptParams(companyID int32, description string, issueDate string, receipt []byte) *CreateReceiptParams { + this := CreateReceiptParams{} + this.CompanyID = companyID + this.IssueDate = issueDate + this.Description = description + this.Receipt = receipt + return &this +} + +type ReceiptResponse struct { + Receipt Receipt `json:"receipt"` +} + +type Receipt struct { + // 証憑ID + ID int32 `json:"id"` + // ステータス(unconfirmed:確認待ち、confirmed:確認済み、deleted:削除済み、ignored:無視) + Status string `json:"status"` + // メモ + Description string `json:"description,omitempty"` + // MIMEタイプ + MimeType string `json:"mime_type"` + // 発生日 + IssueDate string `json:"issue_date,omitempty"` + // アップロード元種別 + Origin string `json:"origin"` + // 作成日時(ISO8601形式) + CreatedAt string `json:"created_at"` + // ファイルのダウンロードURL(freeeにログインした状態でのみ閲覧可能です。)

file_srcは廃止予定の属性になります。
file_srcに替わり、証憑ファイルのダウンロード APIをご利用ください。
証憑ファイルのダウンロードAPIを利用することで、以下のようになります。 + FileSrc string `json:"file_src"` + User UserCreatedReceipt `json:"user"` +} + +type UserCreatedReceipt struct { + // ユーザーID + ID int32 `json:"id"` + // メールアドレス + Email string `json:"email"` + // 表示名 + DisplayName string `json:"display_name,omitempty"` +} + +func (c *Client) CreateReceipt( + ctx context.Context, oauth2Token *oauth2.Token, + params CreateReceiptParams, + receiptName string, +) (*ReceiptResponse, *oauth2.Token, error) { + request := map[string]string{} + request["company_id"] = strconv.Itoa(int(params.CompanyID)) + request["description"] = params.Description + request["issue_date"] = params.IssueDate + var result ReceiptResponse + oauth2Token, err := c.upload(ctx, APIPathReceipts, oauth2Token, nil, request, receiptName, params.Receipt, &result) + if err != nil { + return nil, oauth2Token, err + } + return &result, oauth2Token, nil +}