株式会社スマレジの開発部でスマレジのサーバサイドを作っています

electron+react+gas+spreadsheetでgoogle calender用のスケジューラーを作る(3)

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

今更なんですが、このブログもう4年になるんですね。つまり入社してもう5年目。。。 この業界で同じ会社に4年いるのって長いんでしょうか? 今いるチームでは僕が一番古株なんですが、うちの会社は長い人だと10年以上の人もいるので、 あまり長いって感じがしないんですよね笑

まあ、それだけ働きやすい職場ってことなのかもしれないですね。

と言うわけで、スマレジでは一緒に働いてくれる仲間を募集しています。(定期)

corp.smaregi.jp

今、このリンクを貼るのに「スマレジ 採用」でググったら採用難易度とか出てきました。

離職率ほぼ0は書いてあるように営業ですね。これは本当だと思います。 (社員が言うんじゃ説得力ないですが笑) エンジニアはやっぱり一定の流動性があります。ただ業界内でめっちゃ高いとかはないので、 業界特性なんかなーと思っています。あと、偉い人がコロコロ変わったりとかはないです。 それがいいのかどうかはわからないですが、上司が変わって仕事のやり方がコロコロ変わるとかはないです。

話がそれましたが、今日はreact側のお話。

新しいプロジェクト(バージョン)を作るときにredmineチケットのテンプレートを登録するようにする

前回、プロジェクトでルーティンで実施するMTGや作業について、Google Calendarに登録する仕組みを紹介しました。 利用しているAPIと利用箇所のリンク中心でしたが、詳しい仕組みなんかはコードを追っかけてください。

今回は、上のカレンダーとセットでRedmineで毎回作るチケットについて、テンプレートマスタをスプレッドシートで用意しておいて、 それをGASで作ったAPIで読み込んで読み込んだ情報を元にRedmineに登録する仕組みを作っていきます。

完成系はこんな感じです。(まだ見た目だけですが)

左側のツリーを開くと、チケットの概要が出てきて、タイトルやfrom-toと概要について編集できるようになっています。 実際はこれにチェックボックスを追加して、「そのバージョンで使うチケットと使わないチケット」を切り替えられるようにする予定です。

ソースコード(作成中)

まだ作成中なんですが、こんな感じです。

import React from 'react';
import './template-ticket-tree.css';
import {TreeView, TreeItem} from "@mui/lab";
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import MDEditor from '@uiw/react-md-editor';
import Datetime from 'react-datetime';

export class TemplateTicketTree extends React.Component {
    constructor(props) {
        super(props);
        this.renderTree = this.renderTree.bind(this);
        this.render = this.render.bind(this);
        this.setTicketDetail = this.setTicketDetail.bind(this);
        this.searchNode = this.searchNode.bind(this);
        this.setLabel = this.setLabel.bind(this);
        this.tree = React.createRef();
        this.tree = this.props.tree;
        this.state = {
            id: "",
            label: "",
            startDate: "",
            endDate: "",
            content: "",
            callback : this.props.callback
        };
    }

    searchNode(id, nodeId) {
        if (typeof nodeId === "undefined") {
            nodeId = this.tree[0].id;
        }
        let node = null;
        for (let i = 0; i < this.tree.length; i++) {
            if (this.tree[i].id === nodeId) {
                node = this.tree[i];
            }
        }
        if (id === nodeId) {
            return node;
        }
        let search = null;
        for (let i = 0; i < node.children.length; i++) {
            search = this.searchNode(id, node.children[i]);
            if (search !== false) {
                return search;
            }
        }
        return false;
    }

    setTicketDetail(node) {
        const searchNode = this.searchNode;
        let resFunc = function() {
            const targetNode = searchNode(node.id);
            this.state.id = targetNode.id;
            this.state.content = targetNode.content;
            this.state.startDate = targetNode.startDate;
            this.state.endDate = targetNode.endDate;
            this.state.label = targetNode.label;
            this.setState(this.state);
        }
        resFunc = resFunc.bind(this);
        return resFunc;
    }

    setLabel(e) {
        this.setState({
            label: e.target.value
        })
        let node = this.searchNode(this.state.id);
        node.label = e.target.value;
        this.state.callback(this.state.id, node);
    }

