PostmanでRedmine APIを実行してみる

こんにちは!株式会社スマレジ、エンジニアのmasaです。

3連休の真ん中ですが、皆様いかがお過ごしでしょうか?
masaは久々に実家に帰って親に顔を見せたりしております。

さて、今日からはまたしばらく開発記事になる予定です。

Redmineのメンテが大変…

思ったことないですか?masaは最近思うことがしばしばございます。

といいますのも、現在スマレジではスマレジ4.0に使う新APIを絶賛開発中でございます。

画面やら帳票やらがない分、基盤さえできてしまえば、実装自体はアプリの機能拡張よりカンタンなAPI開発ですが、いかんせんスマレジは機能が多いので、APIの数も増えます。しかし、規模が増えようともその工数やらスケジュールやらは自己管理&メンバーへの共有は常に必要ですし、当然そのコストは実装規模に比例して増大します。

Redmineを使ったチケットドリブン開発はPJ管理に便利な反面、画面からAPI一つ一つの作業チケットを作成するのは意外に疲れますし、あまりルーティンワークに時間はかけたくないのです。

API開発という切り口で言えば、個々のチケットが似通ってくるので共通化して、楽できないかなーというのが今回のお話でございます。

 

Redmine API

RedmineにはREST APIが用意されているので、まずはそれを試しに触ってみました。

最近個人的にRedmineを立てたので今回はそれを使います。

(その時のブログ↓)

masa2019.hatenablog.com


 さすが天下のRedmineだけあって、APIに関する記事はかなり豊富でした。
ただ、やはり古めのPJ管理ツールなのもあってか、最近の環境での連携例は少なめでしたね。(2019年代の記事はみつからなかったです。)

その中でも下記のサイトにお世話になりました。

https://www.r-labs.org/projects/r-labs/wiki

Postmanで実行してみる

取得についてはとてもシンプルで、例えばチケットを取ってきたいのであれば、

{RedmineのURL}/issues.(json or xml)?key=<apiのアクセスキー>

 こんな感じでjsonxmlをお好みで取得できます。

アクセスキーの取得は上記のr-Lab様のサイトに説明がありますのでそちらをご覧ください。

 

f:id:masa2019:20200223223532p:plain

実行例

実行すると、こんな感じでチケット情報を一覧取得できます。

次に、チケット作成ですが、こちらもシンプルです。

f:id:masa2019:20200224232049p:plain

実行例


画像を見たらわかるように、

  • HTTP_METHOD:POST
  • Body:raw(JSON)

にセットし必須絡むとして設定した項目をJSON形式でBodyに記載します。

レスポンスは

{
    "issue": {
        "id"3,
        "project": {
            "id"1,
            "name""練習プロジェクト"
        },
        "tracker": {
            "id"1,
            "name""設計"
        },
        "status": {
            "id"1,
            "name""未着手"
        },
        "priority": {
            "id"1,
            "name""5(標準)"
        },
        "author": {
            "id"1,
            "name""Redmine Admin"
        },
        "subject""Example",
        "description"null,
        "start_date""2020-02-23",
        "due_date"null,
        "done_ratio"0,
        "is_private"false,
        "estimated_hours"null,
        "total_estimated_hours"null,
        "created_on""2020-02-23T13:39:20Z",
        "updated_on""2020-02-23T13:39:20Z",
        "closed_on"null
    }
}

 こんな感じで登録したチケット情報が帰ってきます。

 

流通BMSと在庫管理

こんにちは!株式会社スマレジ、エンジニアのmasaです。

少し、寒くなった?と思ったら今週はまたポカポカ陽気で、ちょっと体が変になってしまいそうな感があります(-_-;)

今日は流通BMSをご紹介したいなと思います。

masaは営業ではないので、あくまで開発者としてふんわり概念的に理解しているだけなので、間違いなどがあればご指摘ください汗

卸と小売業の大きな課題…「仕入」業務

流通系のお仕事にかかわりがないと、ピンと来ないかもしれませんが、
売店仕入業務は、とにかく大変なんです・・・!

① 同じ商品でも卸売業者でフォーマットが異なる

 

f:id:masa2019:20200217000415p:plain

例)キャベツがお店に届くまで

仮に全く同じ生産者の「キャベツ」が近所のスーパーに届くまでを考えても、「産地直送」「卸業者経由」「市場へ買い付け」等、バリエーションはいくつにも分かれます。

例えば仕入れるまでのステップの違いはそのまま仕入れる商品の仕入れ値、つまり「商品原価」の違いに現れます。これは原価計算時に影響を与えます。

また、各経路は「紙媒体」でのやり取りだったり、「EDI」(電子商取引)だったり、インターフェースも様々なため、検品コストが増大します。

仕入用のシステムのランニングコスト

大きなスーパーだったり、多店舗展開している小売業の場合、仕入を電子化するために受注システムを持っていることがあります。しかしシステムのランニングコストは小売り業者持ちなため、規模が大きくなればなるほど、小売業者の負担は大きくなります。

仕入業務の共通化

多岐にわたる仕入ルートのすべてを各小売業者がまとめ続けるのは非常にコストがかかります。そこで、「仕入業務そのものを共通化して、楽をしよう!」という動きが生まれます。

オンライン仕入時のメッセージ規格が「流通BMS

f:id:masa2019:20200217003504p:plain

流通BMSのイメージ

その中でも一番標準化しやすいのが、EDIです。それまで各社マチマチだった仕入元ー仕入先間のインターフェースが規格化されます。この規格のことを流通BMS(Retail Business Message Standerds)と呼びます。詳しくは下記を参照ください。

www.mj-bms.com

スケールメリットでシステムのランニングコストを削減

流通BMSに対応している仕入システムは、仕入情報受信のインターフェースが統一されているため、受注側の卸業者や生産者にとってもメリットが大きいです。それに着目して、流通BMS対応の仕入システムを卸業者が利用する場合は、通信量等に応じて使用料を徴収することができます。こうすることで、システムのランニングコストを下げ、小売業者の負担を減らすことができます。

redmineサーバーを立てようとしたら…(スタートアップスクリプト)

こんばんは!株式会社スマレジ、開発部のmasaです。

ついにスマレジ4.0の新APIが公開されました!

Developer登録を頂いた方には、検証用環境のご案内がメールにて届いておりますので、新APIをどんどんさわってみてください!

・・・で、新APIで何か作ってみよう的なお話をしようと思っていたんですが、
まだ参照APIのみの提供なので、この中で何を作るのか悩み中なので、もうしばらくお待ちください(-_-;)(ほかの人とかぶっても微妙ですし(笑))

 

今日は、redmineの個人サーバを立てようとしたら、思わぬものがあったのでご紹介したいと思います。

redmineって?

これを見ている人で知らない人はほぼいないと思うんですが、一応ご紹介。

redmine.jp

redmineプロジェクト管理Webサービスのひとつです。

redmineの特長は、プロジェクト上の各タスクを「チケット」という単位に分割して、
横断的に進捗を見る点にあります。

チケットはツリー構造を持つことができて、タスクをWBS毎に分解して作業をします。

タスクの作業者は作業の開始、進捗、終了やフィードバックなどをすべてこのチケットを通じてやり取りをしながら開発を行います。このチケットありきの開発スタイルを「チケットドリブン開発」と言ったりします。

 

さくらのVPSredmineサーバを立てようとしたら...

スマレジ4の作業チケットがドえらい数になるので、ある程度スプレッドシート越しに管理できるようにならないかと思い、検証用にredmineサーバを立てたいと思ったのが、そもそものお話です。

redmine上でチケット量産するの、結構面倒・・・。どないかならんかな・・・」と思っていたら

qiita.com


www.kompira.jp

とか、

www.slideshare.net