    renderTree(node) {
        const tree = <TreeItem nodeId={node.id} label={node.label} onClick={this.setTicketDetail(node)}></TreeItem>;
        tree.props.children = [];
        for (let i = 0; i < node.children.length; i++) {
            for (let j = 0; j < this.tree.length; j++) {
                if (this.tree[j].id === node.children[i]) {
                    tree.props.children.push(this.renderTree(this.props.tree[j]));
                }
            }
        }
        return tree;
    }

    render() {
        return (
            <div className={"container"}>
                <div className={"row"}>
                    <div id={"screen"} className={"col-3"}>
                        <div className={"tree-container"}>
                            <TreeView
                                aria-label="template ticket tree"
                                defaultCollapseIcon={<ExpandMoreIcon />}
                                defaultExpandIcon={<ChevronRightIcon />}
                                sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
                            >
                                {this.renderTree(this.tree[0])}
                            </TreeView>
                        </div>
                    </div>
                    <div id={"ticket-info"} className={"col-9"}>
                        <div className={"container"}>
                            <div className={"row g-1"}>
                                <div className={"col-auto"}>
                                    <label>
                                        タイトル:
                                    </label>
                                </div>
                                <div className={"col-auto"}>
                                    <input
                                        type="text"
                                        id={"title"}
                                        value={this.state.label}
                                        class="form-control"
                                        onChange={this.setLabel}
                                        maxLength={128}
                                    />
                                </div>
                            </div>
                            <div className={"row g-1"}>
                                <div className={"col-auto"}>
                                    <label>
                                        作業期間:
                                    </label>
                                </div>
                                <div className={"col-auto"}>
                                    <Datetime
                                        id={"startDate"}
                                        value={this.state.startDate}
                                        locale={"ja"}
                                        dateFormat="YYYY-MM-DD"
                                        timeFormat={false}
                                    />
                                </div>
                                <div className={"col-auto"}>
                                    <span className="form-control-plaintext">~</span>
                                </div>
                                <div className={"col-auto"}>
                                    <Datetime
                                        id={"endDate"}
                                        value={this.state.endDate}
                                        locale={"ja"}
                                        dateFormat="YYYY-MM-DD"
                                        timeFormat={false}
                                    />
                                </div>
                            </div>
                            <div className={"row"}>
                                <div className={"col-12"}>
                                    <MDEditor
                                        value={this.state.content}
                                        onChange={(e) => this.setState({content: e})}
                                    />
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}

呼び出しもとはこんな感じ.

    renderTemplateTicketTree() {
        return (<TemplateTicketTree tree={this.state.templateTicketTreeInfo} callback={this.updateTreeInfo}></TemplateTicketTree>);
    }

tree propsにスクショ左側のチケットツリーの情報を渡してあげる感じです。callbackは子→親へのデータ受け渡し用。

渡すデータの構造はこんな感じ。

[
                {
                    id: "1",
                    label: "プロジェクトチケット",
                    startDate: "2023-01-23",
                    endDate: "",
                    content: "test",
                    children: ["2","3",]
                },
                {
                    id: "2",
                    label: "テストチケット(POS)",
                    startDate: "",
                    endDate: "",
                    content: "test2",
                    children: []
                },
                {
                    id: "3",
                    label: "テストチケット(在庫)",
                    startDate: "",
                    endDate: "",
                    content: "## test",
                    children: ["5"]
                },
                {
                    id: "5",
                    label: "テストチケットA",
                    startDate: "",
                    endDate: "",
                    content: "### test",
                    children: []
                }
]

利用しているtree表示とmdファイルのライブラリはnpmから利用可能です。

tree表示はMaterial UIさんのコンポーネントです。インストールコマンドは下記のサイトから。

mui.com

MDファイルエディタさんはこちら。

github.com

さすがreact。コンポーネントめっちゃ充実してます。感謝。

electron+react+gas+spreadsheetでgoogle calender用のスケジューラーを作る(2)

明けましておめでとうございます。株式会社スマレジ、開発部のmasaです。

皆様は年末年始はいかが過ごされたでしょうか? masaは弟がちょうど年末にコロナになってしまい、実家で過ごそうかと思っていたのですがお流れに・・・。 久しぶり(大学院生以来かも?)の一人のお正月でした。

さて、今回はスケジューラーの続きです。githubのリンクは↓

github.com

GAS側で利用するAPIたち

前回のブログ

masa2019.hatenablog.com

electronからコールするGASで作った簡易APIで利用するGoogle Calendar関係のAPIについて、 前回の技術要素でも簡単に触れていましたが、今回はもうちょっと掘り下げて取り上げていきます。

カレンダーに予定を登録する

カレンダーに予定を登録する場合、終日イベントか時間が決まったイベントかによって、利用するAPIが変わります。

終日イベントを登録する

終日イベントの登録はCalendar::createAllDayEvent()を利用します。

公式ドキュメントリンク developers.google.com

スケジューラーで使っているところ github.com

公式ドキュメントを見てもらえると書いてありますが、createAllDayEvent()は3パターンオーバーロードされていて、 一番詳細設定できるパターンでは、title(予定のタイトル), startDate(開始日), endDate(終了日), optionsの4つが指定できます。 optionsでは、予定で設定できる開催場所や、ゲストの招待、メールの発信有無などをJSONで設定することができます。

時間が決まったイベントを登録する

時間が決まったイベントの登録はCalendar::createEvent()を利用します。

公式ドキュメントリンク developers.google.com

スケジューラーで使っているところ github.com

開始日時と終了日時が必須になったこと以外はcreateAllDayEvent()と大きくは変わりません。optionsの設定も同様に可能です。

スケジューラーでは開始日の設定有無で判定していますが、これはズボラしているので後で直します。 (githubのソースを見ると、全体的にentityなどが準備されていないですが、最低限の機能が備わったらGAS側と同じようにTSに移行しようかなと思っているので、その時にまとめて綺麗にする予定。)

イベント更新のAPIは存在しない。

既存のイベントを指定して、更新するAPIは公式ドキュメントを見る限り現状ないようです。 なので、すでにあるイベントの日時を変更したいときは、delete-createする必要があります。

イベントを削除するAPIとイベントを検索するAPI

イベントの削除はEvent::createEvent()を利用します。

公式ドキュメントリンク developers.google.com

スケジューラーで使っているところ github.com

注意が必要なのは、Calendarではなく、Eventクラスの方に削除があると言うことです。Calendar側で検索削除するようなIFはないので、スケジューラーで実装しているように、Calendar::getEvents()で必要なイベントをリストアップして、その中から指定した条件に合致するものをループで検索して削除するような実装になりそうです。

        const events = calendar.getEvents(new Date(from), new Date(to)); // <-- ここでカレンダーに紐づいているイベントを取り出す
        let res = {
            status: "not found",
        };
        let start: GoogleAppsScript.Base.Date;
        let end: GoogleAppsScript.Base.Date;
        for (let i = 0; i < events.length; i++) {
            if (events[i].getTitle() === versionName + searchString) { 
                // 検索条件(イベント名がバージョン名+指定の文字列と一致)にヒットすれば削除
                res.status = "deleted";
                events[i].deleteEvent();
            }
        }

カレンダー関係で利用するAPIはこれくらいです。 Calendar::getEvents()の期間指定が結構広くとってもOKなのが、さすがGoogleだなと思いました笑

一年を振り返って

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

今回は恒例ですが、1年の振り返りになります。

今年やったこと

masaが2022年に関わったもののうち、わかりやすいものに絞るとこんな感じです。

  • 実装を実施した・手伝ったもの
    * 削除済み取引履歴閲覧機能
    * 売上比較(商品属性別)のプラットフォームAPI対応
    * プラットフォームAPI 精算一覧取得API
    
  • 設計・工数管理・PJ進行したもの
    * 売上比較(商品属性別)の公開(設計レビューア)
    * PHPバージョンアップ(タスク整理・進捗管理)
    

去年はプラットフォームAPIの非同期系のAPIの設計や実装が主でしたが、今年からはそういった設計・実装に加えて管理業務も出てきた感じです。 この中から印象に残った物をあげていこうかと思います。

PHPバージョンアップ

これが一番緊張しました。何せスマレジはリリースした時から一度のPHPバージョンアップをやってきてこなかったので、バージョンは5.3。 そこから、7.4系に上げることになるので、あげた瞬間に画面が動かなくなるわけで。。。

PHPそのものの書き換えだけでなく、バージョンアップに伴うライブラリの動作変更なども激しく、 ローカル環境でトップ画面が出るようになるまで、(この作業自体は何人かで分割してやってたんですが)二週間程度かかってました。。。汗

こんな状態ですから、変更箇所も多い上に、レジの心臓部である金額計算ロジックもかなり変更が発生したので、 インフラチームと相談しながら、分割リリースする形で影響を局所化しながら対応していきました。

また、いつもなら品質管理部門でやるようなリグレッションテストなども、全画面チェックが必要になるわけなので当然手が足りず、 開発でも実施したりと、スケジュール調整も結構大変でした。

メンバー自体、5人前後しかいないチームなのでメンバーに負担がかかるシーンもありましたが、 なんとか大きなトラブルなくリリースできて良かったです。

売上比較(商品属性別)の公開

これは、僕のチームのメンバーで入社2年目の方の設計業務デビューとして、設計主担当ではなく設計補助やレビューアとして入ったものです。 あと、実装も一部担当しています。(メンバー少ないので基本的に実装はどこかしら担当することになる)

僕もスマレジで設計は覚えた人(と言うか去年までじゃんじゃん設計してた人)なので、人に教えるほどものを修めたつもりはないんですが、 基本的な部分や社内の設計を含めた開発フローなんかを主担当の方の相談に乗りながら、伝えていきました。 また、これについては0から設計ではなく、社内で検討していたベースがあったのもあり、画面仕様的なところはスムーズに進んだと思います。 プロトタイピングは大事ですね。

今までの分析画面にはない、いわゆるクロス集計ライクに使う機能なので、性能面に苦しめられた印象が強く、今もデータ量によっては遅い時があるので継続した改善が必要な機能なんですが、これから同じようなクロス集計を作っていく上で良いベースにはなったんじゃないかなと思います。

管理業務全般

管理業務は今年が初めてだったんですが、やっぱりメンバーの少なさに悩まされるシーンは多かったです。 本当は、メンバーごとの働き方に対するスタンスに合わせながら仕事をアサインしていきたいんですが、作業量的な問題で難しいこともありました。 来年は多くのメンバーを受け入れられるような体制づくりにも、力を入れていかないとなと痛感しています。

実はここには書いてないお仕事も結構いっぱいあったのですが、記事にするにはまだ青い内容も多いので、 その辺はまた別の機会に書こうかなと思います。(主に性能改善や生産性可視化などのお仕事)

electron+react+gas+spreadsheetでgoogle calender用のスケジューラーを作る(1)

こんにちは!株式会社スマレジ、開発部のmasaです。 御堂筋のイルミが今年も始まっていますねー!masaも昨日買い物の帰りに眺めながら歩いていました。 まだ、葉っぱが散っていないのですが、それでも結構しっかり光っているのがわかるのが面白いです。

さて、今回はタイトル通り、このブログで使っていることを詰め込んで、開発をしているのでそのお話を。

やりたいこと

プロジェクトを進行していると、「この作業毎回必要だな」と思うような、いわゆるWBSに反映させたい内容が出てきます。 例えば、下記のようなこと。

  • テスト期間中に、パフォーマンスツールやバグ監視ツールを見て、テスト環境でエラーが出てないかとか、負荷が安全値内か確認する
  • 工数の着地やリリースのための事務作業(特にいつまでにやらないといけないのか)

こういったことをもれなく、ダブりなく、やるべき時にやりたいわけです。(たまにもれそうになってヒヤッとする) そのために次のバージョンの作業について、何をするか検討している時に、こういった毎回発生するものについてはスプレッドシートでリスト化して、そのリストベースでGoogleカレンダーに登録しちゃいたい、と言うのが今回のやりたいことです。

要素技術

参考文献という名前の過去ブログ

Electron + Reactの過去記事

masa2019.hatenablog.com

GASは今回はAPIを使うので、外部会員連携の時のブログが使えそうです。

masa2019.hatenablog.com

GASでgoogle calenderに予定を登録

参考リンク↓

developers.google.com

これは簡単。

  const calendar = CalendarApp.getCalendarById("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@group.calendar.google.com");
  calendar.createAllDayEvent("test event all day", new Date("2022-12-11"));
}

ここではグループ予定にタスクを追加しています。(PJメンバー全員に見せたいので) グループ予定のID(@group.calendar.google.comの部分)については、下記画像の「設定と共有」を開いて、

「カレンダーの設定」の下の方に行くと「カレンダーの統合」があって、そこにあります。

上のソースは全日のタスクを入れる例ですが、時間指定も可能です。 実際にこれを実行してみると、下のような感じでタスクが登録されます。

ただし、グループ予定に追加できるのはそのグループの管理者のみなので注意が必要です。 ただ、管理者ユーザ実行扱いでウェブアプリとして公開(doGet,doPost)した場合は、その公開範囲に応じて実行可能なようなので、 リクエストに認証用の秘密鍵をつけておくか、別にトークン発行用のAPIを作って、スクリプトプロパティを使って認証するのが安全そうです。

masa的に気になったサブスク2選

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

弊社スマレジでは、スマレジ・POSをはじめとして、いくつかのサブスクリプション型のWebサービスを提供しています。

私自身、以前にも紹介したLucid ChartやJet Brain(PHP Stormとか)などのIDEのサブスクサービスを使って開発しています。

www.lucidchart.com

そこで今回はあえてWebではない(申し込みとかはWebですが)サブスクに目を向けてみます。 masaの独断と偏見によるNonウェブサービスなサブスクを2つご紹介したいと思います。

Post Coffee

postcoffee.co

毎月、指定した量のコーヒーを、指定した状態(豆のまま、指定の挽き方、コーヒーパック)で送ってきてくれるサービスです。 (コーヒーサブスクの業界はあまり詳しくないですが、割と早い段階からあったイメージがあります) コーヒーのサブスクサービスは他にも色々ありますが、Post Coffeeは比較的細かくサービス内容について設定できます。

気になった理由

コーヒー豆という嗜好性の別れて、かつ(気に入られれば)解約されづらい商材を扱っている点がまず目にとまりました。 また、(他のコーヒーサブスクはわからないですが)Post Coffeeさんはコーヒーパックでの提供もあるので、いわゆるBtoBでの販売もやりやすそうです。 (オフィスの教養部分にコーヒーパックを置いとくイメージ)

食品系のサブスクだと、他にはお菓子のサブスクなどがありますが、製菓の場合はどうしても既存ブランド(どこのお店の新作とか)が強く、 独立したサブスクサービスというより、ある程度のスケールを持った製菓ブランドのネット販売の方が強いイメージがあります。 ただ、お菓子自体を内製して、独自の価値を付加しているところもあり、そういった差別化ができているところは別なのかなと。

それに比べ、コーヒーやお茶などは原産国からの輸入ができれば、他の業者と商材の面で差がつくことはないので、 元々食品輸入のノウハウがある人がやるなら、お菓子よりも手堅そうなイメージがあります。

無印良品の家具

www.muji.com

家具のサブスクも結構種類がありますが、無印良品でもやっているんですね。 ちなみに、サブスクと別に古いベッドの引き取りサービスなどもあるらしいです。

気になった理由

単純にmasaの家の家具が無印良品だらけなのが大きいです笑。 ただ、家具など月額が一定額以上になる場合、どうしても現物確認がしたくなるもの。 その点、現物を手軽に確認できる店舗型ビジネスをやっているブランドさんの方が、 家具サブスク専門でやっている業者さんと比較して強みがあるなと。

スマレジ との類似点

Webサービスが主体なイメージのあるスマレジ ですが、実際それぞれのサービスについて調べてみると、 類似している部分もあります。

一つが「倉庫」の存在です。 「え、スマレジ で倉庫あるの?」と思われそうなのですが、納品するiPadなどの機器を保管する物流拠点があります。 これについてはいわゆる家事代行など、人間が提供するサービスのサブスクや、納品がなく一定のインターネット機器を保存していないSaaSでない限りは、 在庫を持つ=倉庫を持つ必要があるという点で、類似しているかなと。

また、当たり前の話ですが、解約されにくい商材を選んだマーケットが残っていくイメージはあります。 コーヒーにしろ、レジにしろ、理由は異なりますがコロコロと変えにくい(変えることにコストが発生する)商材なのかなーというイメージがあります。 (コーヒーやタバコなどの嗜好品は特定の銘柄に拘って消費し続ける傾向がありそう。ただそれゆえに価格競争にもなりやすそう。POSについては移行コストが大きい。) そういう意味ではお菓子のサブスクなどは、トレンドを追いかけて多彩な変化があることをユーザは期待するので、 サービス企画側のセンスとトレンドをキャッチアップする能力がより問われそうですし、システムもトレンドに柔軟に対応できるような作りでないといけなさそうです。

異なる業種のサブスクでも比較してみると、類似点が見出せるのは面白いですよね。 あくまでmasaの感覚ベースなところが大きいので、ここで書いたことが的を得ているかはわからないですが、 こういった思考は新しい機能を考えたりするときにも必要になってくるので、意識して精度を高めていきたいです。

ReactとVue.jsを使ってみて思ったこと

こんにちは! スマレジ開発部のmasaです。

グッと冷えてきましたね。でもフリースを出すにはまだちょっと暑いような微妙な感じです。 夏に布団カバーを麻に変えたんですが、ちょっと冷たく感じるようになってきたので、 冬用カバーに変えようか検討中です。

さて、今回は表題の通りReactとVueを両方使って思ったことをまとめます。

長く保守するプロダクトならReactが良さそう

Reactは最近触り出したので、浅い所感ですがVueのように一つのファイルに作りたいものを詰め込んでいく(html,css,JSをvueファイルにまとめて書いてbuildするときにまとめる)場合、どうしても同じようなコードが散逸しがちです。

特にVueは仕組みが分かりやすく、0から作るスピードはReactより上なので、工数が十分でない時は工期優先でコードの散逸が起こりがちです。 「Vueはパーツひとつひとつを独立させて、パーツの組み合わせ方を工夫すればいいんだから、散逸したところで問題ないでしょ」という意見もありそうですが、言語仕様がバージョンアップなどで変更するときは影響を受けるので、リスクは高くなってしまうかなと。

反面、Reactは学習コストは確かにVue.jsよりも高いものの、