とか、上げたらきりがないくらい皆さんredmine APIの運用事例をあげられているので、これらを参考にGAS+redmineでチケット管理を効率化したろー!と思ったわけです。

ただ、会社のredmineでいきなりいろいろ更新系APIをいじるのに日和ったmasaは学生時代から借りているさくらVPSで試してみようかとおもったのですが・・・。

 

スタートアップスクリプト・・・!?

「さて、つかってないVPSを初期化してゴリゴリ初期設定をやっていこうか・・・」と思っていたら、

f:id:masa2019:20200209220115p:plain

redmineのスタートアップスクリプト・・・だと?

そこにあったのは[public]CentOS_Redmineという選択肢。

ほかにもgitlabやdocker-composeなんかもあるみたいで、
ここ2年くらいVPSのコンソール画面に入っていなかったmasaは浦島気分でした。
(2年前からあったかもしれないけど、たぶん当時は気が付かなかった。。。)

何はともあれ、[public]CentOS_Redmineを選んで、インストール。

OS起動後にはログインできるようになっていて、redmineの立ち上げログは

/root/.sakuravps/startup.log

ここで見ることができました。

STARTUP SCRIPTS ENDの文字がログの末尾に出たのを確認して、VPSのアドレスをたたくと・・・

f:id:masa2019:20200209220836p:plain

立ち上がってるー!

 

 

いやぁ、最近はこんなに便利なものがあるんですねー。

さくらVPSのスタートアップスクリプトの説明ページには、「さくらインターネット側で用意したスクリプト」という趣旨の記載がありましたが、AWSにもあったらいいな。

もう少しインフラを勉強しないとなと思った今日この頃でした。

スキーにいってきました!

こんばんは!

株式会社スマレジ)開発部のmasaです。

 

もう2月ですよ…。恐ろしい速さで最近時間が経過しております。

そして大阪はそこそこ寒い日はあるものの、暖冬で気候は過ごしやすいです。

さて、今回は会社の部活動の一環として、スキー旅行にいってきました!

スキー合宿!

スマレジでは、1月から2月の週末に参加者を募ってスキー旅行にいっています!

毎回場所は違うんですが、今回は栂池高原までいってきました!

白馬山麓つがいけ高原・栂池高原スキー場

栂池は温泉とスキーでよく聞く白馬の麓にあり、規模は大きくはありませんが、ゲレンデ街として雰囲気のある街です。

雪が心配になる道中

行程は金曜日に早めに退勤して、夜に出発→SAで休憩しながら栂池を目指すルートだったんですが…

…雪が無ェ

長野に入っても雪の姿はなく、

「せっかく長野まできて、べしゃべしゃの雪で滑るのはやだなー」という声が誰ともなく聞こえてきました。

坂を上ると、そこは銀世界でした。

そんな不安を抱えながら仮眠をとり、高速を降り、ちらちらと現れ始めた積雪に一喜一憂していた一行。

山にも雪がかぶっているのは見えていたのですが、我々の行き先二度の程度つもっているのか…


f:id:masa2019:20200202225802j:image

 

まあ、杞憂だったんですけどね笑

確かに例年に比べるとかなり少ない積雪ですが、山道も車も登りやすかったそうなので、結果オーライです。

雪質は上々

車をホテルに泊めて、レンタル一式かりて、いざゲレンデにおりたつと、堅めの雪の上に新雪が20cmほどつもっていて、とても滑りやすかったです!


f:id:masa2019:20200202230232j:image

夜はUNOにスマブラ!?

ひとしきり滑って、ご飯食べて、風呂はいって…

宴会会場に訪れて、飛び込んできたのは、大の大人がスイッチとUNOをそれぞれを囲んでいる姿(笑)

何でも、UNOは毎年の恒例になっているとかで、盛況でした。

この旅行は家族参加されている方も多いので、お子さんも一緒に楽しめるものに落ち着いたとか、違うとか。

そして、スマブラは前にブログで取り上げましたが、最近はやっております。