  • 構成するファイルが全てJS+CSSであり、ファイルも.js, .cssのみなのでプラグインが提供されていないIDEでも扱いやすい
    • htmlはJS内部に記載するけど、記載できる場所が限定されているのであまり気にならなかったです。
  • 原則、JSの言語仕様に則っていて、フレームワークとアプリの棲み分けがVueに比べてわかりやすい
  • JSのフレームワークだけど各パーツをclassベースで定義することも手軽にできるので、バックエンドエンジニアに優しい
    • Vueはテンプレートエンジンを触っている感覚の方が近い感じだった

というメリットを感じています。 パーツやコンポーネントひとつひとつが独自仕様になりづらく、ある程度統一感を持たせた実装をエンジニアに促しやすい点で、長期的なメンテが必要なプロダクトには向いているかなと思いました。(ただ、Vueと同じでコンポーネントの切り方などは自由なので、アプリケーションアーキテクチャを一人が決めて、周りがそれに追従する形がいい気がします)

というところで短いですが今回はここまで。 (最近忙しくてなかなかガッツリしたのが書けない汗)

上半期を振り返って

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

グッと気温が下がってきて、秋めいてきましたね。 温度変化が激しいので皆様風邪は気をつけてください。

ここ半年のmasaのお仕事とその振り返り

今日は定期的に書いている振り返り記事になります。 この記事を通じて、POSチームではどんなお仕事をしているのかを知ってもらえれば幸いです。

売上比較(商品属性別)の実装

売上分析機能の新しい機能追加です。 masaはPMしながら、この時合わせて追加された「商品属性」のAPIの実装の一部をやっていました。 機能は下記ヘルプサイトを見て欲しいのですが、ざっくり言えば売上を商品の属性(部門やオプショングループなど)改装別に確認できる画面です。

help.smaregi.jp

(余談)スマレジのPMはプレイングマネージャー

会社によってPMが管理中心なのか、実装もやる(プレイングマネージャー)のか別れるとことがあると思います。 他のチームはわからないですが、POSチームの場合は基本プレイングマネージャーです。 理由は、管理作業のみではプロダクトに関わる機会が極端に絞られるので、 仕様把握ができず、結果として工数計算ができないというところがあります。 (実装の人手が足りてないというのもありますが...汗)

精算一覧取得API

最近のお仕事だとこれですね。 これは実装がメインです。(設計はチームメンバーが担当) メンバーの中で設計にチャレンジしたい方がいたので、その方に設計をやってもらいつつ、 要件が漏れたところなどは、ある程度実装でカバーしながら作る形になりました。

振り返ってみて

PM業務自体が初挑戦だったんですが、前職がプライムベンダーだったのもあってそこまで苦労はなかったです。 ただ、設計に時間が取れなくなったりすると精度が下がるので、見積もりがずれが起きたりするのが最近は悩ましいですね。。。

あと、記載しているお仕事の量が少ないですが、これ以外も色々あります。 ただ、プロダクトの技術的な情報に関係するプロジェクトだったりで、ここでは紹介できないものになります。