masaは例によってスマブラ参戦してボコボコにされておりました汗

 

こんな感じで、スマレジではみんなでアクティビティに参加する土壌があります。

こういうの、いいな。と思ってくれた方は、是非我々と一緒に、はたらきましょう!

 

 

 

プログラミング以外の仕事

こんばんは!株式会社スマレジ、開発部のmasaです。

町を歩くとマスクをつけている方が増えているのを感じます。皆様も予防にはおつとめください。

 

今日は僕のプログラミング以外の作業を一部紹介したいと思います。

 

プログラミング以外の仕事

IT系以外のお仕事をされている方は驚かれることもあるんですが、スマレジではプログラミングだけしているプログラマって少ないです。

1 設計

仕様を検討する部分がなたくさんある対応については、プログラムを書く前に設計書を作成します。設計書は他の人の意見を効率的に採り入れたいときに役に立ちます。例えば、デザインチームと連携したり、営業やカスタマーサクセスなどの他部署へ、込み入った仕様を説明する必要がある場合があげられます。設計書は基本的にはGoogle Documentなどで作成します。

 

2 お問い合わせの対応

基本的に問い合わせの対応はカスタマーサクセス部が受けるのですが、詳細な仕様については、開発部でうけることもあり、僕もスマレジ管理画面のお問い合わせ対応を一部対応しています。

3 運用作業

スマレジには一部、システム化していない機能が存在します。

例を挙げると、管理者アカウントの変更や外部連携サービスの対応がそれに当たります。

これらは発生件数自体があまり高くないので一部の設定作業をオペレーション化しています。

 

思いつくところだと、こんな感じです。

どの程度あるの?

基本的には問い合わせと運用作業が1日1時間~3時間くらいで、設計についてはそのときの案件の性質によります。

いっさいプログラムを書かないで、設計作業だけの日もまれですがあります。

また、設計を伝えるコミュニケーションの時間も含めると、規模の大きさを問わなければほぼ毎日あります。コミュニケーションがニガテな方には厳しい環境かもですね。

 

Lucid ChartからLaravelのrouteのひな形を生成する(2)

こんにちは、株式会社スマレジ、開発部のmasaです。 前回に引き続き、LaravelのrouteファイルをLucid ChartAPIを利用して生成していきます。 今回は実際に簡単なrouteファイルを出力するところまでをやります。

ちょっと仕様変更

f:id:masa2019:20191222182119p:plain

これまでの表でrouteファイルを生成する場合、 1. actionにuriのパスが書かれているので、actionとuriが一致(CakePHPみたいな感じ)しているものしか作れない 2. タイトル欄(票の一行目)に何を書くのかがイマイチわかりずらい(タイトル欄はコメントに記載する予定だった) 3. わかりずらい 等の問題を感じたので、↓のようにタイトルにパスが入る仕様に変えちゃいます。 (今後も仕様的にだめだなぁと思ったら都度変えます)

f:id:masa2019:20200119220837p:plain
新仕様。タイトル欄にURLを書くように。

Laravel6でファイルを読み書きする

Laravelでは、ファイルファザードが用意されているので、それを利用します。 LucidComponent.php

   use http\Exception\UnexpectedValueException;
   use Illuminate\Support\Facades\Log;
   use Illuminate\Support\Arr;
+ use Illuminate\Support\Facades\File

File::put('storage/file.txt', $content);みたいな感じで使えます。

routeファイル生成ロジックを作る

既に必要な情報は、前回のブログ↓で取得処理は作成しているので、あとは成型するだけです。 今回は、 1. 出力ファイル名はtest.phpで固定 2. URI以外は必ず二列以上 3. タイトルはPHPDOCに記載するだけ というシンプル仕様で作成します。 次回以降はこれをたたき台にして、仕様を詰めたいですね。

下記、ロジック。(LucidComponent.phpに追記)

<?php
    /**
     * laravel6のrouteファイルを生成する
     * @param array $pageInfos
     * @return bool 成功すればtrue;
     */
    public function createLaravelRouteFile($pageInfos)
    {
        //ex. Route::get('/ecosystems/{id}', 'API\EcosystemController@index');
        if (!is_array($pageInfos) || count($pageInfos) == 0) {
            return false;
        }
        $headRouteContent = "Route::";
        $contentArray = [];
        $sentenceArray = [];
        foreach ($pageInfos as $pageInfo) {
            if (count($pageInfo) == 0) {
                return false;
            }
            $commentDivision = 0;
            $commentArray = [];
            $filePath = "";
            $controller = "";
            $action = "";
            $httpMethod = [];
            for ($i = 0; $i < count($pageInfo); $i++) {
                if (count($pageInfo[$i]) == 1) {
                    $filePath = $pageInfo[$i][0];
                    continue;
                }
                if ($pageInfo[$i][0] == "title") {
                    $commentArray[] = $this->toggleCommentEdit($pageInfo[$i][0], $pageInfo[$i][1], $commentDivision);
                    continue;
                }
                if ($pageInfo[$i][0] == "controller") {
                    $controller = $pageInfo[$i][1];
                    continue;
                }
                if ($pageInfo[$i][0] == "action") {
                    $action = $pageInfo[$i][1];
                    continue;
                }
                if ($pageInfo[$i][0] == "HTTP_METHOD") {
                    $httpMethod[] = $pageInfo[$i][1];
                    continue;
                }
            }
            foreach ($httpMethod as $method) {
                $sentenceArray[] = $headRouteContent . strtolower($method) . "('$filePath', " . "'$controller@$action');";
            }
            $commentArray[] = "*/";
            $comment = implode("\n", $commentArray);
            $sentence = implode("\n", $sentenceArray);
            $contentArray[] = $comment . "\n" . $sentence;
        }
        $content = "<?php \n";
        $content .= implode("\n", $contentArray);
        File::put('storage/test.php', $content);
        return true;
    }

    /**
     * toggleDivisionの値に応じてコメントを記載する
     * 0:コメント開始
     * 1:コメント記載
     * 2:コメント終了
     * @param string $lavel タイトル
     * @param string $content 中身
     * @param int $toggleDivision コメント区分
     * @return string
     */
    private function toggleCommentEdit($lavel, $content, &$toggleDivision) {
        $sentence = "";
        if ($toggleDivision == 2) {
            $sentence .= "* " . $lavel . ":" . $content . " */";
        } elseif ($toggleDivision == 1) {
            $sentence .= "* " . $lavel . ":" . $content . " ";
        } else {
            $sentence .= "/** \n* $lavel:" . $content . " ";
        }
        if ($toggleDivision == 0) {
            $toggleDivision = 1;
        }
        return $sentence;
    }

固定の文字列系は定数化させる予定です。 これはひとまず動くものって感じです。 出力すると下のような感じに。

<?php 
/** 
* title:ユーザ情報 
*/
Route::get('detail', 'User@view/{id}');
/** 
* title:一覧 
*/
Route::get('detail', 'User@view/{id}');

ここからいろいろ揉んでいきましょー。

Lucid ChartからLaravelのrouteのひな形を生成する(1)

こんにちは、株式会社スマレジ、エンジニアのmasaです。

今日は以前から作っているLucid Chart APIの矩形データ読み込みを使って、Laravelのrouteファイルのひな形を生成したいと思います。

 前回のブログ↓

masa2019.hatenablog.com

Laravel6.xのroute機能

readouble.com

基本的なことは↑の公式ドキュメントで確認してください。 このシリーズでは特に↓のような

Route::get('/user', 'UserController@index');

http_methodとコントローラ、アクション、パラメータの指定までを生成したいと思います。 今回は取り急ぎの仕様と、前準備としてHTTP_METHODをページ情報に紐づけるところまでをやります。

Lucid Chartで作成する画面遷移図の仕様

今回は一番簡単な「テーブルから必要情報を抜き出して、ファイルを生成する」ことを考えます。 以前のブログでも使用した↓のテーブルが1つの画面だと考えます。 f:id:masa2019:20191222182119p:plain

でHTTP_METHODについては、矢印側で表現するようにします。

f:id:masa2019:20200113000834p:plain
遷移図の例

矢印情報からページのHTTP_METHODを取得する。

過去ブログ(↓)でも記載したように、Lucid Chartでは矢印情報に矢印の形(矢じりなのか、何もないのか等)と始点と終点の矩形のIDが付与されています。 masa2019.hatenablog.com

なので、「矢印の形が矢じりであり、IDが一致する場合の矢印のtext」を取得し、テーブル情報に追加する処理を追記します。 HTTP_METHODは一つのURLに複数定義できるので、今回は

[
  [ "HTTP_METHOD", "GET"],
  [ "HTTP_METHOD", "POST"],
  [ "HTTP_METHOD", "DELETE"]
]

という風にセットするようにします。(カンマ区切りで連結してもよいかと)

HTTP_METHODの取得処理は↓のような感じ

   /**
     * 矢印情報からそのページに必要なHTTP_METHODを取得する
     * @param \SimpleXMLElement $block ページ情報
     * @return array|bool 該当するmethodがあれば
     */
    private function getAllowHttpMethod(\SimpleXMLElement $block) {
        $lines = $this->getLines();
        $blockArray = (array)$block;
        $blockId = Arr::get($blockArray, 'id');
        if (blank($blockId)) {
            return false;
        }
        foreach($lines as $key => $line) {
            $lineArray = (array)$line;
            $endpoint1 = Arr::get($lineArray, 'endpoint1');
            $endpoint1Array = (array)$endpoint1;
            $endpoint2 = Arr::get($lineArray, 'endpoint2');
            $endpoint2Array = (array)$endpoint2;
            //矢印の矢(Arrow)側のIDを取得
            if (Arr::get($endpoint1Array, 'style') == "Arrow") {
                $lineId = Arr::get($endpoint1Array, 'block');
            } elseif (Arr::get($endpoint2Array, 'style') == "Arrow") {
                $lineId = Arr::get($endpoint2Array, 'block');
            } else {
                $lineId = "";
            }
            //IDが一致していたら、矢印のHTTP_METHODを登録
            if ($lineId == $blockId) {
                $httpMethod = strtoupper(Arr::get($lineArray, 'text'));
                $httpMethodArray = ["POST", "PUT", "GET", "DELETE", "PATCH", "OPTION"];
                if (array_search($httpMethod, $httpMethodArray) != false) {
                    return [
                        "HTTP_METHOD",
                        strtoupper(Arr::get($lineArray, 'text')),
                    ];
                }
                $this->setErrorInfo("http_method string is wrong.");
                break;
            }
        }
        return false;
    }

これを↓のところで呼ぶ。

    /**
     * ページ情報配列を作成
     * @return array
     */
    public function getTableInfos() {
       $blocks = $this->getBlocks();
       $pageInfos = [];
       foreach ($blocks as $block) {
           $aryInfo = (array)$block;
           if (!in_array($aryInfo["class"],self::LUCID_TABLE_CLASS_LIST, true)) {
               //テーブル以外はスキップ
               continue;
           }
+           $pageInfo = $this->getTableInfo($block);
+           $allowHttpMethod = $this->getAllowHttpMethod($block);
+           if (is_array($allowHttpMethod)) {
+               $pageInfo[] = $allowHttpMethod;
+           }
+           $pageInfos[] = $pageInfo;

       }
       return $pageInfos;
    }

これで、getTableInfos()でえられるページ情報が下記のような感じに。

f:id:masa2019:20200113013507p:plain
HTTP_METHODを追加

ひとまず、これで最低限のrouteを書く情報はそろいました。 正規表現への対応とか、LaravelのRouteの機能はまだまだありますが、それは骨を作ってから拡張していく方向で考えています。